Compare commits

...

456 Commits

Author SHA1 Message Date
Applevangelist
dd0b2ace65 Merge remote-tracking branch 'origin/master' into develop 2025-03-30 16:51:23 +02:00
Applevangelist
dc83af4d02 #SHORAD - fix typo for maxscootdist
#CTLD - slight refactoring of fixed wing support.
* Added `CTLD:AddAllowedFixedWingType(typename)` to add new types
* Renamed enabler flags into `enableFixedWing, FixedMinAngels, FixedMaxAngels, FixedMaxSpeed`
* Base support for Mosquito added
2025-03-30 16:50:43 +02:00
Applevangelist
342e901dd1 Merge remote-tracking branch 'origin/master' into develop 2025-03-28 15:05:57 +01:00
Applevangelist
0a38700edb #AI_Patrol - small enhancement 2025-03-28 15:05:30 +01:00
Applevangelist
659615114a xx 2025-03-28 15:04:42 +01:00
Applevangelist
bfd26522d6 Merge remote-tracking branch 'origin/develop' into develop 2025-03-27 11:09:06 +01:00
Applevangelist
00d14c7c0a #PLAYERRECCE - small tweak for visual distance setting 2025-03-27 11:09:01 +01:00
Frank
47c9e1ba1f Update Legion.lua
- Dead assets are removed from cohort & legion
2025-03-23 22:45:15 +01:00
Frank
92e680a276 Merge branch 'master' into develop 2025-03-23 14:11:37 +01:00
Frank
4955fe4d92 Update Spawn.lua
- Fix for problem that helos are not spawned on ships but at origin of the map
2025-03-23 14:11:21 +01:00
Applevangelist
e439bcdd0f Merge remote-tracking branch 'origin/master' into develop 2025-03-22 14:59:08 +01:00
Applevangelist
792aa73832 #AIBRASE - Afghanistan new Airbases 2025-03-22 14:58:24 +01:00
Applevangelist
d375e0ce29 #AUTOLASE - small fix for interval 2025-03-22 10:42:36 +01:00
Applevangelist
e3670219ed Merge remote-tracking branch 'origin/master' into develop 2025-03-21 09:22:36 +01:00
Applevangelist
a915452e6e #COORDINATE - use magnetic for BRAA 2025-03-21 09:22:08 +01:00
Applevangelist
6662a1cf97 Merge remote-tracking branch 'origin/master' into develop 2025-03-16 16:57:38 +01:00
Applevangelist
be8405b72b #MANTIS - fix for data table 2025-03-16 16:57:11 +01:00
Applevangelist
9b24695377 Merge remote-tracking branch 'origin/master' into develop 2025-03-16 13:59:23 +01:00
Applevangelist
5ca3e3b2b8 #SHORAD tweak 2025-03-16 13:58:58 +01:00
Applevangelist
8bbef34b74 Merge remote-tracking branch 'origin/master' into develop 2025-03-16 13:07:19 +01:00
Applevangelist
618a8744a2 #MANTIS - improve point defense behaviour if not SAM to defend is around 2025-03-16 13:06:53 +01:00
Applevangelist
5329da32eb Merge remote-tracking branch 'origin/develop' into develop 2025-03-16 11:02:59 +01:00
Applevangelist
df86c3f2a1 #AUTOLASE - added a function to allow to set the monitoring report frequency 2025-03-16 11:02:55 +01:00
Frank
784fcb7882 Merge branch 'master' into develop 2025-03-15 22:49:57 +01:00
Frank
23aeef7a20 Airbase
- Airbase changes for helipads that are also airdromes
- Improved SpawnAtAirbase function
2025-03-15 22:49:43 +01:00
Applevangelist
0ee24b86dc Merge remote-tracking branch 'origin/master' into develop 2025-03-15 16:30:01 +01:00
Applevangelist
9ac4f136aa #MANTIS - extend scaling for short/point systems to make them more reactive 2025-03-15 16:29:24 +01:00
Applevangelist
ca9b2d79cc Merge remote-tracking branch 'origin/master' into develop 2025-03-15 10:56:34 +01:00
Applevangelist
c9a09c2fc9 #SOUND/MSRS - small fix to delete the "--ssml" tag if google provider is used in conjunction with a file based sound output. 2025-03-15 10:56:13 +01:00
Applevangelist
813286f7f3 Merge remote-tracking branch 'origin/master' into develop 2025-03-14 10:41:13 +01:00
Applevangelist
6028c91f81 #NET small fix if PlayerSlot remains nil 2025-03-14 10:40:52 +01:00
Frank
11b6bb2638 Merge branch 'master' into develop 2025-03-13 16:41:25 +01:00
Frank
3c57928f46 Update Airbase.lua
- Fixed bug in counting the number of parking spots per terminal type
2025-03-13 16:40:57 +01:00
Applevangelist
e091e659a2 Merge remote-tracking branch 'origin/master' into develop 2025-03-13 10:51:00 +01:00
Applevangelist
32f0bb33c3 #ZONE - Trigger added OnAfterZoneEmpty and OnAfterObjectDead 2025-03-13 10:50:15 +01:00
Applevangelist
dea64751c3 Merge remote-tracking branch 'origin/master' into develop 2025-03-12 09:18:43 +01:00
Applevangelist
31d0410284 #TEMPLATES remove files 2025-03-12 08:56:13 +01:00
Applevangelist
9cdf550432 #PLAYERTASKMANAGER - Allow use of multiple drones for lasing of precision bombing tasks 2025-03-12 08:45:08 +01:00
Applevangelist
09525029ab Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Moose Development/Moose/Modules.lua
#	Moose Setup/Moose.files
2025-03-12 08:44:00 +01:00
Applevangelist
87c436ba34 #STTS - remove from tree 2025-03-12 08:42:38 +01:00
Applevangelist
b1002017e5 Merge remote-tracking branch 'origin/master' into develop 2025-03-11 16:30:07 +01:00
Applevangelist
6e9727e265 xx 2025-03-11 16:29:31 +01:00
Applevangelist
383e953ba1 Merge remote-tracking branch 'origin/master' into develop 2025-03-11 10:52:40 +01:00
Applevangelist
94ee76fe62 #CONTROLLABLE - Improved IR Markers 2025-03-11 10:52:16 +01:00
Frank
9b52b640e6 Merge branch 'master' into develop 2025-03-10 19:53:51 +01:00
Frank
ea23162ca9 Update Spawn.lua
- Enabled explicit parking spots in SpawnAtAirbase
2025-03-10 19:53:24 +01:00
Thomas
68d0220912 Merge pull request #2268 from FlightControl-Master/master
merge from master
2025-03-10 15:35:08 +01:00
Thomas
683388faee Merge pull request #2266 from FlightControl-Master/Applevangelist-IR-1
Update Controllable.lua
2025-03-10 15:32:07 +01:00
Thomas
56ec3920c5 Update Controllable.lua
IR Strobe . some improvements
2025-03-10 13:12:00 +01:00
Thomas
88842d1c67 Merge pull request #2265 from FlightControl-Master/master
Update CTLD.lua
2025-03-10 10:57:19 +01:00
Thomas
f335ffc4ec Update CTLD.lua
Clarify hover min/max height is in meters
2025-03-10 10:55:01 +01:00
Applevangelist
f76aefa976 Merge remote-tracking branch 'origin/master' into develop 2025-03-09 14:37:56 +01:00
Applevangelist
4976cd86f2 ZONE_ELASTIC - function to remove vertices 2025-03-09 14:36:42 +01:00
Frank
2cd66ae1d4 Merge branch 'master' into develop 2025-03-08 21:37:59 +01:00
Frank
c00eff8b23 AIRBASE
- AIRBASE: Workaround for DCS bug that helipads have category of airdrome
- SET_AIRBASE: Added FilterZones function
2025-03-08 21:37:43 +01:00
Applevangelist
7f0376561b Merge remote-tracking branch 'origin/master' into develop 2025-03-06 14:52:15 +01:00
Applevangelist
3c710613a8 CONTROLLABLE added HasIRMarker 2025-03-06 14:51:49 +01:00
Applevangelist
3585208547 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Moose Development/Moose/Core/Zone.lua
2025-03-06 12:27:08 +01:00
Applevangelist
29c0017e80 xx 2025-03-06 12:26:24 +01:00
Applevangelist
45ebf9a3c7 #ZONE_ELASTIC - Fix zone filling 2025-03-06 12:25:57 +01:00
Frank
908e505ce3 Merge branch 'master' into develop 2025-03-05 20:48:28 +01:00
Frank
c808e4a4e2 Update Airbase.lua
- Added FighterAircraftSmall parking spot type
2025-03-05 20:48:15 +01:00
Thomas
e129eb97a7 Merge pull request #2263 from FlightControl-Master/master
ATIS
2025-03-03 14:21:02 +01:00
Thomas
e2612b97d7 Merge pull request #2262 from FlightControl-Master/Applevangelist-Atis-Kola
Update ATIS.lua
2025-03-03 14:19:45 +01:00
Thomas
70e9d91bb5 Update ATIS.lua 2025-03-03 14:19:11 +01:00
Thomas
0b8810f8b3 Update ATIS.lua
ATIS - Fix for Kola map when no sunset or sunrise arise and SRS is not used
2025-03-03 14:16:47 +01:00
Applevangelist
3d39ccbdce Merge remote-tracking branch 'origin/master' into develop 2025-03-02 12:40:30 +01:00
Applevangelist
41b867a4ca #GROUP - ensure GROUP:GetCoordinate() returns a group heading data field 2025-03-02 12:40:00 +01:00
Applevangelist
427baa43d7 Merge remote-tracking branch 'origin/master' into develop 2025-02-23 16:12:14 +01:00
Applevangelist
d4e141f3c5 #EASYGCICAP - use onbefore on foreign classes rather than OnAfter to avoid user overwrites 2025-02-23 16:11:36 +01:00
Applevangelist
1c0a8d9380 #SET_CLIENT - added option to handle CA slots
#CONTROLLABLE - fix typos
2025-02-23 16:10:32 +01:00
Thomas
f05680f23d Merge pull request #2259 from FlightControl-Master/master
Master merge
2025-02-23 08:40:49 +01:00
Thomas
2b0b9d44eb Merge pull request #2258 from leka1986/patch-3
Update Controllable.lua
2025-02-23 08:40:00 +01:00
leka1986
2fc7a3b542 Update Controllable.lua
Fixing this error
Line 26056: attempt to index local '_coord' (a nil value)
in this line,
local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
2025-02-23 02:34:04 +01:00
Applevangelist
820ae2759b Merge remote-tracking branch 'origin/master' into develop 2025-02-22 16:36:52 +01:00
Applevangelist
12b596a47f #MANTIS - if no EWR system is left over, MANTIS will switch on a random SR/TR each cycle to detect incoming enemies. 2025-02-22 16:36:15 +01:00
Applevangelist
a06d099917 Merge remote-tracking branch 'origin/master' into develop 2025-02-21 10:43:45 +01:00
Applevangelist
7552309a28 #AUTOLASE - option to make ground units aware of target to be lased 2025-02-21 10:43:14 +01:00
Applevangelist
b9ad5b5ba7 #TARGET - add threat level determination for ZONE and OPSZONE 2025-02-21 10:42:52 +01:00
Applevangelist
8ef781a9ac #SET_BASE - added GetThreatLevelMax() 2025-02-21 10:39:21 +01:00
Applevangelist
5e24e8658b Merge remote-tracking branch 'origin/master' into develop 2025-02-19 17:33:17 +01:00
Applevangelist
43eeaede65 #SET - Error output for Addif the ObjectName variable is empty
#UNIT - Return zero is SpeedMax is nil
2025-02-19 17:32:37 +01:00
Frank
749c5f87de Merge pull request #2257 from leka1986/patch-2
Update Range.lua
2025-02-18 21:37:37 +01:00
leka1986
a520daeb56 Update Range.lua
attempt to index local 'target' (a nil value)

Was using this build in another mission. Added a nilcheck.
env.info('*** MOOSE GITHUB Commit Hash ID: 2025-02-14T06:13:08+01:00-24b320077721d45774acd56b25086ef6bdfb2e5a ***')
2025-02-18 21:23:58 +01:00
Applevangelist
9bb713db10 Merge remote-tracking branch 'origin/master' into develop 2025-02-18 11:14:00 +01:00
Applevangelist
f9ba96f228 #MANTIS - New 4-Tier-Approach 2025-02-18 11:13:23 +01:00
Applevangelist
a49bd23a2a #ATC_GROUND 2025-02-17 08:35:30 +01:00
Thomas
913b2c6d3f Merge pull request #2256 from FlightControl-Master/master
CTLD
2025-02-17 07:01:08 +01:00
Thomas
cea2f18228 Merge pull request #2255 from leka1986/patch-1
Update CTLD.lua
2025-02-17 07:00:18 +01:00
leka1986
fd2d8a5119 Update CTLD.lua
Now in the listcargo, (cargo onboard) I will display CargoName and a number / number instead of listing each crate, 5 lines for 5 crates. 

Now 1 line for each CargoName and 2/2 or 3/3, etc.

"Rescan" have been moved to only show up if it detects lesser then what it's needed when loading.
2025-02-16 17:16:05 +01:00
Thomas
24b3200777 Merge pull request #2254 from FlightControl-Master/master
Tld
2025-02-14 06:13:08 +01:00
Thomas
fa4e0447dd Merge pull request #2253 from leka1986/patch-1
Update CTLD.lua
2025-02-14 06:11:49 +01:00
leka1986
31aa604fc4 Update CTLD.lua
Added _LoadSingleCrateSet Function
Added _refreshLoadCratesMenu Function
Added Event for Takeoff, Land.

Changes to the RefreshF10menus function
the menus does not gets deleted, and rebuild, but only what is inside them. 
So they will remain at the same position. 

tested multiple times
2025-02-14 00:20:23 +01:00
Applevangelist
3518c89791 Merge remote-tracking branch 'origin/master' into develop 2025-02-10 18:03:22 +01:00
Applevangelist
f44db27565 #ATC_GROUND_UNIVERSAL
* Correct usage of airbase names if given
* Exclude FARPs and Ships
2025-02-10 18:02:42 +01:00
Thomas
acaab0c6a9 Merge pull request #2251 from FlightControl-Master/master
CTLD
2025-02-10 06:02:33 +01:00
Thomas
9b1abab73a Merge pull request #2250 from leka1986/master
Update CTLD.lua
2025-02-10 06:01:30 +01:00
leka1986
cad8f15b61 Update CTLD.lua
Reworked the logic of the menu.
Now it will Show be shown :
1. Ammo truck
2. Humvee
3. Ammo truck 1/2 -- 1/2 due to incomplete set.

And for the troops

Squad 8 (2) -- 2 set of squad 8, Selecting this will only deploy 1 set.
Squad 16

Changed so when loading troops, it will state 
Squad 8 boarded. same for the extraction.

I have tested it and it works
Heart8reaker also tested it and no issues so far.
2025-02-09 23:41:16 +01:00
Applevangelist
aafbd8c297 Merge remote-tracking branch 'origin/master' into develop 2025-02-09 12:24:41 +01:00
Applevangelist
1d08bcf2e0 #CTLD docu 2025-02-09 12:24:16 +01:00
Applevangelist
6d38346eb8 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Moose Development/Moose/Ops/CTLD.lua
2025-02-09 12:05:57 +01:00
Applevangelist
9487a5ae91 #CTLD 2025-02-09 12:05:18 +01:00
Thomas
d2bb6a4fc0 Merge pull request #2248 from leka1986/patch-4
Update CTLD.lua
2025-02-09 12:04:39 +01:00
Applevangelist
c3ccecdd4a #AWACS - Escort vector catch for noobs not reading the description 2025-02-09 12:01:57 +01:00
leka1986
5ad54648ab Update CTLD.lua
Removed the block from partly drop 1. 

It's not helpful for the user to have 1 incomplete a set of crates onboard. 
With this line removed, If someone loads 2 items, and second item is not completed, due to crate limit or due weight, player can unload the uncomplete set.
2025-02-09 11:51:34 +01:00
Applevangelist
96337cc5df #CTLD 2025-02-09 11:48:22 +01:00
Thomas
fa0ce0927b Merge pull request #2247 from leka1986/patch-3
Update CTLD.lua
2025-02-09 11:34:58 +01:00
leka1986
693c2a730f Update CTLD.lua
Added  self:_RefreshDropCratesMenu(Group, Unit) to  function CTLD:_LoadCratesNearby(Group, Unit)
2025-02-09 11:26:38 +01:00
Thomas
0d438a9452 Merge pull request #2246 from FlightControl-Master/master
Ctd changes
2025-02-09 09:23:25 +01:00
Thomas
bc9eee22b7 Merge pull request #2245 from FlightControl-Master/Applevangelist-patch-1
Update CTLD.lua
2025-02-08 14:29:59 +01:00
Thomas
f74d25b31c Update CTLD.lua
Option to unload single cargo items by @lekaa
2025-02-08 14:29:35 +01:00
Applevangelist
66af360ce6 Merge remote-tracking branch 'origin/master' into develop 2025-02-06 16:36:27 +01:00
Applevangelist
ad9893c3ab Merge remote-tracking branch 'origin/develop' into develop 2025-02-06 16:35:03 +01:00
Thomas
1156971d94 Merge pull request #2243 from shaji-Dev/master
[ADDED] `UNIT:IsEWR()`
2025-02-06 12:22:18 +01:00
Shafik
c79b5c37c4 [ADDED] UNIT:IsEWR() 2025-02-06 13:16:03 +02:00
Thomas
b4e6201b68 Merge pull request #2242 from FlightControl-Master/Applevangelist-patch-1
Update Autolase.lua
2025-02-06 08:52:20 +01:00
Thomas
f26d8d6822 Update Autolase.lua
Added option to set offset smoke parameter
2025-02-06 08:52:05 +01:00
Applevangelist
3d0b7b9267 #AUTOLASE - Add offset parameters for autolase:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) 2025-02-06 08:50:33 +01:00
Thomas
a2b650a9e3 Merge pull request #2241 from shaji-Dev/master
[FIXED] attempt to index field  (a nil value)
2025-02-03 12:03:04 +01:00
Shafik
905b442e9b [FIXED] attempt to index field (a nil value) 2025-02-03 09:03:43 +02:00
Thomas
7cd95377f9 Merge pull request #2240 from FlightControl-Master/master
Merge from master
2025-02-02 12:54:42 +01:00
Thomas
18fe3112bd Merge pull request #2238 from shaji-Dev/master
[ADDED] Set AI On/Off for units
2025-02-02 12:53:37 +01:00
Thomas
84e85dd0b5 Merge pull request #2239 from shaji-Dev/develop
Develop
2025-02-02 12:53:25 +01:00
Shafik
2744befbab [FIXED] Group not turning off due to conflicting condition checking true and false (if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then)
[FIXED] Units turning on for same coalition aircrafts in radius
2025-02-02 13:27:03 +02:00
Shafik
e87dc525b9 [FIXED] Group not turning off due to conflicting condition checking true and false (if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then)
[FIXED] Units turning on for same coalition aircrafts in radius
2025-02-02 13:25:53 +02:00
Shafik
cc057744ba [ADDED] UNIT:IsSAM() and UNIT:IsAAA() to use used in their respective GROUP functions. 2025-02-02 12:46:55 +02:00
Shafik
db1779e1db [ADDED] Set AI On/Off for units 2025-02-02 11:42:40 +02:00
Applevangelist
ed31b87b2f Merge remote-tracking branch 'origin/master' into develop 2025-02-01 18:40:44 +01:00
Applevangelist
d1e31a3652 #PLAYERRECCE - add parameter for SRS Backend option 2025-02-01 18:40:26 +01:00
Applevangelist
d25a723fc7 #MESSAGE - add parameter for SRS Backend 2025-02-01 18:39:51 +01:00
Applevangelist
3fb4cae7b8 Merge remote-tracking branch 'origin/master' into develop 2025-01-31 14:27:25 +01:00
Applevangelist
f0fe1b431d #SPAWN - Add optional waiting time to InitRepeatOnLanding 2025-01-31 14:26:32 +01:00
Thomas
8e286edd25 Merge pull request #2237 from shaji-Dev/master
[FIXED] attempt to index a nil value
2025-01-31 10:04:01 +01:00
Shafik
74520b1359 [FIXED] attempt to index a nil value (AWACS:_CheckAwacsStatus -> OPSGROUP:GetCallsignName -> GROUP:IsPlayer) 2025-01-31 10:30:18 +02:00
shaji-Dev
0ffcdf2f66 Merge branch 'FlightControl-Master:develop' into develop 2025-01-31 10:21:37 +02:00
Applevangelist
fe0edeb011 Merge remote-tracking branch 'origin/master' into develop 2025-01-30 18:33:06 +01:00
Applevangelist
d06e44d37b #CTLD small additions 2025-01-30 18:32:39 +01:00
Applevangelist
35348d9b81 #UTILS - Added UTILS.Weather for fog stuff 2025-01-30 18:32:25 +01:00
Applevangelist
d06a618582 Merge remote-tracking branch 'origin/master' into develop 2025-01-30 13:20:35 +01:00
Applevangelist
4b16e94eaf #CTLD - Added function for the game designer to get an overview of stock and alive groups, if they want to manage the stock: CTLD:_CountStockPlusInHeloPlusAliveGroups() 2025-01-30 13:19:11 +01:00
Applevangelist
d5253f0420 Merge remote-tracking branch 'origin/master' into develop 2025-01-30 09:04:46 +01:00
Thomas
d983676330 Merge pull request #2236 from Fedge7/fedge/enums-weapons
Fixes AH-64D FCR enum value, adds APKWS.
2025-01-30 06:56:44 +01:00
Fedge
03c30f3cce Adds the 2 primary APKWS rockets as entries in the ENUMS.Storage.weapons.missiles table.
I added them to `missiles`, rather than `nurs` because that's where they appear in the mission editor's storage window.
2025-01-29 18:48:34 -07:00
Fedge
99b93266ad Fixes the AH-64D's FCR enum weapon definition. 2025-01-29 18:47:44 -07:00
Thomas
4530b74367 Merge pull request #2235 from FlightControl-Master/master
merge from master
2025-01-29 15:31:29 +01:00
Thomas
e307b57e67 Merge pull request #2234 from shaji-Dev/master
nil fixes
2025-01-29 13:58:42 +01:00
Shafik
5ef9bb2acd [FIXED] attempt to get length of local 'parking' (a nil value) 2025-01-29 14:44:31 +02:00
Shafik
4aacdc1567 [FIXED] attempt to index local 'CleanUpUnit' (a nil value) 2025-01-29 14:43:43 +02:00
Thomas
57552f4300 Merge pull request #2233 from FlightControl-Master/Applevangelist-patch-3
Update CTLD.lua
2025-01-29 13:24:45 +01:00
Thomas
09d53f7d8c Update CTLD.lua
#CTLD - Added HelicopterLost Event
2025-01-29 13:24:04 +01:00
Shafik
161bdc0413 [FIXED] attempt to concatenate field 'IniGroupName' (a nil value) ?? 2025-01-29 10:13:34 +02:00
Shafik
44ad362dbc [FIXED] attempt to index local 'CleanUpUnit' (a nil value) 2025-01-29 10:06:19 +02:00
Shafik
ea776aeacc [FIXED] attempt to get length of local 'parking' (a nil value) 2025-01-29 10:05:55 +02:00
Frank
e30479329a Merge branch 'master' into develop 2025-01-28 20:29:45 +01:00
Frank
d9948d1a19 Update Group.lua 2025-01-28 20:29:35 +01:00
Frank
b962096661 Update RAT.lua
- Tried to fix bug the Pnow is nil
2025-01-28 20:29:04 +01:00
Frank
725b55a505 Merge branch 'master' into develop 2025-01-28 20:22:57 +01:00
Frank
5f7a4f2bbb Update Group.lua
- Added way to get the coordinate of the group
2025-01-28 20:22:45 +01:00
Thomas
f555399e2f Merge pull request #2231 from FlightControl-Master/master
Master merge
2025-01-28 08:32:34 +01:00
Thomas
1b6945e0b0 Merge pull request #2230 from FlightControl-Master/Applevangelist-patch-2
Update Scenery.lua
2025-01-28 08:31:33 +01:00
Thomas
4fd55b1bd6 Update Scenery.lua
#SCENERY
2025-01-28 08:31:17 +01:00
Applevangelist
fb276cf812 Merge remote-tracking branch 'origin/master' into develop 2025-01-27 18:49:01 +01:00
Applevangelist
41dfaab82a xx 2025-01-27 18:48:39 +01:00
Thomas
20b8deb6de Merge pull request #2228 from FlightControl-Master/Applevangelist-patch-2
Update CTLD.lua
2025-01-27 18:46:07 +01:00
Thomas
6af836c118 Update CTLD.lua 2025-01-27 18:45:39 +01:00
Thomas
f87b8a2c2a Merge pull request #2229 from FlightControl-Master/Applevangelist-patch-3
Update Base.lua
2025-01-27 18:44:50 +01:00
Thomas
daffd7412a Update Base.lua 2025-01-27 18:44:35 +01:00
Thomas
bfb60b318e Update CTLD.lua
update
2025-01-27 13:27:13 +01:00
Thomas
a55959dfbb Update Base.lua
Added property functions
2025-01-27 12:00:25 +01:00
Thomas
4d24eb82be Update CTLD.lua
#CTLD - Add the group name of the extracted group to TroopsExtracted FSM event
2025-01-27 08:51:17 +01:00
Applevangelist
4656d3e019 Merge remote-tracking branch 'origin/develop' into develop 2025-01-26 17:35:11 +01:00
Applevangelist
86b76ba216 Merge remote-tracking branch 'origin/master' into develop 2025-01-26 17:35:06 +01:00
Applevangelist
ff561410ad xx 2025-01-26 17:34:36 +01:00
Applevangelist
e26caa2f74 smaller fixes 2025-01-26 17:34:22 +01:00
Applevangelist
b32d17c2ab Merge remote-tracking branch 'origin/develop' into develop 2025-01-26 14:55:24 +01:00
Applevangelist
29111e1018 xx 2025-01-26 14:55:21 +01:00
Applevangelist
3433ebd665 Merge remote-tracking branch 'origin/master' into develop 2025-01-26 14:54:53 +01:00
Applevangelist
b75fff60c8 xx 2025-01-26 14:54:19 +01:00
Applevangelist
a47fa3f9fc Merge remote-tracking branch 'origin/master' into develop 2025-01-26 13:54:59 +01:00
Applevangelist
4ac57fce7a #CTLD - Added CTLD:GetGenericCargoObjectFromGroupName(GroupName) to get the generic CTLD_CARGO entry from a group name. 2025-01-26 13:54:28 +01:00
Applevangelist
6218b94c99 Merge remote-tracking branch 'origin/develop' into develop 2025-01-26 13:29:57 +01:00
Applevangelist
74a6c89801 Merge remote-tracking branch 'origin/master' into develop 2025-01-26 13:29:53 +01:00
Applevangelist
25a9a0120a #CTLD
- If stock is set, show stock number in menu entries
- Corrected table build for GetStockCrates/Troops/Statics
2025-01-26 13:29:18 +01:00
Thomas
deb0747e66 Merge pull request #2227 from shaji-Dev/develop
[FIXED] `bad argument #2 to 'format' (number expected, got nil)`
2025-01-26 08:38:11 +01:00
Shafik
00f5fde5a8 [FIXED] bad argument #2 to 'format' (number expected, got nil) 2025-01-26 00:19:19 +02:00
Applevangelist
03cd354f9e Merge remote-tracking branch 'origin/master' into develop 2025-01-25 15:55:01 +01:00
Applevangelist
66a1fa8af5 #CTLD - small fix for LoadesGroupsTable inserts 2025-01-25 15:54:29 +01:00
Applevangelist
c2096c8dfd Merge remote-tracking branch 'origin/master' into develop 2025-01-25 15:06:40 +01:00
Applevangelist
1b4033cfce CTLD Small fix for PlayerTask ExtractTroops 2025-01-25 15:04:33 +01:00
Applevangelist
750edf1144 Merge remote-tracking branch 'origin/master' into develop 2025-01-24 12:22:24 +01:00
Applevangelist
48bc41873a #STORAGE - Added small helper to get Syria "H" Helipad warehouses via a zone 2025-01-24 12:21:44 +01:00
Applevangelist
2e54f51229 Merge remote-tracking branch 'origin/develop' into develop 2025-01-24 10:30:29 +01:00
Applevangelist
e39e414e0d Merge remote-tracking branch 'origin/master' into develop 2025-01-24 10:30:25 +01:00
Applevangelist
068a1ab99c #CTLD
- Added `OnAfterLoaded` which gives you access to the rebuild troops and vehicles in a table  of loaded groups, each entry is a table with three values: Group, TimeStamp and CargoType
2025-01-24 10:29:52 +01:00
Thomas
e35d9eb07f Merge pull request #2226 from FlightControl-Master/master
merge from master
2025-01-22 09:45:25 +01:00
Thomas
6551383070 Merge pull request #2225 from FlightControl-Master/Applevangelist-ZoneScaen-1
Update Zone.lua
2025-01-22 09:44:27 +01:00
Thomas
b03978cc3d Update Zone.lua
Correct radius sphere search in  ZONE_RADIUS:SearchZone()
2025-01-22 09:43:57 +01:00
Frank
3a7233b594 Merge branch 'master' into develop 2025-01-19 19:18:26 +01:00
Frank
82f4c5790a Merge branch 'master' of https://github.com/FlightControl-Master/MOOSE 2025-01-19 19:18:01 +01:00
Frank
5957124e9e ARTY v1.3.3
- Added missing/new arty units to DB (min/max firing range)
- Adjusted immobile speed check because some DCS units report a speed of 1 m/s
2025-01-19 19:17:59 +01:00
Applevangelist
7c91b9847b Merge remote-tracking branch 'origin/master' into develop 2025-01-19 18:23:19 +01:00
Applevangelist
92a05ca74a #CTLD - If troops or vehicles have a stock set, you can only inject as many as there are in stock. Specifically, when using persistence, the load function will restrict to inject more objects than are in stock, each inject draws on the stock. 2025-01-19 18:22:25 +01:00
Applevangelist
a3c13c8cea #PLAYERTASK - added option for custom player callsigns
#PLAYERRECCE - added option for custom player callsigns
#PLAYERRECCE - added KIOWA support
2025-01-19 17:41:17 +01:00
Frank
d02b5db6dd Merge branch 'master' of https://github.com/FlightControl-Master/MOOSE 2025-01-18 22:34:25 +01:00
Frank
bd074728fe Update Artillery.lua
- Update Arty DB
2025-01-18 22:34:21 +01:00
Applevangelist
8d87531464 #PLAYERRECCE - Add standard FOW for Kiowa 2025-01-18 15:32:08 +01:00
Applevangelist
5fbd3d9525 Merge remote-tracking branch 'origin/master' into develop 2025-01-18 15:30:33 +01:00
Applevangelist
61b7b3ead6 Smaller fixes 2025-01-18 15:30:03 +01:00
Applevangelist
8f2178a79c Merge remote-tracking branch 'origin/master' into develop 2025-01-17 09:56:00 +01:00
Applevangelist
72bb23ed0d xx 2025-01-17 09:55:12 +01:00
Applevangelist
7f7999e3e5 #CSAR - Make the list of downed pilots' coordinate dependent on _SETTINGS global or pilot 2025-01-17 09:54:41 +01:00
Applevangelist
0a08e3fdac Merge remote-tracking branch 'origin/master' into develop 2025-01-17 09:22:32 +01:00
Applevangelist
b522b38d31 #SCENERY - Some fixes for kissing getLife function on some objects, and life points being zero on some objects 2025-01-17 09:21:48 +01:00
Thomas
646a2aec66 Merge pull request #2221 from FlightControl-Master/Applevangelist-awacs-1
Update Awacs.lua
2025-01-15 15:34:36 +01:00
Thomas
7392cb9bf3 Update Awacs.lua
#AWACS - clarify use of escort parameters and template
2025-01-15 12:14:14 +01:00
Frank
2e77988473 Merge branch 'master' into develop 2025-01-14 10:36:27 +01:00
Frank
b2dc7bc232 ARTY v1.3.2
- Fixed bug that MLRS does not fire (ammo count if weapon type is auto)
2025-01-14 10:36:12 +01:00
Applevangelist
1b25220729 #AWACS - make # of escorts required assets 2025-01-13 12:38:53 +01:00
Applevangelist
4953000565 Merge remote-tracking branch 'origin/develop' into develop 2025-01-13 11:46:56 +01:00
Applevangelist
01b7575bca #AWACS
- Fix a logic error: cancel the Escort mission when the replacement has arrived
- Workaround for a situation where a player has a checkin-entry in the menu and a managed group entry at the same time - which should not be the case.
- Added Escort options for formation and relative position behind the AWACS
2025-01-13 11:46:51 +01:00
Frank
f6e6dcac9a OPSGROUP ARTY
- Fixed bug in surface type for coord in range
- Improved search for coord in range
- Added option to set mission waypoint
2025-01-12 23:54:44 +01:00
Applevangelist
93c307d9dd Merge remote-tracking branch 'origin/develop' into develop 2025-01-12 17:10:32 +01:00
Applevangelist
852c18cef8 Merge remote-tracking branch 'origin/master' into develop 2025-01-12 17:10:27 +01:00
Applevangelist
def622a02c #CTLD - InjectTroops - if precision coords is true, do also not randomize unit positions. 2025-01-12 17:09:31 +01:00
Frank
acfe1a856e Update OpsGroup.lua
- Added surface type check for ARTY mission
- Improved search for coordinate in range
- Added option to specify distance conversion for weapon ranges
2025-01-11 23:38:15 +01:00
Thomas
9f1f6af647 Merge pull request #2220 from FlightControl-Master/master
Merge master
2025-01-11 10:17:39 +01:00
Thomas
30bedb39f1 Merge pull request #2219 from FlightControl-Master/Applevangelist-patch-1
Update Unit.lua
2025-01-11 10:14:44 +01:00
Thomas
fbcc4ee32b Update Unit.lua
GetSTN fix if template os nil
2025-01-11 10:14:03 +01:00
Thomas
27571cc22f Merge pull request #2217 from shaji-Dev/develop
Develop
2025-01-11 10:04:44 +01:00
Shafik
1182a5eb1e [FIXED] attempt to index field 'IniDCSGroup' (a nil value) 2025-01-10 21:40:03 +02:00
Applevangelist
5f57c71c62 Merge remote-tracking branch 'origin/develop' into develop 2025-01-08 13:05:28 +01:00
Applevangelist
c0d60b1bcf Merge remote-tracking branch 'origin/master' into develop 2025-01-08 13:05:24 +01:00
Applevangelist
63c68d729b #SET_GROUP - fix for lost event if IniDCSGroup isn't filled 2025-01-08 13:04:30 +01:00
Thomas
5fe29e2ba1 Merge pull request #2214 from FlightControl-Master/Applevangelist-patch-3
Update Auftrag.lua
2025-01-07 09:22:12 +01:00
Thomas
16ff1ac3e7 Update Auftrag.lua
#AUFTRAG Added some options for weapontype setting
2025-01-07 09:21:26 +01:00
Applevangelist
0e4f805e1c Merge remote-tracking branch 'origin/develop' into develop 2025-01-06 17:33:09 +01:00
Applevangelist
5f29bdc7a7 Merge remote-tracking branch 'origin/master' into develop 2025-01-06 17:33:04 +01:00
Applevangelist
04f8f6d512 xx 2025-01-06 17:32:29 +01:00
Thomas
f4768aff07 Merge pull request #2213 from FlightControl-Master/master
Master merge
2025-01-06 14:21:42 +01:00
Thomas
b74d46f762 Merge pull request #2211 from FlightControl-Master/Applevangelist-spawn-1
Update Spawn.lua
2025-01-06 14:20:23 +01:00
Thomas
02decc3901 Merge pull request #2212 from Rolln-dev/rolln/range
#RANGE - Changed parameter naming to fix docs
2025-01-06 14:19:02 +01:00
Rolln
dcdea16624 #RANGE - Changed parameter naming to fix docs 2025-01-06 06:15:35 -07:00
Thomas
2635cf6345 Update Spawn.lua
#SPAWN Added option to use the center of the zone rather than a random postion when using InitRandomizeZone
2025-01-06 14:08:05 +01:00
Applevangelist
578c65196c Merge remote-tracking branch 'origin/master' into develop 2025-01-05 17:45:08 +01:00
Applevangelist
64fb24ce96 Merge remote-tracking branch 'origin/develop' into develop 2025-01-05 17:44:37 +01:00
Applevangelist
45dd7117c7 #CLIENTMENUMANAGER Add a retry loop if possibly client not yet exists in Database 2025-01-05 17:44:32 +01:00
Applevangelist
e86069d39c Smaller updates 2025-01-05 17:43:12 +01:00
Thomas
e9db714937 Merge pull request #2210 from FlightControl-Master/master
Master merhe
2025-01-05 10:21:40 +01:00
Thomas
fdcf153b0b Merge pull request #2209 from shaji-Dev/master
[FIXED] `attempt to call method 'GetPlayerName' (a nil value)`
2025-01-05 10:20:23 +01:00
Shafik
8523b7e20a [FIXED] attempt to call method 'GetPlayerName' (a nil value) 2025-01-05 10:47:14 +02:00
Frank
29b992bf81 Merge pull request #2208 from Rolln-dev/rolln/range
ENHANCEMENT: Adds a ceiling to the range.
2025-01-04 19:15:33 +01:00
Rolln
4c52509d6d ENHANCEMENT: Adds a ceiling to the range. 2025-01-04 11:06:39 -07:00
Applevangelist
8d603a0cef Merge remote-tracking branch 'origin/master' into develop 2025-01-04 18:26:32 +01:00
Applevangelist
9bc067f2e8 #STORAGE - Switched saving items from the storage inventory to using the enumerator (+~120 items), better handling of table IDs. 2025-01-04 18:25:32 +01:00
Applevangelist
5fbe0d9a70 #ENUMS - Added items to the storage enumerator, some number corrections 2025-01-04 18:23:24 +01:00
Shafik
4c1c36ef46 [FIXED] attempt to call method 'GetPlayerName' (a nil value) 2025-01-04 16:16:03 +02:00
Applevangelist
067fbcaeaf Merge remote-tracking branch 'origin/develop' into develop 2025-01-04 13:25:38 +01:00
Applevangelist
f0aad83d53 Merge remote-tracking branch 'origin/master' into develop 2025-01-04 13:25:33 +01:00
Applevangelist
474f767e56 #CTLD - Clarify doc for Hook loadable stuff 2025-01-04 13:24:51 +01:00
Frank
be062061d5 Update Auftrag.lua
- Improved docs for ARTY
2025-01-02 23:01:49 +01:00
Applevangelist
ac5dfab82f #PLAYERRECCE - Fine tune check when a unit is dead or just per se has less than 2 LifePoints 2025-01-02 17:22:39 +01:00
Applevangelist
13d5f4ac99 Merge remote-tracking branch 'origin/master' into develop 2025-01-02 17:07:29 +01:00
Applevangelist
f9030be843 xx 2025-01-02 17:06:53 +01:00
Applevangelist
3cea1cc78c #PLAYERRECCE - allow to display add'l location in report as per user settings for laser and visual targets. 2025-01-02 16:20:47 +01:00
Applevangelist
37c1423c9f Merge remote-tracking branch 'origin/master' into develop 2025-01-02 13:20:55 +01:00
Applevangelist
538e35d8f0 xx 2025-01-02 13:20:18 +01:00
Applevangelist
d23f029953 Merge remote-tracking branch 'origin/master' into develop 2025-01-02 11:19:39 +01:00
Applevangelist
aafa37f80d Merge remote-tracking branch 'origin/develop' into develop 2025-01-02 11:19:16 +01:00
Applevangelist
203f0c8abc #docu 2025-01-02 11:18:44 +01:00
Thomas
1c0e6362a1 Merge pull request #2205 from shaji-Dev/Heartbreaker
[FIXED] OpsZone never goes to `Attacked` state if `threatlevelCapture` is higher than 0.
2025-01-02 07:31:11 +01:00
Shafik
68165cee75 [FIXED] OpsZone never goes to Attacked state if threatlevelCapture is higher than 0. 2025-01-01 23:08:30 +02:00
Applevangelist
8ef5f0b3f6 Merge remote-tracking branch 'origin/master' into develop 2025-01-01 17:56:35 +01:00
Applevangelist
d0736b0b56 #SHORAD - Switch shorad group back to green&no emissions&scoot if it's directly attacked by a missile 2025-01-01 17:55:55 +01:00
Applevangelist
2fbcd9d2b9 Merge remote-tracking branch 'origin/master' into develop 2025-01-01 14:38:43 +01:00
Applevangelist
15b1ed028e #MANTIS - better handling of stats, verbose dist calc in km, coord in MGRS0 now 2025-01-01 14:38:07 +01:00
Applevangelist
a611a38181 Merge remote-tracking branch 'origin/master' into develop 2025-01-01 09:14:46 +01:00
Applevangelist
008617a35c #MANTIS - Fix for Checkloop if no Sharad instance is alive 2025-01-01 09:14:07 +01:00
Thomas
81a403f02d Merge pull request #2204 from FlightControl-Master/master
Update Mantis.lua
2025-01-01 08:32:57 +01:00
Thomas
6144a61a2e Update Mantis.lua
Fix for zones count on possible empty variables
2025-01-01 08:31:06 +01:00
Applevangelist
df102fba6c Merge remote-tracking branch 'origin/master' into develop 2024-12-31 15:35:23 +01:00
Applevangelist
5d192abd25 #SEAD - Add AGM_65 2024-12-31 15:34:46 +01:00
Applevangelist
902f6dae11 Merge remote-tracking branch 'origin/master' into develop 2024-12-31 14:05:41 +01:00
Applevangelist
62337f445a #CTLD - Save and load special static shapes, if they are set 2024-12-31 14:04:46 +01:00
Applevangelist
b38c1c5827 xx 2024-12-30 14:42:03 +01:00
Thomas
8309768735 Merge pull request #2203 from shaji-Dev/Heartbreaker
Mankind saved.
2024-12-30 13:40:54 +01:00
Shafik
42e6b57296 Mankind saved. 2024-12-30 14:09:25 +02:00
Shafik
2835ec812f Mankind saved. 2024-12-30 14:08:04 +02:00
Applevangelist
990161729b Merge remote-tracking branch 'origin/master' into develop 2024-12-30 11:47:11 +01:00
Applevangelist
c08cee2317 #STORAGE Documentation for persistence 2024-12-30 11:45:31 +01:00
Applevangelist
e1ea4322a1 Merge remote-tracking branch 'origin/master' into develop 2024-12-29 18:08:45 +01:00
Applevangelist
0f7759d070 #STORAGE - Added StartAutoSave and StopAutoSave() 2024-12-29 18:04:42 +01:00
Applevangelist
6523c4473f Merge remote-tracking branch 'origin/master' into develop 2024-12-29 12:53:35 +01:00
Applevangelist
4a0842bea6 #STORAGE - Added persistence options 2024-12-29 12:52:51 +01:00
Applevangelist
3745e6a8d8 Merge remote-tracking branch 'origin/master' into develop 2024-12-28 14:52:54 +01:00
Applevangelist
3b3666c5f7 #AIRBASE add 2 Kola bases, correct #ENUM for OH-58D weps in STORAGE 2024-12-28 14:52:09 +01:00
Thomas
9e1a28f7ca Merge pull request #2202 from Fedge7/personal/fedge/enums-weapons-oh58-dec2024
Corrects the table values for OH58 weapon enums for patch `2.9.11.4686.`
2024-12-28 09:26:46 +01:00
Fedge
82c7e921eb Corrects the table values for OH58 weapon enums for patch 2.9.11.4686. 2024-12-27 18:48:30 -07:00
Applevangelist
67ccf532ea Merge remote-tracking branch 'origin/master' into develop 2024-12-27 14:41:15 +01:00
Applevangelist
45912911ee #MSRS Google 2025 voice catalog 2024-12-27 14:40:32 +01:00
Applevangelist
c7fe42d919 Merge remote-tracking branch 'origin/master' into develop 2024-12-27 12:26:31 +01:00
Applevangelist
9916afaa49 #COORDINATE Adding names to smoke to be able to switch them off earlier 2024-12-27 12:25:25 +01:00
Applevangelist
55c5a23616 #CTLD Bugfix in selecting correct helo distance when hover/ground for the Chinook 2024-12-27 12:25:00 +01:00
Applevangelist
7b6df08268 #AUTOLASE - Somewhat nicer report display 2024-12-27 12:24:26 +01:00
Applevangelist
9300050573 Merge remote-tracking branch 'origin/master' into develop 2024-12-26 17:18:28 +01:00
Applevangelist
6bee1cc88e Small additions 2024-12-26 17:16:43 +01:00
Applevangelist
c94767a715 Merge remote-tracking branch 'origin/develop' into develop 2024-12-22 13:00:49 +01:00
Applevangelist
7aa3169523 #AWACS - add a mission parameter to ensure selection of AWACS aircraft 2024-12-22 13:00:46 +01:00
Frank
cf86eacb16 Merge branch 'master' into develop 2024-12-18 22:04:55 +01:00
Frank
43ab4d5f38 Update Warehouse.lua
- Removed trace output of qitem and queue because it stalls the game when written to log file
2024-12-18 22:04:38 +01:00
Applevangelist
aa064a1d0e Merge remote-tracking branch 'origin/master' into develop 2024-12-18 12:35:32 +01:00
Applevangelist
21240a60de #AUFTRAG, OpsGroup - added speed option for mission hold, ingress and egress wpts 2024-12-18 12:35:13 +01:00
Applevangelist
0427c0d3a7 #CTLD - Added GetLoadedCargo(Unit) 2024-12-18 12:34:30 +01:00
Applevangelist
3f632b92e2 Merge remote-tracking branch 'origin/master' into develop 2024-12-18 11:35:39 +01:00
Applevangelist
014750ea7f #SET - Documentation fixes 2024-12-18 11:35:13 +01:00
Applevangelist
548b80969a Merge remote-tracking branch 'origin/master' into develop 2024-12-17 12:46:44 +01:00
Applevangelist
e4bbfce314 #MANTIS
- Better status overview
- Some fixes for SHORAD setup
2024-12-17 12:45:45 +01:00
Applevangelist
278f1db9a9 Merge remote-tracking branch 'origin/master' into develop 2024-12-15 13:35:03 +01:00
Applevangelist
359429b17e #GROUP added Teleport(Coordinate) function leveraging Respawn(). Now also translate routes if there are any. 2024-12-15 13:34:13 +01:00
Applevangelist
f151271cb1 Merge remote-tracking branch 'origin/master' into develop 2024-12-15 11:42:04 +01:00
Applevangelist
6001f6abda #EVENT - another fix for scenery without getName function 2024-12-15 11:41:24 +01:00
Applevangelist
18e5012546 Merge remote-tracking branch 'origin/master' into develop 2024-12-15 11:33:35 +01:00
Applevangelist
fd9b5d8d16 #AIRBASE type fix for single Helipad 2024-12-15 11:33:01 +01:00
Frank
da3ee13582 Merge branch 'master' into develop 2024-12-15 10:01:13 +01:00
Frank
b6f184388a ATIS
- Fixed length of niner sound file
2024-12-15 10:00:59 +01:00
Frank
903c065b74 Merge branch 'master' into develop 2024-12-14 16:55:36 +01:00
Frank
d8471698ab Merge pull request #2197 from FlightControl-Master/FF/MasterDevel
SPAWNSTATIC
2024-12-14 16:54:50 +01:00
Frank
6204cecbbd SPAWNSTATIC
- moved AddStatic to after static spawn fixing registration of spawned static
2024-12-14 16:54:39 +01:00
Frank
ddeca49916 Merge pull request #2196 from FlightControl-Master/FF/MasterDevel
DATABASE (fix for SPAWNSTATIC)
2024-12-14 13:48:40 +01:00
Frank
d8dcf37886 DATABASE
- Fixed error that static was not returned in `DATABASE:AddStatic()` (leads to issues if a static object with the same name is spawned)
- Other minor changes
2024-12-14 13:48:04 +01:00
Frank
5ae41a208b Merge branch 'master' into FF/MasterDevel 2024-12-14 11:29:57 +01:00
Applevangelist
0462d900e6 xx 2024-12-13 12:08:54 +01:00
Applevangelist
4fb2ad88bc #OpsGroup - fix holding condition for FG at holding coordinate 2024-12-13 12:07:31 +01:00
Applevangelist
ea55e90e62 Merge remote-tracking branch 'origin/develop' into develop 2024-12-12 13:11:58 +01:00
Applevangelist
6025c05f33 #AUFTRAG #OPSGROUP Handling of Ingress and Holding points for FlightGroups 2024-12-12 13:11:54 +01:00
Frank
64f854e646 Merge branch 'master' into develop 2024-12-12 00:22:59 +01:00
Frank
0f962461e1 DCS Iraq update
- `AIRBASE`: Added Iraq airports to enums
- `ATIS`: Added missing maps to enums
- `UTILS`: Added missing maps to enums
2024-12-12 00:22:44 +01:00
Frank
62dfb5b5ff OPS update
- Set destination base to airwing base for flight groups
- Restored setting of home, destination and current base in OPSGROUP
- Fixed bug if no airbase associated with airwing in check rescuehelo
- Added check that cohort names are unique
- Removed InitWaypoints function from FLIGHTGROUP (obsolete)
2024-12-11 22:49:40 +01:00
Applevangelist
171af5a3c3 #AUFTRAG, #OPSGROUP - Better functionality for Ingress Points (used as IP Holding Point when combined with a push condition) 2024-12-11 16:45:05 +01:00
Applevangelist
874548d1c1 Merge remote-tracking branch 'origin/master' into develop 2024-12-11 14:18:51 +01:00
Applevangelist
aadc03c38d #CTLD Added option/value for my_ctld.TroopUnloadDistHoverHook 2024-12-11 14:17:58 +01:00
Applevangelist
fb0aeafaa2 Merge remote-tracking branch 'origin/master' into develop 2024-12-11 13:58:23 +01:00
Applevangelist
683fa13bb2 #WEAPON - fix for name not available on target 2024-12-11 13:57:55 +01:00
Applevangelist
d3bd55a290 Merge remote-tracking branch 'origin/master' into develop 2024-12-11 13:54:33 +01:00
Applevangelist
e4408a964d #MANTIS - update CHM assets 2024-12-11 13:54:01 +01:00
Frank
1fbe78b667 Merge pull request #2195 from FlightControl-Master/FF/Ops
OPSGROUP v1.0.3
2024-12-10 22:50:30 +01:00
Frank
4902e0f597 OPSGROUP v1.0.3
- fixes for (naval) teleporting
2024-12-10 22:44:44 +01:00
Thomas
34e248b1c3 Merge pull request #2194 from shaji-Dev/Heartbreaker
[Fixed] FlightGroup do not RTB because of incorrect `destbase` assignment.
2024-12-10 19:08:42 +01:00
shaji
5a05917bc2 [Fixed] FlightGroup do not RTB because of incorrect destbase assignment. 2024-12-10 19:04:09 +01:00
Frank
98a613261c Update OpsGroup.lua
- Delayed spawn
2024-12-09 23:32:24 +01:00
Frank
ee880a893e OPSGROUP
- Improved respawn behaviour
2024-12-08 22:47:16 +01:00
Applevangelist
7e8555d6b7 #EASYGCICAP - allow multiple EWR string prefixes in a table 2024-12-08 18:56:28 +01:00
Applevangelist
0a6bdd6e04 #SET_OPSGROUP - fix for teleport/respawn 2024-12-06 14:07:09 +01:00
Frank
f97f33ab59 ATIS v1.1.0
- Added dynamic fog
2024-12-06 00:09:15 +01:00
Frank
f59102ee09 Merge branch 'master' into FF/MasterDevel 2024-12-05 21:06:06 +01:00
Applevangelist
de304d6bb9 Merge remote-tracking branch 'origin/develop' into develop 2024-12-05 10:56:31 +01:00
Applevangelist
73181e3f45 xx 2024-12-05 10:56:27 +01:00
Frank
e42e4e1ddf Merge branch 'master' into FF/MasterDevel 2024-12-03 21:15:46 +01:00
Thomas
86e899f39b Merge pull request #2192 from FlightControl-Master/master
Update Mantis.lua
2024-12-03 18:10:06 +01:00
Thomas
a30079c45b Update Mantis.lua
Less noise
2024-12-03 18:07:38 +01:00
Frank
f644d49e71 OPS Speed
- `AUFTRAG` Fixed speed unit conversion in multiple AUFTRAG types (DCS task parameter speed should be in m/s)
- `OPSGROUP`: Fixed unit conversion for engage target speed
2024-12-01 14:56:02 +01:00
Applevangelist
c0b09b03d9 Merge remote-tracking branch 'origin/master' into develop 2024-11-30 16:20:14 +01:00
Applevangelist
50ffd9aba6 #MANTIS - Correct detail data check is run even if automode isn't defined 2024-11-30 16:19:51 +01:00
Applevangelist
4d4138c1e6 Merge remote-tracking branch 'origin/master' into develop 2024-11-28 09:47:43 +01:00
Frank
936fec1f49 Update Range.lua 2024-11-26 23:06:31 +01:00
Applevangelist
9625d87dd5 #NET - fix for net.force_player_slot(PlayerID, SideID, SlotID ) not working at all - destroy blocked client instead 2024-11-26 10:43:20 +01:00
Frank
c4d0319bb2 Merge branch 'master' into develop 2024-11-24 21:54:19 +01:00
Frank
802139205c Update Unit.lua
- Fixed bug in `UNIT:GetDCSObject()` when DCS unit does not exist.
2024-11-24 21:54:05 +01:00
Frank
6022a3905f TARGET v0.7.1
- Fixed bug if TARGET is a COORDINATE or ZONE, where we cannot check if it is alive or dead. The count alive previously did not count coordinates or zones. Now it does, which is more consistent as we set life=1 and alive=true for these.
2024-11-23 22:10:26 +01:00
Frank
6f724c62fb Merge branch 'master' into develop 2024-11-21 23:44:13 +01:00
Frank
fb918cb2a4 Update DCS.lua 2024-11-21 23:43:58 +01:00
Frank
1a74f112ef Merge branch 'master' into develop 2024-11-21 23:13:18 +01:00
Frank
abb4de46d7 Controllable Detection Improvements/Fixes
- Fixed bug in detection functions for optical detection (enum incorrect)
- Fixed bug in order of return values of `CONTROLLABLE:IsTargetDetected` (both hoggit and DCS are WRONG and MOOSE adopted it).
2024-11-21 23:13:05 +01:00
Frank
5747c49abf Merge branch 'develop' of https://github.com/FlightControl-Master/MOOSE into develop 2024-11-20 11:15:09 +01:00
Frank
b87930738a TARGET v0.7
- Bug fixes
- Improved FSM function docs
2024-11-20 11:15:05 +01:00
Thomas
d2c78516f5 Merge pull request #2191 from shaji-Dev/Heartbreaker
[Added] OPSZONE:SetDrawZoneForCoalition
2024-11-19 06:44:13 +01:00
shaji
93cff96794 [Added] OPSZONE:SetDrawZoneForCoalition to set if zone is drawn on the F10 map for the owner coalition only instead for all. Useful for recon tasking and fog of war. 2024-11-19 00:42:37 +01:00
Thomas
75e80a0091 Merge pull request #2190 from shaji-Dev/Heartbreaker
Added usage docs
2024-11-18 18:36:47 +01:00
shaji
46d2c9c196 [Added] Usage to doc for PLAYERTASK:AddStaticObjectSuccessCondition, PLAYERTASK:AddOpsZoneCaptureSuccessCondition, PLAYERTASK:AddReconSuccessCondition, PLAYERTASK:AddTimeLimitFailureCondition 2024-11-18 09:25:00 +01:00
Applevangelist
6f61d160af Documentation 2024-11-18 08:54:21 +01:00
Thomas
13419171a9 Merge pull request #2189 from shaji-Dev/Heartbreaker
New New Capture OpsZone PlayerTask and Time Limit Conditions
2024-11-18 06:17:00 +01:00
shaji
147cfea587 [Added] New Capture OpsZone PlayerTask with OPSZONE and SET_OPSZONE targets
[Added] Task "Time Limit" failure condition `PLAYERTASK:AddTimeLimitFailureCondition(TimeLimit)`
[Added] `PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition)`
[Added] `PLAYERTASK:_CheckCaptureOpsZoneSuccess`
2024-11-18 02:35:05 +01:00
Applevangelist
c27da8e54e Merge remote-tracking branch 'origin/develop' into develop 2024-11-17 16:23:10 +01:00
Applevangelist
f3c8b4c1cd Merge remote-tracking branch 'origin/master' into develop 2024-11-17 16:23:06 +01:00
Frank
09e1883488 Merge branch 'master' into develop 2024-11-15 23:22:00 +01:00
Frank
dce9631399 Update Event.lua
- Set `Event.IniGroupName` from db in case group does not exist any more
2024-11-15 23:21:50 +01:00
Thomas
fb2ba1dea0 Merge pull request #2188 from shaji-Dev/Heartbreaker
Docs update PLAYERTASK:AddReconSuccessCondition
2024-11-11 12:19:49 +01:00
Shafik
19073534dd Docs update for:
PLAYERTASK:AddReconSuccessCondition
2024-11-11 12:45:25 +02:00
Thomas
23080e3cc4 Merge pull request #2187 from shaji-Dev/Heartbreaker
PLAYERTASK Added helper functions
2024-11-10 14:56:24 +01:00
Shafik
b50d3fa809 [Added] Constructor PLAYERTASK:NewFromTarget that determines task type based on the target object
[Added] `PLAYERTASK:_GetTaskTypeForTarget` to get the task type based on target description
[Added] `PLAYERTASKCONTROLLER.Scores` for RECON, ESCORT and CAP
[Added] `PLAYERTASK:AddStaticObjectSuccessCondition` task success condition for dead STATIC, SET_STATIC, SCENERY or SET_SCENERY targets
[Added] `PLAYERTASK:AddReconSuccessCondition` for AUFTRAG.Type.RECON tasks for when a client is at a certain LOS distance from the target
2024-11-10 15:41:21 +02:00
Shafik
1f191cf9bb [Added] Constructor PLAYERTASK:NewFromTarget that determines task type based on the target object
[Added] `PLAYERTASK:_GetTaskTypeForTarget` to get the task type based on target description
[Added] `PLAYERTASKCONTROLLER.Scores` for RECON, ESCORT and CAP
[Added] `PLAYERTASK:AddStaticObjectSuccessCondition` task success condition for dead STATIC, SET_STATIC, SCENERY or SET_SCENERY targets
[Added] `PLAYERTASK:AddReconSuccessCondition` for AUFTRAG.Type.RECON tasks for when a client is at a certain LOS distance from the target
2024-11-10 15:37:16 +02:00
Thomas
6c773786d2 Merge pull request #2186 from FlightControl-Master/Applevangelist-patch-2
Update Enums.lua
2024-11-10 13:46:00 +01:00
Thomas
8939963187 Update Enums.lua
OH58d weapons
2024-11-10 13:45:44 +01:00
Thomas
e3fb693cb7 Merge pull request #2185 from Fedge7/personal/fedge/enums-weapons-oh58
Corrects the table values for OH58 weapon enums.
2024-11-10 13:44:01 +01:00
Fedge
7cc11f55bb Corrects the table values for OH58 weapon enums.
After seeing FARP rearming broken for the OH58 in a recent DCS update, I checked all these enum values and updated them by referencing a freshly-created MIZ. I dropped a FARP and added a unique quantity of all the OH58's weapons to the FARP's warehouse. The values were then scraped from the MIZ's `warehouse` lua file.
2024-11-08 00:09:18 -07:00
Thomas
f9747d1c4c Merge pull request #2184 from FlightControl-Master/Applevangelist-pwsh-1
Update SRS.lua
2024-11-04 14:49:56 +01:00
Thomas
60a3d3409e Update SRS.lua
#MSRS Test to get the pwsh window to minimize
2024-11-04 14:49:26 +01:00
Applevangelist
cd8cbc54c8 Merge remote-tracking branch 'origin/master' into develop 2024-10-31 18:16:32 +01:00
Applevangelist
b72124c0d9 #CONTROLLABLE Corrected parameter omissions 2024-10-31 18:16:02 +01:00
Applevangelist
0d6a1644e2 Merge remote-tracking branch 'origin/master' into develop 2024-10-31 16:06:00 +01:00
Applevangelist
d51e761b26 #CONTROLLABLE - Rollback changes for EPLRS command 2024-10-31 16:05:24 +01:00
Applevangelist
1ebe8b82ec Merge remote-tracking branch 'origin/master' into develop 2024-10-30 17:10:25 +01:00
Applevangelist
1445ef61a0 #AIRBASE - added bases from 2.9.9.2280 2024-10-30 17:09:38 +01:00
Applevangelist
f82490f0d5 Merge remote-tracking branch 'origin/master' into develop 2024-10-29 13:57:56 +01:00
Applevangelist
dfe2ed2a98 #EVENT - Fix for TgtUnit Name being unobtainable 2024-10-29 13:57:21 +01:00
Applevangelist
bb5c044a25 Merge remote-tracking branch 'origin/master' into develop 2024-10-29 13:19:40 +01:00
Applevangelist
be87103b53 #CTLD - fixed a collision when extracting on a possible distance key duplication 2024-10-29 13:18:55 +01:00
Applevangelist
54f7627f9c Merge remote-tracking branch 'origin/master' into develop 2024-10-29 11:37:44 +01:00
Applevangelist
2fb460c4bb #ATC_Ground - stop kicking players immediately for speeding 2024-10-29 11:37:06 +01:00
Applevangelist
1b3c94cc57 Merge remote-tracking branch 'origin/master' into develop 2024-10-27 13:25:41 +01:00
Applevangelist
216ea230a8 #BASE - Roll back to "old" serialize method for Trace 2024-10-27 13:25:13 +01:00
Applevangelist
7e727620a4 Merge remote-tracking branch 'origin/develop' into develop 2024-10-26 18:45:45 +02:00
Applevangelist
13ec9bcdd4 Merge remote-tracking branch 'origin/master' into develop 2024-10-26 18:45:40 +02:00
Applevangelist
90096163ee EPLRS - give group ID only if ground 2024-10-26 18:45:01 +02:00
Thomas
d2564d4a54 Merge pull request #2183 from FlightControl-Master/master
Merge from master
2024-10-21 11:35:35 +02:00
Thomas
cd178d6a8c Merge pull request #2182 from FlightControl-Master/Applevangelist-patch-1
Update MarkerOps_Base.lua
2024-10-21 11:33:14 +02:00
Thomas
2aa0b5ddfb Update MarkerOps_Base.lua
Added data to FSM events
2024-10-21 11:32:47 +02:00
Applevangelist
cf6e026392 Merge remote-tracking branch 'origin/master' into develop 2024-10-11 08:06:29 +02:00
Applevangelist
37f819458a #AIRBOSS - Change event Land to event RunwayTouch 2024-10-11 08:06:05 +02:00
Applevangelist
16fb0c3081 Merge remote-tracking branch 'origin/master' into develop 2024-10-08 10:05:56 +02:00
Applevangelist
168f4301d2 #GROUP - extended options to create callsigns for TTS with own function 2024-10-08 10:05:31 +02:00
Applevangelist
9fb03d9d8a xx 2024-10-08 10:04:05 +02:00
Applevangelist
d89ed535b7 Merge remote-tracking branch 'origin/master' into develop 2024-10-03 14:06:42 +02:00
Applevangelist
08b4b890ce #AWACS - added option to increase mission range from 100nm as default 2024-10-03 14:06:26 +02:00
Applevangelist
d5a406c60f #MSRS experimental - option to use PowerShell to execute SRS-TTS - set MSRS.UsePowerShell = true to test it. Known issue - PS window does not close until everything is executed. 2024-10-03 14:05:51 +02:00
Applevangelist
cb16210577 #SEAD - better event management of weapons, return earlier if no (H)ARM, return if SEAD Plane is nil 2024-10-03 14:03:06 +02:00
Applevangelist
8c0e0de45f xx 2024-10-01 12:10:21 +02:00
Applevangelist
3524cba4ef xx 2024-10-01 12:08:51 +02:00
Applevangelist
df6a37a972 Merge remote-tracking branch 'origin/develop' into develop 2024-10-01 12:06:24 +02:00
Applevangelist
13600ff6fd Merge remote-tracking branch 'origin/master' into develop 2024-10-01 12:06:17 +02:00
Frank
a957bb4968 Merge branch 'master' into develop 2024-10-01 09:08:03 +02:00
Frank
5f8d1cf5b0 Update Warehouse.lua
- Added check for incorrect plane attribute
2024-10-01 09:07:51 +02:00
Thomas
a115267e08 Merge pull request #2181 from DarthZyll/patch-6
Update CSAR.lua - Updated correct type name for Kiowa to OH58D
2024-10-01 06:19:29 +02:00
Applevangelist
37998fbf1d Merge remote-tracking branch 'origin/master' into develop 2024-09-30 11:36:06 +02:00
Applevangelist
846aa823d4 fixes for #SEAD and #SPAWN 2024-09-30 11:35:25 +02:00
Applevangelist
0d877853eb Merge remote-tracking branch 'origin/master' into develop 2024-09-27 11:58:08 +02:00
Applevangelist
68298fc585 #EVENT Fix for scenery sometimes nit having a getName() function 2024-09-27 11:57:40 +02:00
Applevangelist
b97a7cf387 Merge remote-tracking branch 'origin/master' into develop 2024-09-26 09:07:20 +02:00
Applevangelist
e9d75f6d94 #CSAR Beacon Name Fix 2024-09-26 09:06:57 +02:00
Applevangelist
1dce2eb747 Merge remote-tracking branch 'origin/develop' into develop 2024-09-26 09:03:05 +02:00
Applevangelist
7c110d90cd Merge remote-tracking branch 'origin/master' into develop 2024-09-26 09:03:00 +02:00
Mike Young
2ecc02ce4d Update CSAR.lua - Updated correct type name for Kiowa to OH58D
Update CSAR.lua - Updated correct type name for Kiowa to OH58D - original Brykeller change
2024-09-26 01:05:38 -04:00
Thomas
8fa5277417 Merge pull request #2178 from brykeller/patch-2
Kiowa rescues sit on the skids, no doors need to be opened
2024-09-26 06:55:02 +02:00
Thomas
7cce5745af Merge pull request #2177 from DarthZyll/patch-5
Update CSAR.lua for BeaconName being built incorrectly
2024-09-26 06:54:20 +02:00
Thomas
231f1f236d Merge pull request #2180 from brykeller/patch-3
Update CTLD.lua to update the correct Kiowa typename to OH58D
2024-09-26 06:53:21 +02:00
Bryan Keller
ece0a46f97 Update CTLD.lua to update the correct Kiowa typename to OH58D
Update CTLD.lua to update the correct Kiowa typename to OH58D
2024-09-25 18:59:26 -07:00
Bryan Keller
2c9b0b8376 Kiowa rescues sit on the skids, no doors need to be opened
Kiowa rescues sit on the skids, no doors need to be opened
2024-09-25 18:51:31 -07:00
Mike Young
76577c03e7 Update CSAR.lua for BeaconName being built incorrectly
Prior to this change, the F10 menu "List Active CSAR" is always blank even after a CSAR activity occurs
2024-09-25 21:13:22 -04:00
Applevangelist
48d30250a0 Merge remote-tracking branch 'origin/master' into develop 2024-09-23 12:44:14 +02:00
Applevangelist
a798f2d61c Small fixes 2024-09-23 12:43:39 +02:00
Applevangelist
d0f8b1436b #EASYGCICAP - Alert5 typo correction, typo corrected to assign correct coalition side 2024-09-23 10:21:05 +02:00
Applevangelist
ddc5e0f86e Merge remote-tracking branch 'origin/master' into develop 2024-09-15 19:25:08 +02:00
Applevangelist
ae08c87822 #CTLD 2024-09-15 19:24:24 +02:00
Thomas
ae880e9d1c Merge pull request #2176 from Maintenance-Partnership-Systems/Minor-Doco-Fixes
Minor doco fixes
2024-09-15 11:16:08 +02:00
Michael Barnes
101d2e1de5 More incorrect AIRBASE constants in examples. 2024-09-15 18:48:42 +10:00
Michael Barnes
f6b7708567 Fixed some outdated references to AIRBASE constants in examples. 2024-09-15 18:34:18 +10:00
Applevangelist
051286acd1 #SPAWN - Tuning measures from DEVELOP encorporated 2024-09-08 13:24:09 +02:00
73 changed files with 7176 additions and 4104 deletions

View File

@@ -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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event 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. -- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage. --- OnAfter Transition Handler for Event Engage.

View File

@@ -162,7 +162,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event 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. -- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage. --- OnAfter Transition Handler for Event Engage.

View File

@@ -846,7 +846,6 @@ function AI_PATROL_ZONE:onafterStatus()
OldAIControllable:SetTask( TimedOrbitTask, 10 ) OldAIControllable:SetTask( TimedOrbitTask, 10 )
RTB = true RTB = true
else
end end
-- TODO: Check GROUP damage function. -- TODO: Check GROUP damage function.
@@ -856,6 +855,16 @@ function AI_PATROL_ZONE:onafterStatus()
RTB = true RTB = true
end 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 if RTB == true then
self:RTB() self:RTB()
else else

View File

@@ -34,11 +34,12 @@ local _TraceClassMethod = {}
local _ClassID = 0 local _ClassID = 0
--- --- Base class of everything
-- @type BASE -- @type BASE
-- @field ClassName The name of the class. -- @field #string ClassName The name of the class.
-- @field ClassID The ID number of the class. -- @field #number ClassID The ID number of the class.
-- @field ClassNameAndID The name of the class concatenated with 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 --- BASE class
-- --
@@ -200,6 +201,7 @@ BASE = {
States = {}, States = {},
Debug = debug, Debug = debug,
Scheduler = nil, Scheduler = nil,
Properties = {},
} }
-- @field #BASE.__ -- @field #BASE.__
@@ -210,14 +212,6 @@ BASE._ = {
Schedules = {}, --- Contains the Schedulers Active Schedules = {}, --- Contains the Schedulers Active
} }
--- The Formation Class
-- @type FORMATION
-- @field Cone A cone formation.
FORMATION = {
Cone = "Cone",
Vee = "Vee",
}
--- BASE constructor. --- BASE constructor.
-- --
-- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. -- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE.
@@ -1116,6 +1110,31 @@ function BASE:ClearState( Object, StateName )
end end
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 -- Trace section
-- Log a trace (only shown when trace is on) -- Log a trace (only shown when trace is on)
@@ -1416,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 ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
else 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
end end
@@ -1443,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 ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
else 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
end end

View File

@@ -20,7 +20,7 @@
-- --
-- @module Core.ClientMenu -- @module Core.ClientMenu
-- @image Core_Menu.JPG -- @image Core_Menu.JPG
-- last change: May 2024 -- last change: Jan 2025
-- TODO -- TODO
---------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------
@@ -57,9 +57,9 @@
--- ---
-- @field #CLIENTMENU -- @field #CLIENTMENU
CLIENTMENU = { CLIENTMENU = {
ClassName = "CLIENTMENUE", ClassName = "CLIENTMENU",
lid = "", lid = "",
version = "0.1.2", version = "0.1.3",
name = nil, name = nil,
path = nil, path = nil,
group = nil, group = nil,
@@ -455,7 +455,7 @@ end
-- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENUMANAGER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
-- @return #CLIENTMENUMANAGER self -- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:_EventHandler(EventData) function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
self:T(self.lid.."_EventHandler: "..EventData.id) self:T(self.lid.."_EventHandler: "..EventData.id)
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName)) --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 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 EventData.IniPlayerName and EventData.IniGroup then
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) 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 return self
end end
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName) --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.PilotDead, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
self:SetEventPriority(5) self:SetEventPriority(6)
return self return self
end end

View File

@@ -211,10 +211,9 @@ function DATABASE:AddStatic( DCSStaticName )
if not self.STATICS[DCSStaticName] then if not self.STATICS[DCSStaticName] then
self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName )
return self.STATICS[DCSStaticName]
end end
return nil return self.STATICS[DCSStaticName]
end end
@@ -224,12 +223,11 @@ function DATABASE:DeleteStatic( DCSStaticName )
self.STATICS[DCSStaticName] = nil self.STATICS[DCSStaticName] = nil
end end
--- Finds a STATIC based on the StaticName. --- Finds a STATIC based on the Static Name.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string StaticName -- @param #string StaticName Name of the static object.
-- @return Wrapper.Static#STATIC The found STATIC. -- @return Wrapper.Static#STATIC The found STATIC.
function DATABASE:FindStatic( StaticName ) function DATABASE:FindStatic( StaticName )
local StaticFound = self.STATICS[StaticName] local StaticFound = self.STATICS[StaticName]
return StaticFound return StaticFound
end end
@@ -241,9 +239,8 @@ end
function DATABASE:AddDynamicCargo( Name ) function DATABASE:AddDynamicCargo( Name )
if not self.DYNAMICCARGO[Name] then if not self.DYNAMICCARGO[Name] then
self.DYNAMICCARGO[Name] = DYNAMICCARGO:Register(Name) self.DYNAMICCARGO[Name] = DYNAMICCARGO:Register(Name)
return self.DYNAMICCARGO[Name]
end end
return nil return self.DYNAMICCARGO[Name]
end end
--- Finds a DYNAMICCARGO based on the Dynamic Cargo Name. --- Finds a DYNAMICCARGO based on the Dynamic Cargo Name.
@@ -1582,12 +1579,29 @@ end
-- @param DCS#Airbase airbase Airbase. -- @param DCS#Airbase airbase Airbase.
-- @return #DATABASE self -- @return #DATABASE self
function DATABASE:_RegisterAirbase(airbase) function DATABASE:_RegisterAirbase(airbase)
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
local countHSyria = 0
if airbase then if airbase then
-- Get the airbase name. -- Get the airbase name.
local DCSAirbaseName = airbase:getName() 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. -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=airbase:getID() local airbaseID=airbase:getID()

View File

@@ -1329,6 +1329,7 @@ function EVENT:onEvent( Event )
end end
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or "" 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 if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
Event.IniDCSGroupName = Event.IniDCSGroup:getName() Event.IniDCSGroupName = Event.IniDCSGroup:getName()
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
@@ -1371,10 +1372,10 @@ function EVENT:onEvent( Event )
-- Scenery -- Scenery
--- ---
Event.IniDCSUnit = Event.initiator 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.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) 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" Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
elseif Event.IniObjectCategory == Object.Category.BASE then elseif Event.IniObjectCategory == Object.Category.BASE then
@@ -1473,11 +1474,13 @@ function EVENT:onEvent( Event )
-- SCENERY -- SCENERY
--- ---
Event.TgtDCSUnit = Event.target Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil
Event.TgtUnitName = Event.TgtDCSUnitName if Event.TgtDCSUnitName~=nil then
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
end end
end end

View File

@@ -948,7 +948,8 @@ do -- FSM
end end
do -- FSM_CONTROLLABLE do -- FSM_CONTROLLABLE
---
-- @type FSM_CONTROLLABLE -- @type FSM_CONTROLLABLE
-- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @field Wrapper.Controllable#CONTROLLABLE Controllable
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -1081,7 +1082,8 @@ do -- FSM_CONTROLLABLE
end end
do -- FSM_PROCESS do -- FSM_PROCESS
---
-- @type FSM_PROCESS -- @type FSM_PROCESS
-- @field Tasking.Task#TASK Task -- @field Tasking.Task#TASK Task
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE

View File

@@ -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. --- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded -- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param #string From The From state -- @param #string From The From state.
-- @param #string Event The Event called -- @param #string Event The Event called.
-- @param #string To The To state -- @param #string To The To state.
-- @param #string Text The text on the marker -- @param #string Text The text on the marker.
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text.
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number MarkerID Id of this marker -- @param #number MarkerID Id of this marker.
-- @param #number CoalitionNumber Coalition of the marker creator -- @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. --- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged -- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param #string From The From state -- @param #string From The From state.
-- @param #string Event The Event called -- @param #string Event The Event called.
-- @param #string To The To state -- @param #string To The To state.
-- @param #string Text The text on the marker -- @param #string Text The text on the marker.
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text.
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number MarkerID Id of this marker -- @param #number MarkerID Id of this marker.
-- @param #number CoalitionNumber Coalition of the marker creator -- @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. --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
@@ -167,7 +171,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) 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
end end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
@@ -177,7 +181,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) 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
end end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then elseif Event.id==world.event.S_EVENT_MARK_REMOVED then

View File

@@ -105,6 +105,7 @@ function MENU_INDEX:PrepareCoalition( CoalitionSide )
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
end end
--- ---
-- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#GROUP Group
function MENU_INDEX:PrepareGroup( Group ) function MENU_INDEX:PrepareGroup( Group )
@@ -118,9 +119,11 @@ end
function MENU_INDEX:HasMissionMenu( Path ) function MENU_INDEX:HasMissionMenu( Path )
return self.MenuMission.Menus[Path] return self.MenuMission.Menus[Path]
end end
function MENU_INDEX:SetMissionMenu( Path, Menu ) function MENU_INDEX:SetMissionMenu( Path, Menu )
self.MenuMission.Menus[Path] = Menu self.MenuMission.Menus[Path] = Menu
end end
function MENU_INDEX:ClearMissionMenu( Path ) function MENU_INDEX:ClearMissionMenu( Path )
self.MenuMission.Menus[Path] = nil self.MenuMission.Menus[Path] = nil
end end
@@ -128,9 +131,11 @@ end
function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
return self.Coalition[Coalition].Menus[Path] return self.Coalition[Coalition].Menus[Path]
end end
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
self.Coalition[Coalition].Menus[Path] = Menu self.Coalition[Coalition].Menus[Path] = Menu
end end
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
self.Coalition[Coalition].Menus[Path] = nil self.Coalition[Coalition].Menus[Path] = nil
end end
@@ -138,19 +143,24 @@ end
function MENU_INDEX:HasGroupMenu( Group, Path ) function MENU_INDEX:HasGroupMenu( Group, Path )
if Group and Group:IsAlive() then if Group and Group:IsAlive() then
local MenuGroupName = Group:GetName() 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 end
return nil return nil
end end
function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
local MenuGroupName = Group:GetName() local MenuGroupName = Group:GetName()
Group:F({MenuGroupName=MenuGroupName,Path=Path}) --Group:F({MenuGroupName=MenuGroupName,Path=Path})
self.Group[MenuGroupName].Menus[Path] = Menu self.Group[MenuGroupName].Menus[Path] = Menu
end end
function MENU_INDEX:ClearGroupMenu( Group, Path ) function MENU_INDEX:ClearGroupMenu( Group, Path )
local MenuGroupName = Group:GetName() local MenuGroupName = Group:GetName()
self.Group[MenuGroupName].Menus[Path] = nil self.Group[MenuGroupName].Menus[Path] = nil
end end
function MENU_INDEX:Refresh( Group ) function MENU_INDEX:Refresh( Group )
for MenuID, Menu in pairs( self.MenuMission.Menus ) do for MenuID, Menu in pairs( self.MenuMission.Menus ) do
Menu:Refresh() Menu:Refresh()

View File

@@ -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. --- 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 self
-- @param #string MessageText is the text of the Message. -- @param #string Text 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 #number Duration Duration in seconds how long the message text is shown.
-- @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 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. -- @param #boolean ClearScreen (optional) Clear all previous messages if true.
-- @return #MESSAGE -- @return #MESSAGE self
-- @usage -- @usage
-- --
-- -- Create a series of new Messages. -- -- Create a series of new Messages.
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". -- -- 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". -- -- 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".
-- -- 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" ) -- 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" ) -- 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" ) -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed 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() ) local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
self:F( { Text, Duration, Category } )
self.MessageType = nil self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title... -- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then if Category and Category ~= "" then
if MessageCategory:sub( -1 ) ~= "\n" then if Category:sub( -1 ) ~= "\n" then
self.MessageCategory = MessageCategory .. ": " self.MessageCategory = Category .. ": "
else else
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" self.MessageCategory = Category:sub( 1, -2 ) .. ":\n"
end end
else else
self.MessageCategory = "" self.MessageCategory = ""
@@ -114,9 +116,9 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
self.ClearScreen = ClearScreen self.ClearScreen = ClearScreen
end end
self.MessageDuration = MessageDuration or 5 self.MessageDuration = Duration or 5
self.MessageTime = timer.getTime() 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.MessageSent = false
self.MessageGroup = false self.MessageGroup = false
@@ -462,6 +464,7 @@ _MESSAGESRS = {}
-- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @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 #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set.
-- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from. -- @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 -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
@@ -469,7 +472,7 @@ _MESSAGESRS = {}
-- -- later on in your code -- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- 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" _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) _MESSAGESRS.MSRS:SetCoordinate(Coordinate)
end end
if Backend then
_MESSAGESRS.MSRS:SetBackend(Backend)
end
_MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB" _MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB"
_MESSAGESRS.MSRS:SetCulture(Culture) _MESSAGESRS.MSRS:SetCulture(Culture)

View File

@@ -110,7 +110,7 @@ do -- COORDINATE
-- --
-- ## 4.4) Get the North correction of the current location. -- ## 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 -- ## 4.5) Point Randomization
-- --
@@ -968,8 +968,13 @@ do -- COORDINATE
-- @return DCS#Distance Distance The distance in meters. -- @return DCS#Distance Distance The distance in meters.
function COORDINATE:Get2DDistance(TargetCoordinate) function COORDINATE:Get2DDistance(TargetCoordinate)
if not TargetCoordinate then return 1000000 end if not TargetCoordinate then return 1000000 end
local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} --local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z}
local norm=UTILS.VecNorm(a) 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 return norm
end end
@@ -1216,7 +1221,7 @@ do -- COORDINATE
local s = string.format( '%03d°', AngleDegrees ) local s = string.format( '%03d°', AngleDegrees )
if MagVar then if MagVar then
local variation = UTILS.GetMagneticDeclination() or 0 local variation = self:GetMagneticDeclination() or 0
local AngleMagnetic = AngleDegrees - variation local AngleMagnetic = AngleDegrees - variation
if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end
@@ -1329,13 +1334,16 @@ do -- COORDINATE
-- @param Core.Settings#SETTINGS Settings -- @param Core.Settings#SETTINGS Settings
-- @param #string Language (Optional) Language "en" or "ru" -- @param #string Language (Optional) Language "en" or "ru"
-- @param #boolean MagVar If true, also state angle in magnetic -- @param #boolean MagVar If true, also state angle in magnetic
-- @param #number Precision Rounding precision, defaults to 0
-- @return #string The BR Text -- @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 local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
Precision = Precision or 0
local BearingText = self:GetBearingText( AngleRadians, 0, Settings, MagVar ) 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 local BRText = BearingText .. DistanceText
@@ -1957,9 +1965,18 @@ do -- COORDINATE
--- Smokes the point in a color. --- Smokes the point in a color.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor -- @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 } ) 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 end
--- Smoke the COORDINATE Green. --- Smoke the COORDINATE Green.
@@ -2410,7 +2427,7 @@ do -- COORDINATE
for i,coord in ipairs(Coordinates) do for i,coord in ipairs(Coordinates) do
vecs[i+1]=coord:GetVec3() vecs[i+1]=coord:GetVec3()
end end
if #vecs<3 then if #vecs<3 then
self:E("ERROR: A free form polygon needs at least three points!") self:E("ERROR: A free form polygon needs at least three points!")
elseif #vecs==3 then elseif #vecs==3 then
@@ -2900,12 +2917,13 @@ do -- COORDINATE
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @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 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 #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. -- @return #string The BR text.
function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar ) function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar, Precision )
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local AngleRadians = self:GetAngleRadians( DirectionVec3 )
local Distance = self:Get2DDistance( FromCoordinate ) 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 end
--- Return a BRA string from a COORDINATE to the COORDINATE. --- Return a BRA string from a COORDINATE to the COORDINATE.
@@ -2941,6 +2959,8 @@ do -- COORDINATE
local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local AngleRadians = self:GetAngleRadians( DirectionVec3 )
local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 )
local magnetic = self:GetMagneticDeclination() or 0
bearing = bearing - magnetic
local rangeMetres = self:Get2DDistance(currentCoord) local rangeMetres = self:Get2DDistance(currentCoord)
local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0)
@@ -3313,16 +3333,16 @@ do -- COORDINATE
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. -- @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 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. -- @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() } ) -- self:E( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
local ModeA2A = nil local ModeA2A = nil
--[[
if Task then if Task then
if Task:IsInstanceOf( TASK_A2A ) then if Task:IsInstanceOf( TASK_A2A ) then
ModeA2A = true ModeA2A = true
@@ -3339,7 +3359,7 @@ do -- COORDINATE
end end
end end
end end
--]]
if ModeA2A == nil then if ModeA2A == nil then
local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false

View File

@@ -289,7 +289,14 @@ do -- SET_BASE
-- Debug info. -- Debug info.
--self:T2( { ObjectName = ObjectName, Object = Object } ) --self:T2( { ObjectName = ObjectName, Object = Object } )
-- Error ahndling
if not ObjectName or ObjectName == "" then
self:E("SET_BASE:Add - Invalid ObjectName handed")
self:E({ObjectName=ObjectName, Object=Object})
return self
end
-- Ensure that the existing element is removed from the Set before a new one is inserted to the Set -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set
if self.Set[ObjectName] then if self.Set[ObjectName] then
self:Remove( ObjectName, true ) self:Remove( ObjectName, true )
@@ -524,6 +531,21 @@ do -- SET_BASE
return self.SomeIteratorLimit or self:Count() return self.SomeIteratorLimit or self:Count()
end end
--- Get max threat level of all objects in the SET.
-- @param #SET_BASE self
-- @return #number Max threat level found.
function SET_BASE:GetThreatLevelMax()
local ThreatMax = 0
for _,_unit in pairs(self.Set or {}) do
local unit = _unit -- Wrapper.Unit#UNIT
local threat = unit.GetThreatLevel and unit:GetThreatLevel() or 0
if threat > ThreatMax then
ThreatMax = threat
end
end
return ThreatMax
end
--- Filters for the defined collection. --- Filters for the defined collection.
-- @param #SET_BASE self -- @param #SET_BASE self
@@ -1538,6 +1560,13 @@ do
local size = 1 local size = 1
if Event.IniDCSGroup then if Event.IniDCSGroup then
size = Event.IniDCSGroup:getSize() size = Event.IniDCSGroup:getSize()
elseif Event.IniDCSGroupName then
local grp = Group.getByName(Event.IniDCSGroupName)
if grp then
size = grp:getSize()
end
elseif Object:IsAlive() then
size = Object:CountAliveUnits()
end end
if size == 1 then -- Only remove if the last unit of the group was destroyed. if size == 1 then -- Only remove if the last unit of the group was destroyed.
self:Remove( ObjectName ) self:Remove( ObjectName )
@@ -1709,7 +1738,7 @@ do
--- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1756,7 +1785,7 @@ do
--- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise.
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1781,7 +1810,7 @@ do
--- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is in @{Core.Zone} --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is in @{Core.Zone}
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise.
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1807,7 +1836,7 @@ do
--- Iterate the SET_GROUP and return true if at least one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is partly in @{Core.Zone}. --- Iterate the SET_GROUP and return true if at least one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is partly in @{Core.Zone}.
-- Will return false if a @{Wrapper.Group#GROUP} is fully in the @{Core.Zone} -- Will return false if a @{Wrapper.Group#GROUP} is fully in the @{Core.Zone}
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise.
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1842,7 +1871,7 @@ do
-- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the -- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the
-- mission designer to add a dedicated method -- mission designer to add a dedicated method
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean true if no @{Wrapper.Group#GROUP} is inside the @{Core.Zone#ZONE} in any way, false otherwise. -- @return #boolean true if no @{Wrapper.Group#GROUP} is inside the @{Core.Zone#ZONE} in any way, false otherwise.
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1869,7 +1898,7 @@ do
-- That could easily be done with SET_GROUP:ForEachGroupCompletelyInZone(), but this function -- That could easily be done with SET_GROUP:ForEachGroupCompletelyInZone(), but this function
-- provides an easy to use shortcut... -- provides an easy to use shortcut...
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #number the number of GROUPs completely in the Zone -- @return #number the number of GROUPs completely in the Zone
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -1891,7 +1920,7 @@ do
--- Iterate the SET_GROUP and count how many UNITs are completely in the Zone --- Iterate the SET_GROUP and count how many UNITs are completely in the Zone
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #number the number of GROUPs completely in the Zone -- @return #number the number of GROUPs completely in the Zone
-- @usage -- @usage
-- local MyZone = ZONE:New("Zone1") -- local MyZone = ZONE:New("Zone1")
@@ -2706,7 +2735,7 @@ do -- SET_UNIT
--- Check if no element of the SET_UNIT is in the Zone. --- Check if no element of the SET_UNIT is in the Zone.
-- @param #SET_UNIT self -- @param #SET_UNIT self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean -- @return #boolean
function SET_UNIT:IsNotInZone( Zone ) function SET_UNIT:IsNotInZone( Zone )
@@ -3746,7 +3775,7 @@ do -- SET_STATIC
--- Check if no element of the SET_STATIC is in the Zone. --- Check if no element of the SET_STATIC is in the Zone.
-- @param #SET_STATIC self -- @param #SET_STATIC self
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param Core.Zone#ZONE Zone The Zone to be tested for.
-- @return #boolean -- @return #boolean
function SET_STATIC:IsNotInZone( Zone ) function SET_STATIC:IsNotInZone( Zone )
@@ -4582,6 +4611,16 @@ do -- SET_CLIENT
end end
return self return self
end end
--- Make the SET handle CA slots **only** (GROUND units used by any player). Needs active filtering with `FilterStart()`
-- @param #SET_CLIENT self
-- @return #SET_CLIENT self
function SET_CLIENT:HandleCASlots()
self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit)
self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit)
self:FilterFunction(function(client) if client and client:IsAlive() and client:IsGround() then return true else return false end end)
return self
end
--- Handles the Database to check on an event (birth) that the Object was added in the Database. --- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
@@ -5364,6 +5403,7 @@ do -- SET_AIRBASE
Airbases = {}, Airbases = {},
Filter = { Filter = {
Coalitions = nil, Coalitions = nil,
Zones = nil,
}, },
FilterMeta = { FilterMeta = {
Coalitions = { Coalitions = {
@@ -5515,6 +5555,31 @@ do -- SET_AIRBASE
end end
return self return self
end end
--- Builds a set of airbase objects in zones.
-- @param #SET_AIRBASE self
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
-- @return #SET_AIRBASE self
function SET_AIRBASE:FilterZones( Zones )
if not self.Filter.Zones then
self.Filter.Zones = {}
end
local zones = {}
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
zones = Zones.Set
elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones = Zones
end
for _,Zone in pairs( zones ) do
local zonename = Zone:GetName()
--self:T((zonename)
self.Filter.Zones[zonename] = Zone
end
return self
end
--- Starts the filtering. --- Starts the filtering.
-- @param #SET_AIRBASE self -- @param #SET_AIRBASE self
@@ -5653,6 +5718,20 @@ do -- SET_AIRBASE
--self:T(( { "Evaluated Category", MAirbaseCategory } ) --self:T(( { "Evaluated Category", MAirbaseCategory } )
MAirbaseInclude = MAirbaseInclude and MAirbaseCategory MAirbaseInclude = MAirbaseInclude and MAirbaseCategory
end end
if self.Filter.Zones and MAirbaseInclude then
local MAirbaseZone = false
for ZoneName, Zone in pairs( self.Filter.Zones ) do
--self:T(( "Zone:", ZoneName )
local coord = MAirbase:GetCoordinate()
if coord and Zone:IsCoordinateInZone(coord) then
MAirbaseZone = true
end
--self:T(( { "Evaluated Zone", MSceneryZone } )
end
MAirbaseInclude = MAirbaseInclude and MAirbaseZone
end
end end
if self.Filter.Functions and MAirbaseInclude then if self.Filter.Functions and MAirbaseInclude then
@@ -7936,22 +8015,31 @@ do -- SET_OPSGROUP
--- Handles the OnBirth event for the Set. --- Handles the OnBirth event for the Set.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param Core.Event#EVENTDATA Event Event data. -- @param Core.Event#EVENTDATA Event Event data.
function SET_OPSGROUP:_EventOnBirth( Event ) function SET_OPSGROUP:_EventOnBirth(Event)
--self:F3( { Event } ) --self:F3( { Event } )
if Event.IniDCSUnit and Event.IniDCSGroup then if Event.IniDCSUnit and Event.IniDCSGroup then
local DCSgroup=Event.IniDCSGroup --DCS#Group local DCSgroup = Event.IniDCSGroup --DCS#Group
if DCSgroup:getInitialSize() == DCSgroup:getSize() then -- This seems to be not a good check as even for the first birth event, getSize returns the total number of units in the group. -- group:CountAliveUnits() alternative as this fails for Respawn/Teleport
local CountAliveActive = 0
local groupname, group = self:AddInDatabase( Event ) for index, data in pairs(DCSgroup:getUnits()) do
if data:isExist() and data:isActive() then
if group and group:CountAliveUnits()==DCSgroup:getInitialSize() then CountAliveActive = CountAliveActive + 1
if group and self:IsIncludeObject( group ) then end
self:Add( groupname, group ) end
end
if DCSgroup:getInitialSize() == DCSgroup:getSize() then
local groupname, group = self:AddInDatabase(Event)
-- group:CountAliveUnits() alternative
if group and CountAliveActive == DCSgroup:getInitialSize() then
if group and self:IsIncludeObject(group) then
self:Add(groupname, group)
end
end
end end
end
end end
end end
@@ -7962,7 +8050,7 @@ do -- SET_OPSGROUP
function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) function SET_OPSGROUP:_EventOnDeadOrCrash( Event )
--self:F( { Event } ) --self:F( { Event } )
if Event.IniDCSUnit then if Event.IniDCSGroup then
local ObjectName, Object = self:FindInDatabase( Event ) local ObjectName, Object = self:FindInDatabase( Event )
if ObjectName then if ObjectName then
if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed.
@@ -8355,14 +8443,17 @@ do -- SET_SCENERY
-- @param #SET_SCENERY self -- @param #SET_SCENERY self
-- @return Core.Point#COORDINATE The center coordinate of all the objects in the set. -- @return Core.Point#COORDINATE The center coordinate of all the objects in the set.
function SET_SCENERY:GetCoordinate() function SET_SCENERY:GetCoordinate()
--[[
local Coordinate = COORDINATE:New({0,0,0}) local Coordinate = COORDINATE:New({0,0,0})
local Item = self:GetRandomSurely() local Item = self:GetRandomSurely()
if Item then if Item then
Coordinate:GetCoordinate() Coordinate:GetCoordinate()
end end
--]]
local Coordinate = self:GetFirst():GetCoordinate()
local x1 = Coordinate.x local x1 = Coordinate.x
local x2 = Coordinate.x local x2 = Coordinate.x

View File

@@ -494,7 +494,7 @@ do -- SETTINGS
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
end end
-- @param #SETTINGS self --- @param #SETTINGS self
-- @param Wrapper.Group#GROUP MenuGroup Group for which to add menus. -- @param Wrapper.Group#GROUP MenuGroup Group for which to add menus.
-- @param #table RootMenu Root menu table -- @param #table RootMenu Root menu table
-- @return #SETTINGS -- @return #SETTINGS
@@ -948,49 +948,49 @@ do -- SETTINGS
return self return self
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem ) function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
self.A2GSystem = A2GSystem self.A2GSystem = A2GSystem
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem ) function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
self.A2ASystem = A2ASystem self.A2ASystem = A2ASystem
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy ) function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy ) function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW ) function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
self.Metric = MW self.Metric = MW
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime ) function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime ) self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll()
end end
do do
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
--BASE:E( {PlayerUnit:GetName(), A2GSystem } ) --BASE:E( {PlayerUnit:GetName(), A2GSystem } )
self.A2GSystem = A2GSystem self.A2GSystem = A2GSystem
@@ -1001,7 +1001,7 @@ do -- SETTINGS
end end
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem self.A2ASystem = A2ASystem
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) 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
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
self.LL_Accuracy = 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 ) 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
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
self.MGRS_Accuracy = 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 ) 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
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW ) function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
self.Metric = 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 ) 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
end end
-- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime ) function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime ) self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup )

View File

@@ -1081,7 +1081,7 @@ function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
self.SpawnRandomizeTemplate = true self.SpawnRandomizeTemplate = true
for SpawnGroupID = 1, self.SpawnMaxGroups do for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_RandomizeTemplate( SpawnGroupID ) self:_RandomizeTemplate( SpawnGroupID, RandomizePositionInZone )
end end
return self return self
@@ -1093,6 +1093,7 @@ end
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
-- @param #SPAWN self -- @param #SPAWN self
-- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. -- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned.
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
-- @return #SPAWN -- @return #SPAWN
-- @usage -- @usage
-- --
@@ -1111,11 +1112,11 @@ end
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
-- --
function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet,RandomizePositionInZone )
--self:F( { self.SpawnTemplatePrefix } ) --self:F( { self.SpawnTemplatePrefix } )
local setnames = SpawnTemplateSet:GetSetNames() local setnames = SpawnTemplateSet:GetSetNames()
self:InitRandomizeTemplate(setnames) self:InitRandomizeTemplate(setnames,RandomizePositionInZone)
return self return self
end end
@@ -1125,7 +1126,8 @@ end
-- but they will all follow the same Template route and have the same prefix name. -- but they will all follow the same Template route and have the same prefix name.
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. -- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned.
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
-- @return #SPAWN -- @return #SPAWN
-- @usage -- @usage
-- --
@@ -1141,12 +1143,12 @@ end
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 )
-- --
function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes, RandomizePositionInZone ) -- R2.3
--self:F( { self.SpawnTemplatePrefix } ) --self:F( { self.SpawnTemplatePrefix } )
local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce()
self:InitRandomizeTemplateSet( SpawnTemplateSet ) self:InitRandomizeTemplateSet( SpawnTemplateSet, RandomizePositionInZone )
return self return self
end end
@@ -1166,6 +1168,7 @@ end
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects. -- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects.
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
-- @return #SPAWN self -- @return #SPAWN self
-- @usage -- @usage
-- --
@@ -1178,7 +1181,7 @@ end
-- :InitRandomizeZones( ZoneTable ) -- :InitRandomizeZones( ZoneTable )
-- :SpawnScheduled( 5, .5 ) -- :SpawnScheduled( 5, .5 )
-- --
function SPAWN:InitRandomizeZones( SpawnZoneTable ) function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone )
--self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) --self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
local temptable = {} local temptable = {}
@@ -1190,7 +1193,7 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable )
self.SpawnRandomizeZones = true self.SpawnRandomizeZones = true
for SpawnGroupID = 1, self.SpawnMaxGroups do for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_RandomizeZones( SpawnGroupID ) self:_RandomizeZones( SpawnGroupID, RandomizePositionInZone )
end end
return self return self
@@ -1275,6 +1278,7 @@ end
--- Respawn group after landing. --- Respawn group after landing.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #number WaitingTime Wait this many seconds before despawning the alive group after landing. Defaults to 3 .
-- @return #SPAWN self -- @return #SPAWN self
-- @usage -- @usage
-- --
@@ -1282,15 +1286,16 @@ end
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) -- SpawnRU_SU34 = SPAWN:New( 'Su-34' )
-- :InitRandomizeRoute( 1, 1, 3000 ) -- :InitRandomizeRoute( 1, 1, 3000 )
-- :InitRepeatOnLanding() -- :InitRepeatOnLanding(20)
-- :Spawn() -- :Spawn()
-- --
function SPAWN:InitRepeatOnLanding() function SPAWN:InitRepeatOnLanding(WaitingTime)
--self:F( { self.SpawnTemplatePrefix } ) --self:F( { self.SpawnTemplatePrefix } )
self:InitRepeat() self:InitRepeat()
self.RepeatOnEngineShutDown = false self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true self.RepeatOnLanding = true
self.RepeatOnLandingTime = (WaitingTime and WaitingTime > 3) and WaitingTime or 3
return self return self
end end
@@ -2028,12 +2033,10 @@ end
-- --
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig )
-- --
function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata )
--self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } )
-- Get position of airbase. -- Get position of airbase.
local PointVec3 = SpawnAirbase:GetCoordinate() local PointVec3 = SpawnAirbase:GetCoordinate()
--self:T2( PointVec3 )
-- Set take off type. Default is hot. -- Set take off type. Default is hot.
Takeoff = Takeoff or SPAWN.Takeoff.Hot Takeoff = Takeoff or SPAWN.Takeoff.Hot
@@ -2043,39 +2046,24 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
EmergencyAirSpawn = true EmergencyAirSpawn = true
end end
--self:F( { SpawnIndex = self.SpawnIndex } )
if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then
-- Get group template. -- Get group template.
local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
--self:F( { SpawnTemplate = SpawnTemplate } )
if SpawnTemplate then if SpawnTemplate then
-- Check if the aircraft with the specified SpawnIndex is already spawned.
-- If yes, ensure that the aircraft is spawned at the same aircraft spot.
local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex )
--self:F( { GroupAlive = GroupAlive } )
-- Debug output
--self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } )
-- Template group, unit and its attributes. -- Template group, unit and its attributes.
local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) local group = GROUP:FindByName( self.SpawnTemplatePrefix )
local TemplateUnit = TemplateGroup:GetUnit( 1 ) local unit = group:GetUnit( 1 )
-- General category of spawned group. -- General category of spawned group.
local group = TemplateGroup
local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" ) local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" )
local isawacs = group:HasAttribute( "AWACS" ) local isawacs = group:HasAttribute( "AWACS" )
local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" )) local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" ))
local isbomber = group:HasAttribute( "Strategic bombers" ) local isbomber = group:HasAttribute( "Strategic bombers" )
local istanker = group:HasAttribute( "Tankers" ) local istanker = group:HasAttribute( "Tankers" )
local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) local ishelo = unit:HasAttribute( "Helicopters" )
-- Number of units in the group. With grouping this can actually differ from the template group size! -- Number of units in the group. With grouping this can actually differ from the template group size!
local nunits = #SpawnTemplate.units local nunits = #SpawnTemplate.units
@@ -2093,40 +2081,32 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory()
--self:F( { AirbaseCategory = AirbaseCategory } ) --self:F( { AirbaseCategory = AirbaseCategory } )
-- Set airdromeId. -- Set airdrome ID. For helipads and ships we need to add the helipad ID and linked unit.
-- Note, it is important not to set the airdrome ID for at least ships, because spawn will happen at origin of the map
if AirbaseCategory == Airbase.Category.SHIP then if AirbaseCategory == Airbase.Category.SHIP then
SpawnPoint.linkUnit = AirbaseID SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.HELIPAD then elseif AirbaseCategory == Airbase.Category.HELIPAD then
SpawnPoint.linkUnit = AirbaseID SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then else
SpawnPoint.airdromeId = AirbaseID SpawnPoint.airdromeId = AirbaseID
end end
-- Set waypoint type/action. -- Set waypoint type/action.
SpawnPoint.alt = 0 SpawnPoint.alt = 0
SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Check if we spawn on ground. -- Check if we spawn on ground.
local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) local spawnonground = not (Takeoff == SPAWN.Takeoff.Air)
--self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } )
-- Check where we actually spawn if we spawn on ground. -- Check where we actually spawn if we spawn on ground.
local spawnonship = false local autoparking=false
local spawnonfarp = false if SpawnAirbase.isAirdrome then
local spawnonrunway = false autoparking=false
local spawnonairport = false else
if spawnonground then autoparking=true
if AirbaseCategory == Airbase.Category.SHIP then
spawnonship = true
elseif AirbaseCategory == Airbase.Category.HELIPAD then
spawnonfarp = true
elseif AirbaseCategory == Airbase.Category.AIRDROME then
spawnonairport = true
end
spawnonrunway = Takeoff == SPAWN.Takeoff.Runway
end end
-- Array with parking spots coordinates. -- Array with parking spots coordinates.
@@ -2142,8 +2122,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
-- Set terminal type. -- Set terminal type.
local termtype = TerminalType local termtype = TerminalType
if spawnonrunway then if Takeoff==SPAWN.Takeoff.Runway then
if spawnonship then if SpawnAirbase.isShip then
-- Looks like there are no runway spawn spots on the stennis! -- Looks like there are no runway spawn spots on the stennis!
if ishelo then if ishelo then
termtype = AIRBASE.TerminalType.HelicopterUsable termtype = AIRBASE.TerminalType.HelicopterUsable
@@ -2163,34 +2143,31 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local verysafe = false local verysafe = false
-- Number of free parking spots at the airbase. -- Number of free parking spots at the airbase.
if spawnonship or spawnonfarp or spawnonrunway then if autoparking then
-- These places work procedural and have some kind of build in queue ==> Less effort. -- These places work procedural and have some kind of build in queue ==> Less effort.
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
--[[
elseif Parkingdata~=nil then elseif Parkingdata~=nil then
-- Parking data explicitly set by user as input parameter. -- Parking data explicitly set by user as input parameter. (This was commented out for some unknown reason. But I need it this way.)
nfree=#Parkingdata nfree=#Parkingdata
spots=Parkingdata spots=Parkingdata
]]
else else
if ishelo then if ishelo then
if termtype == nil then if termtype == nil then
-- Helo is spawned. Try exclusive helo spots first. -- Helo is spawned. Try exclusive helo spots first.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
if nfree < nunits then if nfree < nunits then
-- Not enough helo ports. Let's try also other terminal types. -- Not enough helo ports. Let's try also other terminal types.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
end end
else else
-- No terminal type specified. We try all spots except shelters. -- No terminal type specified. We try all spots except shelters.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
end end
else else
@@ -2199,44 +2176,33 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if isbomber or istransport or istanker or isawacs then if isbomber or istransport or istanker or isawacs then
-- First we fill the potentially bigger spots. -- First we fill the potentially bigger spots.
--self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
if nfree < nunits then if nfree < nunits then
-- Now we try the smaller ones. -- Now we try the smaller ones.
--self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
end end
else else
--self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
end end
else else
-- Terminal type explicitly given. -- Terminal type explicitly given.
--self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
nfree = #spots nfree = #spots
end end
end end
end end
-- Debug: Get parking data.
--[[
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
--self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
for _,_spot in pairs(parkingdata) do
--self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d",
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
end
--self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
]]
-- Set this to true if not enough spots are available for emergency air start. -- Set this to true if not enough spots are available for emergency air start.
local _notenough = false local _notenough = false
-- Need to differentiate some cases again. -- Need to differentiate some cases again.
if spawnonship or spawnonfarp or spawnonrunway then if autoparking then
-- On free spot required in these cases. -- On free spot required in these cases.
if nfree >= 1 then if nfree >= 1 then
@@ -2254,7 +2220,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
_notenough = true _notenough = true
end end
elseif spawnonairport then else
if nfree >= nunits then if nfree >= nunits then
@@ -2276,13 +2242,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
-- Not enough parking spots at the airport ==> Spawn in air. -- Not enough parking spots at the airport ==> Spawn in air.
spawnonground = false autoparking=false
spawnonship = false
spawnonfarp = false
spawnonrunway = false
-- Set waypoint type/action to turning point. -- Set waypoint type/action to turning point.
SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point
SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point
-- Adjust altitude to be 500-1000 m above the airbase. -- Adjust altitude to be 500-1000 m above the airbase.
@@ -2324,7 +2287,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
SpawnTemplate.parked = true SpawnTemplate.parked = true
for UnitID = 1, nunits do for UnitID = 1, nunits do
--self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y )
-- Template of the current unit. -- Template of the current unit.
local UnitTemplate = SpawnTemplate.units[UnitID] local UnitTemplate = SpawnTemplate.units[UnitID]
@@ -2340,9 +2302,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if spawnonground then if spawnonground then
-- Ships and FARPS seem to have a build in queue. -- Ships and FARPS seem to have a build in queue.
if spawnonship or spawnonfarp or spawnonrunway then if autoparking then
--self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
-- Spawn on ship. We take only the position of the ship. -- Spawn on ship. We take only the position of the ship.
SpawnTemplate.units[UnitID].x = PointVec3.x -- TX SpawnTemplate.units[UnitID].x = PointVec3.x -- TX
@@ -2351,20 +2311,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
else else
--self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) )
-- Get coordinates of parking spot. -- Get coordinates of parking spot.
SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y
-- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID]))
end end
else else
--self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
-- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set.
SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].y = TY
@@ -2378,11 +2333,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if parkingindex[UnitID] then if parkingindex[UnitID] then
UnitTemplate.parking = parkingindex[UnitID] UnitTemplate.parking = parkingindex[UnitID]
end end
-- Debug output.
--self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) )
--self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) )
--self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y )
end end
end end
@@ -2402,14 +2352,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
-- When spawned in the air, we need to generate a Takeoff Event. -- When spawned in the air, we need to generate a Takeoff Event.
if Takeoff == GROUP.Takeoff.Air then if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --No need to create a new SCHEDULER instance every time!
self:ScheduleOnce(5, BASE.CreateEventTakeoff, {GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject()})
end end
end end
-- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive.
if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then --if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then
SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) -- SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 )
end --end
return GroupSpawned return GroupSpawned
end end
@@ -3814,8 +3765,9 @@ end
--- Private method that randomizes the @{Core.Zone}s where the Group will be spawned. --- Private method that randomizes the @{Core.Zone}s where the Group will be spawned.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #number SpawnIndex -- @param #number SpawnIndex
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
-- @return #SPAWN self -- @return #SPAWN self
function SPAWN:_RandomizeZones( SpawnIndex ) function SPAWN:_RandomizeZones( SpawnIndex, RandomizePositionInZone)
--self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
if self.SpawnRandomizeZones then if self.SpawnRandomizeZones then
@@ -3829,7 +3781,11 @@ function SPAWN:_RandomizeZones( SpawnIndex )
--self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) --self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() )
local SpawnVec2 = SpawnZone:GetRandomVec2() local SpawnVec2 = SpawnZone:GetVec2()
if RandomizePositionInZone ~= false then
SpawnVec2 = SpawnZone:GetRandomVec2()
end
--self:T2( { SpawnVec2 = SpawnVec2 } ) --self:T2( { SpawnVec2 = SpawnVec2 } )
@@ -3956,12 +3912,17 @@ end
-- @return #number count -- @return #number count
function SPAWN:_CountAliveUnits() function SPAWN:_CountAliveUnits()
local count = 0 local count = 0
--self:I("self.SpawnAliasPrefix="..tostring(self.SpawnAliasPrefix).." | self.SpawnTemplatePrefix="..tostring(self.SpawnTemplatePrefix))
if self.SpawnAliasPrefix then if self.SpawnAliasPrefix then
if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped = string.gsub(self.SpawnAliasPrefix,"[%p%s]",".") end if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped = string.gsub(self.SpawnAliasPrefix,"[%p%s]",".") end
--self:I("self.SpawnAliasPrefixEscaped="..tostring(self.SpawnAliasPrefixEscaped))
local SpawnAliasPrefix = self.SpawnAliasPrefixEscaped local SpawnAliasPrefix = self.SpawnAliasPrefixEscaped
local agroups = GROUP:FindAllByMatching(SpawnAliasPrefix) local agroups = GROUP:FindAllByMatching(SpawnAliasPrefix)
for _,_grp in pairs(agroups) do for _,_grp in pairs(agroups) do
--self:I("Group Name = " .. _grp:GetName())
local game = self:_GetPrefixFromGroupName(_grp.GroupName) local game = self:_GetPrefixFromGroupName(_grp.GroupName)
--self:I("Game = "..game)
--self:I("Count = ".._grp:CountAliveUnits())
if game and game == self.SpawnAliasPrefix then if game and game == self.SpawnAliasPrefix then
count = count + _grp:CountAliveUnits() count = count + _grp:CountAliveUnits()
end end
@@ -3977,15 +3938,16 @@ function SPAWN:_CountAliveUnits()
end end
end end
end end
return count self.AliveUnits = count
return self
end end
--- ---
-- @param #SPAWN self -- @param #SPAWN self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnDeadOrCrash( EventData ) function SPAWN:_OnDeadOrCrash( EventData )
--self:T2( "Dead or crash event ID "..tostring(EventData.id or 0)) --self:I( "Dead or crash event ID "..tostring(EventData.id or 0))
--self:T2( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) --self:I( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") )
--if EventData.id == EVENTS.Dead then return end --if EventData.id == EVENTS.Dead then return end
@@ -3997,11 +3959,12 @@ function SPAWN:_OnDeadOrCrash( EventData )
local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName)
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
--self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) --self:I(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0))
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then
--self:I( { "Dead event: " .. EventPrefix } ) --self:I( { "Dead event: " .. EventPrefix } )
--self.AliveUnits = self.AliveUnits - 1 --self.AliveUnits = self.AliveUnits - 1
self.AliveUnits = self:_CountAliveUnits() self:ScheduleOnce(1,self._CountAliveUnits,self)
--self.AliveUnits = self:_CountAliveUnits()
--self:I( "New Alive Units: " .. self.AliveUnits ) --self:I( "New Alive Units: " .. self.AliveUnits )
end end
end end
@@ -4049,7 +4012,7 @@ function SPAWN:_OnLand( EventData )
-- self:ReSpawn( SpawnGroupIndex ) -- self:ReSpawn( SpawnGroupIndex )
-- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076
-- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events.
SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, self.RepeatOnLandingTime or 3 )
end end
end end
end end

View File

@@ -535,12 +535,6 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
-- Name of the spawned static. -- Name of the spawned static.
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) 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. -- Add static to the game.
local Static=nil --DCS#StaticObject local Static=nil --DCS#StaticObject
@@ -577,12 +571,28 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
self:T("Spawning Static") self:T("Spawning Static")
self:T2({Template=Template}) self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID, 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 end
-- Add and register the new static.
local mystatic=_DATABASE:AddStatic(Template.name)
-- If there is a SpawnFunction hook defined, call it. -- If there is a SpawnFunction hook defined, call it.
if self.SpawnFunctionHook then if self.SpawnFunctionHook then
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. -- 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 end
return mystatic return mystatic

View File

@@ -605,10 +605,13 @@ function ZONE_BASE:Trigger(Objects)
self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning")
self:AddTransition("*","EnteredZone","*") self:AddTransition("*","EnteredZone","*")
self:AddTransition("*","LeftZone","*") self:AddTransition("*","LeftZone","*")
self:AddTransition("*","ZoneEmpty","*")
self:AddTransition("*","ObjectDead","*")
self:AddTransition("*","TriggerRunCheck","*") self:AddTransition("*","TriggerRunCheck","*")
self:AddTransition("*","TriggerStop","TriggerStopped") self:AddTransition("*","TriggerStop","TriggerStopped")
self:TriggerStart() self:TriggerStart()
self.checkobjects = Objects self.checkobjects = Objects
self.ObjectsInZone = false
if UTILS.IsInstanceOf(Objects,"SET_BASE") then if UTILS.IsInstanceOf(Objects,"SET_BASE") then
self.objectset = Objects.Set self.objectset = Objects.Set
else else
@@ -646,6 +649,22 @@ function ZONE_BASE:Trigger(Objects)
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable leaving the zone. -- @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 end
--- (Internal) Check the assigned objects for being in/out of the zone --- (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 -- just earmark everyone in/out
for _,_object in pairs(objectset) do for _,_object in pairs(objectset) do
local obj = _object -- Wrapper.Controllable#CONTROLLABLE 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 if obj and obj:IsAlive() and self:IsCoordinateInZone(obj:GetCoordinate()) then
obj.TriggerInZone[self.ZoneName] = true obj.TriggerInZone[self.ZoneName] = true
self.ObjectsInZone = true
else else
obj.TriggerInZone[self.ZoneName] = false obj.TriggerInZone[self.ZoneName] = false
end end
@@ -669,6 +692,7 @@ function ZONE_BASE:_TriggerCheck(fromstart)
end end
else else
-- Check for changes -- Check for changes
local objcount = 0
for _,_object in pairs(objectset) do for _,_object in pairs(objectset) do
local obj = _object -- Wrapper.Controllable#CONTROLLABLE local obj = _object -- Wrapper.Controllable#CONTROLLABLE
if obj and obj:IsAlive() then if obj and obj:IsAlive() then
@@ -683,11 +707,20 @@ function ZONE_BASE:_TriggerCheck(fromstart)
-- is obj in zone? -- is obj in zone?
local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) local inzone = self:IsCoordinateInZone(obj:GetCoordinate())
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) --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 if inzone and not obj.TriggerInZone[self.ZoneName] then
-- wasn't in zone before -- wasn't in zone before
--self:I("Newly entered") --self:I("Newly entered")
self:__EnteredZone(0.5,obj) self:__EnteredZone(0.5,obj)
obj.TriggerInZone[self.ZoneName] = true obj.TriggerInZone[self.ZoneName] = true
objcount = objcount + 1
self.ObjectsInZone = true
obj.TriggerZoneDeadNotification = false
elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then
-- has left the zone -- has left the zone
--self:I("Newly left") --self:I("Newly left")
@@ -696,8 +729,21 @@ function ZONE_BASE:_TriggerCheck(fromstart)
else else
--self:I("Not left or not entered, or something went wrong!") --self:I("Not left or not entered, or something went wrong!")
end end
else
-- object dead
if not obj.TriggerZoneDeadNotification == true then
obj.TriggerInZone = nil
self:__ObjectDead(0.5,obj)
obj.TriggerZoneDeadNotification = true
end
end 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 end
return self return self
end end
@@ -1415,7 +1461,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories )
id = world.VolumeType.SPHERE, id = world.VolumeType.SPHERE,
params = { params = {
point = ZoneCoord:GetVec3(), point = ZoneCoord:GetVec3(),
radius = ZoneRadius / 2, radius = ZoneRadius,
} }
} }
@@ -2046,7 +2092,7 @@ function _ZONE_TRIANGLE:New(p1, p2, p3)
end end
self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5
return self return self
end end
@@ -2054,7 +2100,7 @@ end
-- @param #_ZONE_TRIANGLE self -- @param #_ZONE_TRIANGLE self
-- @param #table pt The point to check -- @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 -- @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) function _ZONE_TRIANGLE:ContainsPoint(pt, points)
points = points or self.Points points = points or self.Points
@@ -2563,7 +2609,7 @@ function ZONE_POLYGON_BASE:ReFill(Color,Alpha)
self.FillTriangles = {} self.FillTriangles = {}
end end
-- refill -- refill
for _, triangle in pairs(self._Triangles) do for _,triangle in pairs(self._Triangles) do
local draw_ids = triangle:Fill(coalition,color,alpha,nil) local draw_ids = triangle:Fill(coalition,color,alpha,nil)
self.FillTriangles = draw_ids self.FillTriangles = draw_ids
table.combine(self.DrawID, draw_ids) table.combine(self.DrawID, draw_ids)
@@ -3536,7 +3582,37 @@ do -- ZONE_ELASTIC
return self return self
end 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. --- Add a vertex (point) to the polygon.
-- @param #ZONE_ELASTIC self -- @param #ZONE_ELASTIC self
@@ -3574,7 +3650,7 @@ do -- ZONE_ELASTIC
-- Debug info. -- 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. -- Copy all points.
local points=UTILS.DeepCopy(self.points or {}) local points=UTILS.DeepCopy(self.points or {})
@@ -3592,6 +3668,9 @@ do -- ZONE_ELASTIC
-- Update polygon verticies from points. -- Update polygon verticies from points.
self._.Polygon=self:_ConvexHull(points) self._.Polygon=self:_ConvexHull(points)
self._Triangles = self:_Triangulate()
self.SurfaceArea = self:_CalculateSurfaceArea()
if Draw~=false then if Draw~=false then
if self.DrawID or Draw==true then if self.DrawID or Draw==true then
@@ -3790,7 +3869,7 @@ end
--- Checks if a point is contained within the oval. --- Checks if a point is contained within the oval.
-- @param #ZONE_OVAL self -- @param #ZONE_OVAL self
-- @param #table point The point to check -- @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) function ZONE_OVAL:IsVec2InZone(vec2)
local cos, sin = math.cos, math.sin local cos, sin = math.cos, math.sin
local dx = vec2.x - self.CenterVec2.x local dx = vec2.x - self.CenterVec2.x

View File

@@ -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.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.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.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. --- The world singleton contains functions centered around two different but extremely useful functions.
-- * Events and event handlers are all governed within world. -- * Events and event handlers are all governed within world.
@@ -132,6 +133,36 @@ do -- world
-- @function [parent=#world] getAirbases -- @function [parent=#world] getAirbases
-- @param #number coalitionId The coalition side number ID. Default is all airbases are returned. -- @param #number coalitionId The coalition side number ID. Default is all airbases are returned.
-- @return #table Table of DCS airbase objects. -- @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 end -- world
@@ -407,7 +438,7 @@ do -- coalition
-- @param #table groupData Group data table. -- @param #table groupData Group data table.
-- @return DCS#Group The spawned Group object. -- @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 -- @function [parent=#coalition] addStaticObject
-- @param #number countryId Id of the country. -- @param #number countryId Id of the country.
-- @param #table groupData Group data table. -- @param #table groupData Group data table.
@@ -420,6 +451,7 @@ end -- coalition
do -- Types do -- Types
--- Descriptors.
-- @type Desc -- @type Desc
-- @field #number speedMax0 Max speed in meters/second at zero altitude. -- @field #number speedMax0 Max speed in meters/second at zero altitude.
-- @field #number massEmpty Empty mass in kg. -- @field #number massEmpty Empty mass in kg.
@@ -598,9 +630,13 @@ do -- Object
--- @function [parent=#Object] destroy --- @function [parent=#Object] destroy
-- @param #Object self -- @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 -- @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. --- Returns type name of the Object.
-- @function [parent=#Object] getTypeName -- @function [parent=#Object] getTypeName
@@ -1013,14 +1049,16 @@ do -- Spot
end -- Spot end -- Spot
do -- Controller 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. --- 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: -- This class has 2 types of functions:
-- --
-- * Tasks -- * 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 -- @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. --- Enables and disables the controller.
-- Note: Now it works only for ground / naval groups! -- Note: Now it works only for ground / naval groups!
@@ -1079,18 +1117,18 @@ do -- Controller
-- Detection -- Detection
--- Enum contains identifiers of surface types. --- Enum containing detection types.
-- @type Controller.Detection -- @type Controller.Detection
-- @field VISUAL -- @field #number VISUAL Visual detection. Numeric value 1.
-- @field OPTIC -- @field #number OPTIC Optical detection. Numeric value 2.
-- @field RADAR -- @field #number RADAR Radar detection. Numeric value 4.
-- @field IRST -- @field #number IRST Infra-red search and track detection. Numeric value 8.
-- @field RWR -- @field #number RWR Radar Warning Receiver detection. Numeric value 16.
-- @field DLINK -- @field #number DLINK Data link detection. Numeric value 32.
--- Detected target. --- Detected target.
-- @type DetectedTarget -- @type Controller.DetectedTarget
-- @field Wrapper.Object#Object object The target -- @field DCS#Object object The target
-- @field #boolean visible The target is visible -- @field #boolean visible The target is visible
-- @field #boolean type The target type is known -- @field #boolean type The target type is known
-- @field #boolean distance Distance to the target 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 -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN
-- @return #boolean detected True if the target is detected. -- @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 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 #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 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. -- @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 do -- Unit
--- Unit.
-- @type Unit -- @type Unit
-- @extends #CoalitionObject -- @extends #CoalitionObject
-- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. -- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically.

View File

@@ -18,7 +18,7 @@
-- ### Author: FlightControl - Framework Design & Programming -- ### Author: FlightControl - Framework Design & Programming
-- ### Refactoring to use the Runway auto-detection: Applevangelist -- ### Refactoring to use the Runway auto-detection: Applevangelist
-- @date August 2022 -- @date August 2022
-- Last Update Nov 2023 -- Last Update Feb 2025
-- --
-- === -- ===
-- --
@@ -416,7 +416,7 @@ end
-- @field #ATC_GROUND_UNIVERSAL -- @field #ATC_GROUND_UNIVERSAL
ATC_GROUND_UNIVERSAL = { ATC_GROUND_UNIVERSAL = {
ClassName = "ATC_GROUND_UNIVERSAL", ClassName = "ATC_GROUND_UNIVERSAL",
Version = "0.0.1", Version = "0.0.2",
SetClient = nil, SetClient = nil,
Airbases = nil, Airbases = nil,
AirbaseList = nil, AirbaseList = nil,
@@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
self:T( { self.ClassName } ) self:T( { self.ClassName } )
self.Airbases = {} self.Airbases = {}
for _name,_ in pairs(_DATABASE.AIRBASES) do
self.Airbases[_name]={}
end
self.AirbaseList = AirbaseList self.AirbaseList = AirbaseList
if not self.AirbaseList then if not self.AirbaseList then
self.AirbaseList = {} self.AirbaseList = {}
for _name,_ in pairs(_DATABASE.AIRBASES) do for _name,_base in pairs(_DATABASE.AIRBASES) do
self.AirbaseList[_name]=_name -- 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
end end
@@ -721,14 +729,18 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
if NotInRunwayZone then if NotInRunwayZone then
local Taxi = Client:GetState( self, "Taxi" )
if IsOnGround then if IsOnGround then
local Taxi = Client:GetState( self, "Taxi" )
self:T( Taxi ) self:T( Taxi )
if Taxi == false then if Taxi == false then
local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed ) local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed )
Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " .. Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " ..
Velocity:ToString() , 20, "ATC" ) Velocity:ToString() , 20, "ATC" )
Client:SetState( self, "Taxi", true ) Client:SetState( self, "Taxi", true )
Client:SetState( self, "Speeding", false )
Client:SetState( self, "Warnings", 0 )
end end
-- TODO: GetVelocityKMH function usage -- TODO: GetVelocityKMH function usage
@@ -737,7 +749,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
local IsAboveRunway = Client:IsAboveRunway() local IsAboveRunway = Client:IsAboveRunway()
self:T( {IsAboveRunway, IsOnGround, Velocity:Get() }) self:T( {IsAboveRunway, IsOnGround, Velocity:Get() })
if IsOnGround then if IsOnGround and not Taxi then
local Speeding = false local Speeding = false
if AirbaseMeta.MaximumKickSpeed then if AirbaseMeta.MaximumKickSpeed then
if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then
@@ -749,15 +761,17 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
end end
end end
if Speeding == true then if Speeding == true then
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. --MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() ..
" has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() -- " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
Client:Destroy() --Client:Destroy()
Client:SetState( self, "Speeding", false ) Client:SetState( self, "Speeding", true )
Client:SetState( self, "Warnings", 0 ) 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
end end
if IsOnGround then if IsOnGround then
local Speeding = false local Speeding = false
@@ -1035,23 +1049,23 @@ end
-- The following airbases are monitored at the Nevada region. -- The following airbases are monitored at the Nevada region.
-- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored. -- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored.
-- --
-- * `AIRBASE.Nevada.Beatty_Airport` -- * `AIRBASE.Nevada.Beatty`
-- * `AIRBASE.Nevada.Boulder_City_Airport` -- * `AIRBASE.Nevada.Boulder_City`
-- * `AIRBASE.Nevada.Creech_AFB` -- * `AIRBASE.Nevada.Creech`
-- * `AIRBASE.Nevada.Echo_Bay` -- * `AIRBASE.Nevada.Echo_Bay`
-- * `AIRBASE.Nevada.Groom_Lake_AFB` -- * `AIRBASE.Nevada.Groom_Lake`
-- * `AIRBASE.Nevada.Henderson_Executive_Airport` -- * `AIRBASE.Nevada.Henderson_Executive`
-- * `AIRBASE.Nevada.Jean_Airport` -- * `AIRBASE.Nevada.Jean`
-- * `AIRBASE.Nevada.Laughlin_Airport` -- * `AIRBASE.Nevada.Laughlin`
-- * `AIRBASE.Nevada.Lincoln_County` -- * `AIRBASE.Nevada.Lincoln_County`
-- * `AIRBASE.Nevada.McCarran_International_Airport` -- * `AIRBASE.Nevada.McCarran_International`
-- * `AIRBASE.Nevada.Mesquite` -- * `AIRBASE.Nevada.Mesquite`
-- * `AIRBASE.Nevada.Mina_Airport` -- * `AIRBASE.Nevada.Mina`
-- * `AIRBASE.Nevada.Nellis_AFB` -- * `AIRBASE.Nevada.Nellis`
-- * `AIRBASE.Nevada.North_Las_Vegas` -- * `AIRBASE.Nevada.North_Las_Vegas`
-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip` -- * `AIRBASE.Nevada.Pahute_Mesa`
-- * `AIRBASE.Nevada.Tonopah_Airport` -- * `AIRBASE.Nevada.Tonopah`
-- * `AIRBASE.Nevada.Tonopah_Test_Range_Airfield` -- * `AIRBASE.Nevada.Tonopah_Test_Range`
-- --
-- # Installation -- # Installation
-- --
@@ -1088,10 +1102,10 @@ end
-- --
-- -- Monitor specific airbases. -- -- Monitor specific airbases.
-- ATC_Ground = ATC_GROUND_NEVADA:New( -- ATC_Ground = ATC_GROUND_NEVADA:New(
-- { AIRBASE.Nevada.Laughlin_Airport, -- { AIRBASE.Nevada.Laughlin,
-- AIRBASE.Nevada.Lincoln_County, -- AIRBASE.Nevada.Lincoln_County,
-- AIRBASE.Nevada.North_Las_Vegas, -- 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. -- The following airbases are monitored at the PersianGulf region.
-- Use the @{Wrapper.Airbase#AIRBASE.PersianGulf} enumeration to select the airbases to be monitored. -- Use the @{Wrapper.Airbase#AIRBASE.PersianGulf} enumeration to select the airbases to be monitored.
-- --
-- * `AIRBASE.PersianGulf.Abu_Musa_Island_Airport` -- * `AIRBASE.PersianGulf.Abu_Musa_Island`
-- * `AIRBASE.PersianGulf.Al_Dhafra_AB` -- * `AIRBASE.PersianGulf.Al_Dhafra_AFB`
-- * `AIRBASE.PersianGulf.Al_Maktoum_Intl` -- * `AIRBASE.PersianGulf.Al_Maktoum_Intl`
-- * `AIRBASE.PersianGulf.Al_Minhad_AB` -- * `AIRBASE.PersianGulf.Al_Minhad_AFB`
-- * `AIRBASE.PersianGulf.Bandar_Abbas_Intl` -- * `AIRBASE.PersianGulf.Bandar_Abbas_Intl`
-- * `AIRBASE.PersianGulf.Bandar_Lengeh` -- * `AIRBASE.PersianGulf.Bandar_Lengeh`
-- * `AIRBASE.PersianGulf.Dubai_Intl` -- * `AIRBASE.PersianGulf.Dubai_Intl`
-- * `AIRBASE.PersianGulf.Fujairah_Intl` -- * `AIRBASE.PersianGulf.Fujairah_Intl`
-- * `AIRBASE.PersianGulf.Havadarya` -- * `AIRBASE.PersianGulf.Havadarya`
-- * `AIRBASE.PersianGulf.Kerman_Airport` -- * `AIRBASE.PersianGulf.Kerman`
-- * `AIRBASE.PersianGulf.Khasab` -- * `AIRBASE.PersianGulf.Khasab`
-- * `AIRBASE.PersianGulf.Lar_Airbase` -- * `AIRBASE.PersianGulf.Lar`
-- * `AIRBASE.PersianGulf.Qeshm_Island` -- * `AIRBASE.PersianGulf.Qeshm_Island`
-- * `AIRBASE.PersianGulf.Sharjah_Intl` -- * `AIRBASE.PersianGulf.Sharjah_Intl`
-- * `AIRBASE.PersianGulf.Shiraz_International_Airport` -- * `AIRBASE.PersianGulf.Shiraz_Intl`
-- * `AIRBASE.PersianGulf.Sir_Abu_Nuayr` -- * `AIRBASE.PersianGulf.Sir_Abu_Nuayr`
-- * `AIRBASE.PersianGulf.Sirri_Island` -- * `AIRBASE.PersianGulf.Sirri_Island`
-- * `AIRBASE.PersianGulf.Tunb_Island_AFB` -- * `AIRBASE.PersianGulf.Tunb_Island_AFB`
-- * `AIRBASE.PersianGulf.Tunb_Kochak` -- * `AIRBASE.PersianGulf.Tunb_Kochak`
-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport` -- * `AIRBASE.PersianGulf.Sas_Al_Nakheel`
-- * `AIRBASE.PersianGulf.Bandar_e_Jask_airfield` -- * `AIRBASE.PersianGulf.Bandar_e_Jask`
-- * `AIRBASE.PersianGulf.Abu_Dhabi_International_Airport` -- * `AIRBASE.PersianGulf.Abu_Dhabi_Intl`
-- * `AIRBASE.PersianGulf.Al_Bateen_Airport` -- * `AIRBASE.PersianGulf.Al_Bateen`
-- * `AIRBASE.PersianGulf.Kish_International_Airport` -- * `AIRBASE.PersianGulf.Kish_Intl`
-- * `AIRBASE.PersianGulf.Al_Ain_International_Airport` -- * `AIRBASE.PersianGulf.Al_Ain_Intl`
-- * `AIRBASE.PersianGulf.Lavan_Island_Airport` -- * `AIRBASE.PersianGulf.Lavan_Island`
-- * `AIRBASE.PersianGulf.Jiroft_Airport` -- * `AIRBASE.PersianGulf.Jiroft`
-- --
-- # Installation -- # Installation
-- --
@@ -1391,8 +1405,8 @@ end
-- AirbasePoliceCaucasus = ATC_GROUND_PERSIANGULF:New() -- AirbasePoliceCaucasus = ATC_GROUND_PERSIANGULF:New()
-- --
-- ATC_Ground = ATC_GROUND_PERSIANGULF:New( -- ATC_Ground = ATC_GROUND_PERSIANGULF:New(
-- { AIRBASE.PersianGulf.Kerman_Airport, -- { AIRBASE.PersianGulf.Kerman,
-- AIRBASE.PersianGulf.Al_Minhad_AB -- 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 ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
end end
---
-- @type ATC_GROUND_MARIANAISLANDS -- @type ATC_GROUND_MARIANAISLANDS
-- @extends #ATC_GROUND -- @extends #ATC_GROUND
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} --- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
-- --

View File

@@ -619,63 +619,148 @@ ARTY.WeaponType={
} }
--- Database of common artillery unit properties. --- 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 -- @type ARTY.db
ARTY.db={ ARTY.db={
["2B11 mortar"] = { -- type "2B11 mortar" ["LeFH_18-40-105"] = {
minrange = 500, -- correct? displayname = "FH LeFH-18 105mm", -- name displayed in the ME
maxrange = 7000, -- 7 km minrange = 500, -- min range (green circle) in meters
reloadtime = 30, -- 30 sec maxrange = 10500, -- max range (red circle) in meters
reloadtime = nil, -- reload time in seconds
}, },
["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika" ["M2A1-105"] = {
minrange = 300, -- correct? displayname = "FH M2A1 105mm",
maxrange = 15000, -- 15 km minrange = 500,
reloadtime = nil, -- unknown maxrange = 11500,
reloadtime = nil,
}, },
["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta" ["Pak40"] = {
minrange = 300, -- correct? displayname = "FH Pak 40 75mm",
maxrange = 23500, -- 23.5 km minrange = 500,
reloadtime = nil, -- unknown 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" ["Smerch"] = {
minrange = 300, -- correct? displayname = "MLRS 9A52 Smerch CM 300mm",
maxrange = 17000, -- 17 km minrange = 20000,
reloadtime = nil, -- unknown maxrange = 70000,
reloadtime = 2160,
}, },
["SPH 2S9 Nona"] = { --type "SAU 2-C9" ["Smerch_HE"] = {
minrange = 500, -- correct? displayname = "MLRS 9A52 Smerch HE 300mm",
maxrange = 7000, -- 7 km minrange = 20000,
reloadtime = nil, -- unknown maxrange = 70000,
reloadtime = 2160,
}, },
["SPH M109 Paladin"] = { -- type "M-109", alias "M109" ["Uragan_BM-27"] = {
minrange = 300, -- correct? displayname = "MLRS 9K57 Uragan BM-27 220mm",
maxrange = 22000, -- 22 km minrange = 11500,
reloadtime = nil, -- unknown maxrange = 35800,
reloadtime = 840,
}, },
["SpGH Dana"] = { -- type "SpGH_Dana" ["Grad-URAL"] = {
minrange = 300, -- correct? displayname = "MLRS BM-21 Grad 122mm",
maxrange = 18700, -- 18.7 km minrange = 5000,
reloadtime = nil, -- unknown maxrange = 19000,
reloadtime = 420,
}, },
["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" ["HL_B8M1"] = {
minrange = 5000, -- 5 km displayname = "MLRS HL with B8M1 80mm",
maxrange = 19000, -- 19 km minrange = 500,
reloadtime = 420, -- 7 min maxrange = 5000,
reloadtime = nil,
}, },
["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" ["tt_B8M1"] = {
minrange = 11500, -- 11.5 km displayname = "MLRS LC with B8M1 80mm",
maxrange = 35800, -- 35.8 km minrange = 500,
reloadtime = 840, -- 14 min maxrange = 5000,
reloadtime = nil,
}, },
["MLRS 9A52 Smerch"] = { -- type "Smerch" ["MLRS"] = {
minrange = 20000, -- 20 km displayname = "MLRS M270 227mm",
maxrange = 70000, -- 70 km minrange = 10000,
reloadtime = 2160, -- 36 min maxrange = 32000,
reloadtime = 540,
}, },
["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" ["2B11 mortar"] = {
minrange = 10000, -- 10 km displayname = "Mortar 2B11 120mm",
maxrange = 32000, -- 32 km minrange = 500,
reloadtime = 540, -- 9 min 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. --- Target.
@@ -695,7 +780,7 @@ ARTY.db={
--- Arty script version. --- Arty script version.
-- @field #string 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. -- Maximum speed in km/h.
self.SpeedMax=group:GetSpeedMax() self.SpeedMax=group:GetSpeedMax()
-- Group is mobile or not (e.g. mortars). -- 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>1 then if self.SpeedMax>3.6 then
self.ismobile=true self.ismobile=true
else else
self.ismobile=false self.ismobile=false
@@ -1923,7 +2008,7 @@ function ARTY:onafterStart(Controllable, From, Event, To)
end end
-- Check if we have and arty type that is in the DB. -- 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}) self:T({dbproperties=_dbproperties})
if _dbproperties~=nil then if _dbproperties~=nil then
for property,value in pairs(_dbproperties) do 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("Type = %s\n", self.Type)
text=text..string.format("Display Name = %s\n", self.DisplayName) 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("Number of units = %d\n", self.IniGroupStrength)
text=text..string.format("Speed max = %d km/h\n", self.SpeedMax) text=text..string.format("Speed max = %.1f km/h\n", self.SpeedMax)
text=text..string.format("Speed default = %d km/h\n", self.Speed) 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 mobile = %s\n", tostring(self.ismobile))
text=text..string.format("Is cargo = %s\n", tostring(self.iscargo)) text=text..string.format("Is cargo = %s\n", tostring(self.iscargo))
text=text..string.format("Min range = %.1f km\n", self.minrange/1000) 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 nfire=Narty
local _type="shots" local _type="shots"
if target.weapontype==ARTY.WeaponType.Auto then if target.weapontype==ARTY.WeaponType.Auto then
nfire=Narty nfire=Nammo -- We take everything that is available
_type="shots" _type="shots"
elseif target.weapontype==ARTY.WeaponType.Cannon then elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Narty nfire=Narty
@@ -3070,6 +3155,8 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
nfire=Nmissiles nfire=Nmissiles
_type="cruise missiles" _type="cruise missiles"
end 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. -- Adjust if less than requested ammo is left.
target.nshells=math.min(target.nshells, nfire) target.nshells=math.min(target.nshells, nfire)

View File

@@ -74,7 +74,7 @@
-- @image Designation.JPG -- @image Designation.JPG
-- --
-- Date: 24 Oct 2021 -- Date: 24 Oct 2021
-- Last Update: May 2024 -- Last Update: Mar 2025
-- --
--- Class AUTOLASE --- Class AUTOLASE
-- @type AUTOLASE -- @type AUTOLASE
@@ -89,6 +89,10 @@
-- @field #table playermenus -- @field #table playermenus
-- @field #boolean smokemenu -- @field #boolean smokemenu
-- @field #boolean threatmenu -- @field #boolean threatmenu
-- @field #number RoundingPrecision
-- @field #table smokeoffset
-- @field #boolean increasegroundawareness
-- @field #number MonitorFrequency
-- @extends Ops.Intel#INTEL -- @extends Ops.Intel#INTEL
--- ---
@@ -100,6 +104,9 @@ AUTOLASE = {
alias = "", alias = "",
debug = false, debug = false,
smokemenu = true, smokemenu = true,
RoundingPrecision = 0,
increasegroundawareness = true,
MonitorFrequency = 30,
} }
--- Laser spot info --- Laser spot info
@@ -118,7 +125,7 @@ AUTOLASE = {
--- AUTOLASE class version. --- AUTOLASE class version.
-- @field #string version -- @field #string version
AUTOLASE.version = "0.1.25" AUTOLASE.version = "0.1.31"
------------------------------------------------------------------- -------------------------------------------------------------------
-- Begin Functional.Autolase.lua -- Begin Functional.Autolase.lua
@@ -191,6 +198,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
self.reporttimelong = 30 self.reporttimelong = 30
self.smoketargets = false self.smoketargets = false
self.smokecolor = SMOKECOLOR.Red self.smokecolor = SMOKECOLOR.Red
self.smokeoffset = nil
self.notifypilots = true self.notifypilots = true
self.targetsperrecce = {} self.targetsperrecce = {}
self.RecceUnits = {} self.RecceUnits = {}
@@ -207,6 +215,11 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
self.playermenus = {} self.playermenus = {}
self.smokemenu = true self.smokemenu = true
self.threatmenu = 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. -- 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") 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 -- 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. --- [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 #AUTOLASE self
-- @param #list<#number> LaserCodes -- @param #list<#number> LaserCodes
-- @return #AUTOLASE -- @return #AUTOLASE self
function AUTOLASE:SetLaserCodes( LaserCodes ) function AUTOLASE:SetLaserCodes( LaserCodes )
self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes } self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes }
return self return self
end 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. --- (Internal) Function to set pilot menu.
-- @param #AUTOLASE self -- @param #AUTOLASE self
-- @return #AUTOLASE self -- @return #AUTOLASE self
@@ -600,11 +638,26 @@ function AUTOLASE:SetSmokeTargets(OnOff,Color)
return self return self
end 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. --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default.
-- @param #AUTOLASE self -- @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 -- @return #AUTOLASE self
function AUTOLASE:EnableSmokeMenu() function AUTOLASE:EnableSmokeMenu(Offset)
self.smokemenu = true 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 return self
end end
@@ -613,6 +666,7 @@ end
-- @return #AUTOLASE self -- @return #AUTOLASE self
function AUTOLASE:DisableSmokeMenu() function AUTOLASE:DisableSmokeMenu()
self.smokemenu = false self.smokemenu = false
self.smokeoffset = nil
return self return self
end end
@@ -671,7 +725,8 @@ function AUTOLASE:CleanCurrentLasing()
local unit = recce:GetUnit(1) local unit = recce:GetUnit(1)
local name = unit:GetName() local name = unit:GetName()
if not self.RecceUnits[name] then 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 end
end end
@@ -757,9 +812,11 @@ function AUTOLASE:ShowStatus(Group,Unit)
end end
local code = self:GetLaserCode(unit:GetName()) local code = self:GetLaserCode(unit:GetName())
report:Add(string.format("Recce %s has code %d",name,code)) report:Add(string.format("Recce %s has code %d",name,code))
report:Add("---------------")
end end
end end
report:Add(string.format("Lasing min threat level %d",self.minthreatlevel)) report:Add(string.format("Lasing min threat level %d",self.minthreatlevel))
report:Add("---------------")
local lines = 0 local lines = 0
for _ind,_entry in pairs(self.CurrentLasing) do for _ind,_entry in pairs(self.CurrentLasing) do
local entry = _entry -- #AUTOLASE.LaserSpot local entry = _entry -- #AUTOLASE.LaserSpot
@@ -779,22 +836,28 @@ function AUTOLASE:ShowStatus(Group,Unit)
if playername then if playername then
local settings = _DATABASE:GetPlayerSettings(playername) local settings = _DATABASE:GetPlayerSettings(playername)
if settings then if settings then
self:I("Get Settings ok!") self:T("Get Settings ok!")
if settings:IsA2G_MGRS() then if settings:IsA2G_MGRS() then
locationstring = entry.coordinate:ToStringMGRS(settings) locationstring = entry.coordinate:ToStringMGRS(settings)
elseif settings:IsA2G_LL_DMS() then elseif settings:IsA2G_LL_DMS() then
locationstring = entry.coordinate:ToStringLLDMS(settings) locationstring = entry.coordinate:ToStringLLDMS(settings)
elseif settings:IsA2G_LL_DDM() then
locationstring = entry.coordinate:ToStringLLDDM(settings)
elseif settings:IsA2G_BR() then 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 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(text)
report:Add("---------------")
lines = lines + 1 lines = lines + 1
end end
if lines == 0 then if lines == 0 then
report:Add("No targets!") report:Add("No targets!")
report:Add("---------------")
end end
local reporttime = self.reporttimelong local reporttime = self.reporttimelong
if lines == 0 then reporttime = self.reporttimeshort end if lines == 0 then reporttime = self.reporttimeshort end
@@ -913,6 +976,65 @@ function AUTOLASE:CanLase(Recce,Unit)
return canlase return canlase
end 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 -- FSM Functions
------------------------------------------------------------------- -------------------------------------------------------------------
@@ -925,6 +1047,9 @@ end
-- @return #AUTOLASE self -- @return #AUTOLASE self
function AUTOLASE:onbeforeMonitor(From, Event, To) function AUTOLASE:onbeforeMonitor(From, Event, To)
self:T({From, Event, To}) self:T({From, Event, To})
if self.increasegroundawareness then
self:_Prescient()
end
-- Check if group has detected any units. -- Check if group has detected any units.
self:UpdateIntel() self:UpdateIntel()
return self return self
@@ -953,7 +1078,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local grp = contact.group local grp = contact.group
local coord = contact.position local coord = contact.position
local reccename = contact.recce or "none" local reccename = contact.recce or "none"
local threat = contact.threatlevel or 0 local threat = contact.threatlevel or 0
local reccegrp = UNIT:FindByName(reccename) local reccegrp = UNIT:FindByName(reccename)
if reccegrp then if reccegrp then
local reccecoord = reccegrp:GetCoordinate() local reccecoord = reccegrp:GetCoordinate()
@@ -1077,6 +1202,9 @@ function AUTOLASE:onafterMonitor(From, Event, To)
} }
if self.smoketargets then if self.smoketargets then
local coord = unit:GetCoordinate() local coord = unit:GetCoordinate()
if self.smokeoffset then
coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true)
end
local color = self:GetSmokeColor(reccename) local color = self:GetSmokeColor(reccename)
coord:Smoke(color) coord:Smoke(color)
end end
@@ -1087,7 +1215,8 @@ function AUTOLASE:onafterMonitor(From, Event, To)
end end
end end
self:__Monitor(-30) local nextloop = -self.MonitorFrequency or -30
self:__Monitor(nextloop)
return self return self
end end

View File

@@ -357,7 +357,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
self:F({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.CleanUpList[Event.IniDCSUnitName] == nil then
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName ) self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName )
@@ -365,7 +365,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
end end
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.CleanUpList[Event.TgtDCSUnitName] == nil then
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName ) self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName )
@@ -387,7 +387,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
local CleanUpGroupName = CleanUpListData.CleanUpGroupName local CleanUpGroupName = CleanUpListData.CleanUpGroupName
if CleanUpUnit:IsAlive() ~= nil then if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
@@ -414,7 +414,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
end end
end end
-- Clean Units which are waiting for a very long time in the CleanUpZone. -- 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() local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH()
if CleanUpUnitVelocity < 1 then if CleanUpUnitVelocity < 1 then
if CleanUpListData.CleanUpMoved then if CleanUpListData.CleanUpMoved then

View File

@@ -652,7 +652,7 @@ do -- DETECTION_BASE
if DetectedObject:isExist() then 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, DetectedObject,
self.DetectVisual, self.DetectVisual,
self.DetectOptical, self.DetectOptical,

View File

@@ -1154,8 +1154,6 @@ function ESCORT:_ReportTargetsScheduler()
if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then
if true then
local EscortGroupName = self.EscortGroup:GetName() local EscortGroupName = self.EscortGroup:GetName()
self.EscortMenuAttackNearbyTargets:RemoveSubMenus() self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
@@ -1226,177 +1224,6 @@ function ESCORT:_ReportTargetsScheduler()
end end
return true 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 end
return false return false

View File

@@ -1060,7 +1060,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Tracking info and init of last bomb position. -- 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) 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) MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Loop over players. -- Loop over players.

View File

@@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: July 2024 -- Last Update: Mar 2025
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **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 #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 #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 #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 -- @extends Core.Base#BASE
@@ -71,7 +74,7 @@
-- --
-- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * 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. -- * 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 -- * **Classic mode** behaves like before
-- * Leverage evasiveness from SEAD, leverage attack range setting -- * Leverage evasiveness from SEAD, leverage attack range setting
-- * Automatic setup of SHORAD based on groups of the class "short-range" -- * 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" -- * 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" -- * 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" -- * 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 -- 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. -- 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 -- **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 -- 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. -- 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 -- ## 1.2 SAM sites
-- --
@@ -192,26 +197,24 @@
-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- 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 -- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) -- 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 -- ### 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 -- mybluemantis.automode = false
-- --
-- -- switch off auto shorad **before** you start MANTIS. -- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- mybluemantis.autoshorad = false
--
-- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- -- also see engagerange below. -- -- also see engagerange below.
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1 -- self.radiusscale[MANTIS.SamType.LONG] = 1.1
-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 -- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 -- self.radiusscale[MANTIS.SamType.SHORT] = 1.3
-- self.radiusscale[MANTIS.SamType.POINT] = 1.4
-- --
-- ### 2.1.5 Friendlies check in firing range -- ### 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. -- 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: -- 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() -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
@@ -296,6 +299,7 @@ MANTIS = {
SAM_Table_Long = {}, SAM_Table_Long = {},
SAM_Table_Medium = {}, SAM_Table_Medium = {},
SAM_Table_Short = {}, SAM_Table_Short = {},
SAM_Table_PointDef = {},
lid = "", lid = "",
Detection = nil, Detection = nil,
AWACS_Detection = nil, AWACS_Detection = nil,
@@ -329,6 +333,9 @@ MANTIS = {
autoshorad = true, autoshorad = true,
ShoradGroupSet = nil, ShoradGroupSet = nil,
checkforfriendlies = false, checkforfriendlies = false,
SmokeDecoy = false,
SmokeDecoyColor = SMOKECOLOR.White,
checkcounter = 1,
} }
--- Advanced state enumerator --- Advanced state enumerator
@@ -345,8 +352,17 @@ MANTIS.SamType = {
SHORT = "Short", SHORT = "Short",
MEDIUM = "Medium", MEDIUM = "Medium",
LONG = "Long", 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 --- SAM data
-- @type MANTIS.SamData -- @type MANTIS.SamData
-- @field #number Range Max firing range in km -- @field #number Range Max firing range in km
@@ -354,6 +370,7 @@ MANTIS.SamType = {
-- @field #number Height Max firing height in km -- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @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 Radar Radar typename on unit level (used as key)
-- @field #string Point Point defense capable
MANTIS.SamData = { MANTIS.SamData = {
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["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 ["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-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-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" }, ["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" }, ["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-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-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-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" },
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, ["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" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, ["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" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
-- units from HDS Mod, multi launcher options is tricky -- units from HDS Mod, multi launcher options is tricky
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, ["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-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"}, ["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" }, ["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" }, ["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" }, ["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 #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @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 Radar Radar typename on unit level (used as key)
-- @field #string Point Point defense capable
MANTIS.SamDataHDS = { MANTIS.SamDataHDS = {
-- units from HDS Mod, multi launcher options is tricky -- units from HDS Mod, multi launcher options is tricky
-- group name MUST contain HDS to ID launcher type correctly! -- 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 #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @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 Radar Radar typename on unit level (used as key)
-- @field #string Point Point defense capable
MANTIS.SamDataSMA = { MANTIS.SamDataSMA = {
-- units from SMA Mod (Sweedish Military Assets) -- units from SMA Mod (Sweedish Military Assets)
-- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/ -- 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" }, ["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" }, ["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" }, ["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 --- SAM data CH
@@ -438,28 +456,52 @@ MANTIS.SamDataSMA = {
-- @field #number Height Max firing height in km -- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @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 Radar Radar typename on unit level (used as key)
-- @field #string Point Point defense capable
MANTIS.SamDataCH = { MANTIS.SamDataCH = {
-- units from CH (Military Assets by Currenthill) -- units from CH (Military Assets by Currenthill)
-- https://www.currenthill.com/ -- https://www.currenthill.com/
-- group name MUST contain CHM to ID launcher type correctly! -- group name MUST contain CHM to ID launcher type correctly!
["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" }, ["2S38 CHM"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" },
["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PantsirS2 CHM"] = { 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" }, ["PGL-625 CHM"] = { 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" }, ["HQ-17A CHM"] = { 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" }, ["M903PAC2 CHM"] = { 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" }, ["M903PAC3 CHM"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, ["TorM2M CHM"] = { 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-AMRAAMER CHM"] = { 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" }, ["NASAMS3-AIM9X2 CHM"] = { 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" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" },
["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, ["S350-9M100 CHM"] = { 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" }, ["S350-9M96D CHM"] = { 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" }, ["LAV-AD CHM"] = { 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" }, ["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_Long = {}
self.SAM_Table_Medium = {} self.SAM_Table_Medium = {}
self.SAM_Table_Short = {} self.SAM_Table_Short = {}
self.SAM_Table_PointDef = {}
self.dynamic = dynamic or false self.dynamic = dynamic or false
self.checkradius = 25000 self.checkradius = 25000
self.grouping = 5000 self.grouping = 5000
@@ -548,10 +591,6 @@ do
self.SuppressedGroups = {} self.SuppressedGroups = {}
-- 0.8 additions -- 0.8 additions
self.automode = true 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.SAMCheckRanges = {}
self.usezones = false self.usezones = false
self.AcceptZones = {} self.AcceptZones = {}
@@ -560,6 +599,7 @@ do
self.maxlongrange = 1 self.maxlongrange = 1
self.maxmidrange = 2 self.maxmidrange = 2
self.maxshortrange = 2 self.maxshortrange = 2
self.maxpointdefrange = 6
self.maxclassic = 6 self.maxclassic = 6
self.autoshorad = true self.autoshorad = true
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
@@ -567,7 +607,10 @@ do
self.SkateZones = nil self.SkateZones = nil
self.SkateNumber = 3 self.SkateNumber = 3
self.shootandscoot = false self.shootandscoot = false
self.SmokeDecoy = false
self.SmokeDecoyColor = SMOKECOLOR.White
self.UseEmOnOff = true self.UseEmOnOff = true
if EmOnOff == false then if EmOnOff == false then
@@ -579,6 +622,7 @@ do
else else
self.advAwacs = false self.advAwacs = false
end end
-- Set the string id for output to DCS.log file. -- Set the string id for output to DCS.log file.
self.lid=string.format("MANTIS %s | ", self.name) self.lid=string.format("MANTIS %s | ", self.name)
@@ -638,9 +682,12 @@ do
self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC)
end end
-- counter for SAM table updates
self.checkcounter = 1
-- TODO Version -- TODO Version
-- @field #string version -- @field #string version
self.version="0.8.18" self.version="0.9.27"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@@ -837,7 +884,7 @@ do
self.AcceptZones = AcceptZones or {} self.AcceptZones = AcceptZones or {}
self.RejectZones = RejectZones or {} self.RejectZones = RejectZones or {}
self.ConflictZones = ConflictZones 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 self.usezones = true
end end
return self return self
@@ -876,19 +923,31 @@ do
return self return self
end 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 --- Function to set number of SAMs going active on a valid, detected thread
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number Short Number of short-range systems activated, defaults to 1. -- @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 Mid Number of mid-range systems activated, defaults to 2.
-- @param #number Long Number of long-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 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 -- @return #MANTIS self
function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point)
self:T(self.lid .. "SetMaxActiveSAMs") self:T(self.lid .. "SetMaxActiveSAMs")
self.maxclassic = Classic or 6 self.maxclassic = Classic or 6
self.maxlongrange = Long or 1 self.maxlongrange = Long or 1
self.maxmidrange = Mid or 2 self.maxmidrange = Mid or 2
self.maxshortrange = Short or 2 self.maxshortrange = Short or 2
self.maxpointdefrange= Point or 6
return self return self
end end
@@ -1090,6 +1149,24 @@ do
end end
return self return self
end 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 --- [Internal] Function to determine state of the advanced mode
-- @param #MANTIS self -- @param #MANTIS self
@@ -1264,9 +1341,9 @@ do
-- DEBUG -- DEBUG
set = self:_PreFilterHeight(height) set = self:_PreFilterHeight(height)
end end
local friendlyset -- Core.Set#SET_GROUP --self.friendlyset -- Core.Set#SET_GROUP
if self.checkforfriendlies == true then if self.checkforfriendlies == true and self.friendlyset == nil 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 = 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 end
for _,_coord in pairs (set) do for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check local coord = _coord -- get current coord to check
@@ -1282,20 +1359,21 @@ do
zonecheck = self:_CheckCoordinateInZones(coord) zonecheck = self:_CheckCoordinateInZones(coord)
end end
if self.verbose and self.debug then if self.verbose and self.debug then
local dectstring = coord:ToStringLLDMS() --local dectstring = coord:ToStringLLDMS()
local samstring = samcoordinate:ToStringLLDMS() local samstring = samcoordinate:ToStringMGRS({MGRS_Accuracy=0})
samstring = string.gsub(samstring,"%s","")
local inrange = "false" local inrange = "false"
if targetdistance <= rad then if targetdistance <= rad then
inrange = "true" inrange = "true"
end 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) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text) self:T(self.lid..text)
end end
-- friendlies around? -- friendlies around?
local nofriendlies = true local nofriendlies = true
if self.checkforfriendlies == true then 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 if closestfriend and distance and distance < rad then
nofriendlies = false nofriendlies = false
end end
@@ -1396,7 +1474,7 @@ do
-- @return #string type Long, medium or short range -- @return #string type Long, medium or short range
-- @return #number blind "blind" spot -- @return #number blind "blind" spot
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm) function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm)
self:T(self.lid.."_GetSAMRangeFromUnits") self:T(self.lid.."_GetSAMDataFromUnits")
local found = false local found = false
local range = self.checkradius local range = self.checkradius
local height = 3000 local height = 3000
@@ -1435,6 +1513,17 @@ do
end end
if found then break end if found then break end
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 if not found then
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end end
@@ -1449,7 +1538,7 @@ do
-- @return #string type Long, medium or short range -- @return #string type Long, medium or short range
-- @return #number blind "blind" spot -- @return #number blind "blind" spot
function MANTIS:_GetSAMRange(grpname) function MANTIS:_GetSAMRange(grpname)
self:T(self.lid.."_GetSAMRange") self:T(self.lid.."_GetSAMRange for "..tostring(grpname))
local range = self.checkradius local range = self.checkradius
local height = 3000 local height = 3000
local type = MANTIS.SamType.MEDIUM local type = MANTIS.SamType.MEDIUM
@@ -1466,9 +1555,9 @@ do
elseif string.find(grpname,"CHM",1,true) then elseif string.find(grpname,"CHM",1,true) then
CHMod = true CHMod = true
end end
if self.automode then --if self.automode then
for idx,entry in pairs(self.SamData) do for idx,entry in pairs(self.SamData) do
--self:I("ID = " .. idx) self:T2("ID = " .. idx)
if string.find(grpname,idx,1,true) then if string.find(grpname,idx,1,true) then
local _entry = entry -- #MANTIS.SamData local _entry = entry -- #MANTIS.SamData
type = _entry.Type type = _entry.Type
@@ -1476,18 +1565,32 @@ do
range = _entry.Range * 1000 * radiusscale -- max firing range range = _entry.Range * 1000 * radiusscale -- max firing range
height = _entry.Height * 1000 -- max firing height height = _entry.Height * 1000 -- max firing height
blind = _entry.Blindspot blind = _entry.Blindspot
--self:I("Matching Groupname = " .. grpname .. " Range= " .. range) self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
found = true found = true
break break
end end
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 end
-- secondary filter if not found --- Tertiary filter if not found
if (not found and self.automode) or HDSmod or SMAMod or CHMod then if (not found) or HDSmod or SMAMod or CHMod then
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
elseif not found then elseif not found then
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end 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 return range, height, type, blind
end end
@@ -1505,6 +1608,7 @@ do
local SAM_Tbl_lg = {} -- table of long range 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_md = {} -- table of mid range SAM defense zones
local SAM_Tbl_sh = {} -- table of short 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 SEAD_Grps = {} -- table of SAM names to make evasive
local engagerange = self.engagerange -- firing range in % of max local engagerange = self.engagerange -- firing range in % of max
--cycle through groups and set alarm state etc --cycle through groups and set alarm state etc
@@ -1523,23 +1627,27 @@ do
local grpname = group:GetName() local grpname = group:GetName()
local grpcoord = group:GetCoordinate() local grpcoord = group:GetCoordinate()
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) 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 ) --table.insert( SEAD_Grps, grpname )
if type == MANTIS.SamType.LONG then 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 ) 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 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 ) 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 elseif type == MANTIS.SamType.SHORT then
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type})
--self:T("SAM "..grpname.." is type SHORT") 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) self.ShoradGroupSet:Add(grpname,group)
if not self.autoshorad then if not self.autoshorad then
table.insert( SEAD_Grps, grpname ) table.insert( SEAD_Grps, grpname )
end end
end end
self.SamStateTracker[grpname] = "GREEN" self.SamStateTracker[grpname] = "GREEN"
end end
@@ -1548,6 +1656,7 @@ do
self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Long = SAM_Tbl_lg
self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Medium = SAM_Tbl_md
self.SAM_Table_Short = SAM_Tbl_sh self.SAM_Table_Short = SAM_Tbl_sh
self.SAM_Table_PointDef = SAM_Tbl_pt
-- make SAMs evasive -- make SAMs evasive
local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD
mysead:SetEngagementRange(engagerange) mysead:SetEngagementRange(engagerange)
@@ -1571,7 +1680,8 @@ do
local SAM_Tbl = {} -- table of SAM defense zones local SAM_Tbl = {} -- table of SAM defense zones
local SAM_Tbl_lg = {} -- table of long range 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_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 SEAD_Grps = {} -- table of SAM names to make evasive
local engagerange = self.engagerange -- firing range in % of max local engagerange = self.engagerange -- firing range in % of max
--cycle through groups and set alarm state etc --cycle through groups and set alarm state etc
@@ -1582,17 +1692,21 @@ do
local grpname = group:GetName() local grpname = group:GetName()
local grpcoord = group:GetCoordinate() local grpcoord = group:GetCoordinate()
local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) 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 ) table.insert( SEAD_Grps, grpname )
if type == MANTIS.SamType.LONG then if type == MANTIS.SamType.LONG and radaralive then
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
--self:I({grpname,grprange, grpheight}) self:T({grpname,grprange, grpheight})
elseif type == MANTIS.SamType.MEDIUM then elseif type == MANTIS.SamType.MEDIUM and radaralive then
table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type})
--self:I({grpname,grprange, grpheight}) self:T({grpname,grprange, grpheight})
elseif type == MANTIS.SamType.SHORT then elseif type == MANTIS.SamType.SHORT and radaralive then
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type})
-- self:I({grpname,grprange, grpheight}) 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) self.ShoradGroupSet:Add(grpname,group)
if self.autoshorad then if self.autoshorad then
self.Shorad.Groupset = self.ShoradGroupSet self.Shorad.Groupset = self.ShoradGroupSet
@@ -1604,6 +1718,7 @@ do
self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Long = SAM_Tbl_lg
self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Medium = SAM_Tbl_md
self.SAM_Table_Short = SAM_Tbl_sh self.SAM_Table_Short = SAM_Tbl_sh
self.SAM_Table_PointDef = SAM_Tbl_pt
-- make SAMs evasive -- make SAMs evasive
if self.mysead ~= nil then if self.mysead ~= nil then
local mysead = self.mysead local mysead = self.mysead
@@ -1647,20 +1762,33 @@ do
-- @param #table detset Table of COORDINATES -- @param #table detset Table of COORDINATES
-- @param #boolean dlink Using DLINK -- @param #boolean dlink Using DLINK
-- @param #number limit of SAM sites to go active on a contact -- @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) function MANTIS:_CheckLoop(samset,detset,dlink,limit)
self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates")
local switchedon = 0 local switchedon = 0
local instatusred = 0
local instatusgreen = 0
local activeshorads = 0
local SEADactive = 0
for _,_data in pairs (samset) do for _,_data in pairs (samset) do
local samcoordinate = _data[2] local samcoordinate = _data[2]
local name = _data[1] local name = _data[1]
local radius = _data[3] local radius = _data[3]
local height = _data[4] local height = _data[4]
local blind = _data[5] * 1.25 + 1 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 samgroup = GROUP:FindByName(name)
local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink)
local suppressed = self.SuppressedGroups[name] or false 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 IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD
if samgroup:IsAlive() then if samgroup:IsAlive() then
-- switch on SAM -- switch on SAM
@@ -1673,12 +1801,23 @@ do
elseif (not self.UseEmOnOff) and switchedon < limit then elseif (not self.UseEmOnOff) and switchedon < limit then
samgroup:OptionAlarmStateRed() samgroup:OptionAlarmStateRed()
switchedon = switchedon + 1 switchedon = switchedon + 1
switch = true switch = true
end end
if self.SamStateTracker[name] ~= "RED" and switch then if self.SamStateTracker[name] ~= "RED" and switch then
self:__RedState(1,samgroup) self:__RedState(1,samgroup)
self.SamStateTracker[name] = "RED" self.SamStateTracker[name] = "RED"
end 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 -- link in to SHORAD if available
-- DONE: Test integration fully -- DONE: Test integration fully
if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early 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 -- debug output
if (self.debug or self.verbose) and switch then if (self.debug or self.verbose) and switch then
local text = string.format("SAM %s in alarm state RED!", name) 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 if self.verbose then self:I(self.lid..text) end
end end
end --end alive end --end alive
@@ -1709,13 +1848,27 @@ do
end end
if self.debug or self.verbose then if self.debug or self.verbose then
local text = string.format("SAM %s in alarm state GREEN!", name) 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 if self.verbose then self:I(self.lid..text) end
end end
end --end alive end --end alive
end --end check end --end check
end --for for loop end --for loop
return self 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 end
--- [Internal] Check detection function --- [Internal] Check detection function
@@ -1728,22 +1881,38 @@ do
--get detected set --get detected set
local detset = detection:GetDetectedItemCoordinates() local detset = detection:GetDetectedItemCoordinates()
--self:T("Check:", {detset}) --self:T("Check:", {detset})
-- randomly update SAM Table -- update SAM Table evey 3 runs
local rand = math.random(1,100) if self.checkcounter%3 == 0 then
if rand > 65 then -- 1/3 of cases
self:_RefreshSAMTable() self:_RefreshSAMTable()
end 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 -- switch SAMs on/off if (n)one of the detected groups is inside their reach
if self.automode then 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 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 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 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 else
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height 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 end
return self return self
end end
@@ -1828,7 +1997,7 @@ do
end end
--]] --]]
if self.autoshorad then 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.Shorad:SetDefenseLimits(80,95)
self.ShoradLink = true self.ShoradLink = true
self.Shorad.Groupset=self.ShoradGroupSet self.Shorad.Groupset=self.ShoradGroupSet
@@ -1853,12 +2022,34 @@ do
if not self.state2flag then if not self.state2flag then
self:_Check(self.Detection,self.DLink) self:_Check(self.Detection,self.DLink)
end end
--[[ check Awacs local EWRAlive = self:_CheckAnyEWRAlive()
if self.advAwacs and not self.state2flag then
self:_Check(self.AWACS_Detection,false) 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 end
--]]
-- relocate HQ and EWR -- relocate HQ and EWR
if self.autorelocate then if self.autorelocate then
@@ -1868,8 +2059,6 @@ do
local halfintv = math.floor(timepassed / relointerval) local halfintv = math.floor(timepassed / relointerval)
--self:T({timepassed=timepassed, halfintv=halfintv})
if halfintv >= 1 then if halfintv >= 1 then
self.TimeStamp = timer.getAbsTime() self.TimeStamp = timer.getAbsTime()
self:_Relocate() self:_Relocate()
@@ -1998,7 +2187,7 @@ do
local Shorad = self.Shorad local Shorad = self.Shorad
local radius = self.checkradius local radius = self.checkradius
local ontime = self.ShoradTime local ontime = self.ShoradTime
Shorad:WakeUpShorad(Name, radius, ontime) Shorad:WakeUpShorad(Name, radius, ontime, nil, true)
self:__ShoradActivated(1,Name, radius, ontime) self:__ShoradActivated(1,Name, radius, ontime)
end end
return self return self

View File

@@ -3814,15 +3814,20 @@ function RAT:Status(message, forID)
local N0units=group:GetInitialSize() local N0units=group:GetInitialSize()
-- Monitor travelled distance since last check. -- Monitor travelled distance since last check.
local Pnow=coords local Dtravel=0
local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow) if coords and ratcraft.Pnow then
ratcraft.Pnow=Pnow local Dtravel=coords:Get2DDistance(ratcraft.Pnow)
ratcraft.Pnow=coords
end
-- Add up the travelled distance. -- Add up the travelled distance.
ratcraft.Distance=ratcraft.Distance+Dtravel ratcraft.Distance=ratcraft.Distance+Dtravel
-- Distance remaining to destination. -- 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. -- Status report.
if (forID and spawnindex==forID) or (not forID) then if (forID and spawnindex==forID) or (not forID) then

View File

@@ -107,6 +107,9 @@
-- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor. -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
-- @field #number Coalition Coalition side for the menu, if any. -- @field #number Coalition Coalition side for the menu, if any.
-- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu. -- @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 -- @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 --- *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. -- -- Create a range object.
-- GoldwaterRange=RANGE:New("Goldwater Range") -- 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. -- -- 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. -- -- 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") -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left")
@@ -358,6 +365,8 @@ RANGE = {
targetpath = nil, targetpath = nil,
targetprefix = nil, targetprefix = nil,
Coalition = nil, Coalition = nil,
ceilingaltitude = 20000,
ceilingenabled = false,
} }
--- Default range parameters. --- Default range parameters.
@@ -1085,6 +1094,37 @@ function RANGE:SetRangeZone( zone )
return self return self
end 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. --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`.
@@ -1893,7 +1933,7 @@ function RANGE:OnEventHit( EventData )
local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
-- Player has rolled in on a strafing target. -- 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 playerPos = _unit:GetCoordinate()
local targetPos = target:GetCoordinate() local targetPos = target:GetCoordinate()
@@ -2987,7 +3027,7 @@ function RANGE:_DisplayBombTargets( _unitname )
end end
end end
self:_DisplayMessageToGroup( _unit, _text, 120, true, true, _multiplayer ) self:_DisplayMessageToGroup( _unit, _text, 150, true, true, _multiplayer )
end end
end end
@@ -3112,7 +3152,10 @@ function RANGE:_CheckPlayers()
if unit and unit:IsAlive() then 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 -- -- Player INSIDE Range Zone --
@@ -3453,10 +3496,10 @@ function RANGE:_AddF10Commands( _unitName )
-- Range menu -- Range menu
local _rangePath = MENU_GROUP:New( group, self.rangename, _rootMenu ) 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 _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/ -- F10/On the Range/<Range Name>/My Settings/
local _mysmokePath = MENU_GROUP:New( group, "Smoke Color", _settingsPath ) local _mysmokePath = MENU_GROUP:New( group, "Smoke Color", _settingsPath )

View File

@@ -19,7 +19,7 @@
-- --
-- ### Authors: **applevangelist**, **FlightControl** -- ### Authors: **applevangelist**, **FlightControl**
-- --
-- Last Update: Dec 2023 -- Last Update: Dec 2024
-- --
-- === -- ===
-- --
@@ -28,6 +28,16 @@
--- ---
-- @type SEAD -- @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 -- @extends Core.Base#BASE
--- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- Make SAM sites execute evasive and defensive behaviour when being fired upon.
@@ -56,10 +66,11 @@ SEAD = {
SEADGroupPrefixes = {}, SEADGroupPrefixes = {},
SuppressedGroups = {}, SuppressedGroups = {},
EngagementRange = 75, -- default 75% engagement range Feature Request #1355 EngagementRange = 75, -- default 75% engagement range Feature Request #1355
Padding = 10, Padding = 15,
CallBack = nil, CallBack = nil,
UseCallBack = false, UseCallBack = false,
debug = false, debug = false,
WeaponTrack = false,
} }
--- Missile enumerators --- Missile enumerators
@@ -69,6 +80,7 @@ SEAD = {
["AGM_122"] = "AGM_122", ["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84", ["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45", ["AGM_45"] = "AGM_45",
["AGM_65"] = "AGM_65",
["ALARM"] = "ALARM", ["ALARM"] = "ALARM",
["LD-10"] = "LD-10", ["LD-10"] = "LD-10",
["X_58"] = "X_58", ["X_58"] = "X_58",
@@ -88,6 +100,7 @@ SEAD = {
-- km and mach -- km and mach
["AGM_88"] = { 150, 3}, ["AGM_88"] = { 150, 3},
["AGM_45"] = { 12, 2}, ["AGM_45"] = { 12, 2},
["AGM_65"] = { 16, 0.9},
["AGM_122"] = { 16.5, 2.3}, ["AGM_122"] = { 16.5, 2.3},
["AGM_84"] = { 280, 0.8}, ["AGM_84"] = { 280, 0.8},
["ALARM"] = { 45, 2}, ["ALARM"] = { 45, 2},
@@ -144,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding )
self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "ManageEvasion", "*")
self:AddTransition("*", "CalculateHitZone", "*") self:AddTransition("*", "CalculateHitZone", "*")
self:I("*** SEAD - Started Version 0.4.6") self:I("*** SEAD - Started Version 0.4.9")
return self return self
end end
@@ -371,7 +384,7 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
reach = wpndata[1] * 1.1 reach = wpndata[1] * 1.1
local mach = wpndata[2] local mach = wpndata[2]
wpnspeed = math.floor(mach * 340.29) wpnspeed = math.floor(mach * 340.29)
if Weapon then if Weapon and Weapon:GetSpeed() > 0 then
wpnspeed = Weapon:GetSpeed() wpnspeed = Weapon:GetSpeed()
self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed))
end end
@@ -452,29 +465,38 @@ end
-- @return #SEAD self -- @return #SEAD self
function SEAD:HandleEventShot( EventData ) function SEAD:HandleEventShot( EventData )
self:T( { EventData.id } ) 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) local SEADWeapon = EventData.Weapon -- Identify the weapon fired
--self:T({ SEADWeapon }) local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type
if self:_CheckHarms(SEADWeaponName) then 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' ) 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 _targetskill = "Random"
local _targetgroupname = "none" local _targetgroupname = "none"
local _target = EventData.Weapon:getTarget() -- Identify target local _target = EventData.Weapon:getTarget() -- Identify target
if not _target or self.debug then -- AGM-88 or 154 w/o target data 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")) 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 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 pos0 = SEADPlane:GetCoordinate()
local fheight = SEADPlane:GetHeight() local fheight = SEADPlane:GetHeight()
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
@@ -520,7 +542,7 @@ function SEAD:HandleEventShot( EventData )
end end
if SEADGroupFound == true then -- yes we are being attacked if SEADGroupFound == true then -- yes we are being attacked
if string.find(SEADWeaponName,"ADM_141",1,true) then 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 else
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
end end

View File

@@ -21,7 +21,7 @@
-- @image Functional.Shorad.jpg -- @image Functional.Shorad.jpg
-- --
-- Date: Nov 2021 -- Date: Nov 2021
-- Last Update: Nov 2023 -- Last Update: Jan 2025
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE --- **SHORAD** class, extends Core.Base#BASE
@@ -113,7 +113,7 @@ SHORAD = {
SkateNumber = 3, SkateNumber = 3,
SkateZones = nil, SkateZones = nil,
minscootdist = 100, minscootdist = 100,
minscootdist = 3000, maxscootdist = 3000,
scootrandomcoord = false, scootrandomcoord = false,
} }
@@ -443,7 +443,9 @@ do
for _,_groups in pairs (shoradset) do for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName() local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1, true) then if string.find(groupname, tgtgrp, 1, true) then
returnname = true if _groups:IsSAM() then
returnname = true
end
end end
end end
return returnname return returnname
@@ -470,6 +472,7 @@ do
-- @param #number Radius Radius of the #ZONE -- @param #number Radius Radius of the #ZONE
-- @param #number ActiveTimer Number of seconds to stay active -- @param #number ActiveTimer Number of seconds to stay active
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC -- @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 -- @return #SHORAD self
-- @usage Use this function to integrate with other systems, example -- @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 = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mymantis:AddShorad(myshorad,720) -- mymantis:AddShorad(myshorad,720)
-- mymantis:Start() -- 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(self.lid .. " WakeUpShorad")
self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
local targetcat = TargetCat or Object.Category.UNIT local targetcat = TargetCat or Object.Category.UNIT
@@ -521,7 +524,27 @@ do
-- go through set and find the one(s) to activate -- go through set and find the one(s) to activate
local TDiff = 4 local TDiff = 4
for _,_group in pairs (shoradset) do 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()) local text = string.format("Waking up SHORAD %s", _group:GetName())
self:T(text) self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
@@ -529,7 +552,6 @@ do
_group:EnableEmission(true) _group:EnableEmission(true)
end end
_group:OptionAlarmStateRed() _group:OptionAlarmStateRed()
local groupname = _group:GetName()
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
self.ActiveGroups[groupname] = { Timing = ActiveTimer } self.ActiveGroups[groupname] = { Timing = ActiveTimer }
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
@@ -607,7 +629,7 @@ do
_targetgroupname = tgtgrp:GetName() -- group name _targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill() _targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname) 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 end
end end
@@ -736,7 +758,7 @@ do
-- if being shot at, find closest SHORADs to activate -- if being shot at, find closest SHORADs to activate
if shotatsams or shotatus then if shotatsams or shotatus then
self:T({shotatsams=shotatsams,shotatus=shotatus}) 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 end
end end

View File

@@ -288,7 +288,7 @@ function TIRESIAS:_InitGroups()
} }
end end
if grp.Tiresias and (not grp.Tiresias.exception == true) then 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:SetCommandInvisible(true)
grp.Tiresias.invisible = true grp.Tiresias.invisible = true
if SwitchAAA then if SwitchAAA then
@@ -315,10 +315,11 @@ function TIRESIAS:_InitGroups()
} }
end end
if grp.Tiresias and (not grp.Tiresias.exception == true) then 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:SetCommandInvisible(true)
grp:SetAIOff() grp:SetAIOff()
grp.Tiresias.invisible = true grp.Tiresias.invisible = true
grp.Tiresias.AIOff = true
end end
end end
--BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) --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 end
if grp.Tiresias and (not grp.Tiresias.exception == true) then 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:SetCommandInvisible(true)
grp.Tiresias.invisible = true grp.Tiresias.invisible = true
end end
@@ -392,7 +393,8 @@ function TIRESIAS:_SwitchOnGroups(group,radius)
ground:ForEachGroupAlive( ground:ForEachGroupAlive(
function(grp) function(grp)
local name = grp:GetName() 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 if grp.Tiresias.invisible == true then
grp:SetCommandInvisible(false) grp:SetCommandInvisible(false)
grp.Tiresias.invisible = false grp.Tiresias.invisible = false

View File

@@ -6047,7 +6047,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
else 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) local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
self:_DebugMessage(text) self:_DebugMessage(text)
return nil return nil
@@ -6089,7 +6089,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
terminal=parking[i].TerminalID terminal=parking[i].TerminalID
end end
if self.Debug then if self.Debug and terminal then
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal) local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
coord:MarkToAll(text) coord:MarkToAll(text)
env.info(text) env.info(text)
@@ -8122,9 +8122,11 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
-- Debug output for occupied spots. -- Debug output for occupied spots.
if self.Debug then if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE 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) if coord then
self:I(self.lid..text) 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)
coord:MarkToAll(string.format(text)) self:I(self.lid..text)
coord:MarkToAll(text)
end
else else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
end end
@@ -8433,12 +8435,14 @@ function WAREHOUSE:_GetAttribute(group)
local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute
if group then if group then
local groupCat=group:GetCategory()
----------- -----------
--- Air --- --- Air ---
----------- -----------
-- Planes -- 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 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 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") local bomber=group:HasAttribute("Strategic bombers")
@@ -8593,7 +8597,6 @@ end
-- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed. -- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed.
-- @param #table queue The queue from which the item should be deleted. -- @param #table queue The queue from which the item should be deleted.
function WAREHOUSE:_DeleteQueueItem(qitem, queue) function WAREHOUSE:_DeleteQueueItem(qitem, queue)
self:F({qitem=qitem, queue=queue})
for i=1,#queue do for i=1,#queue do
local _item=queue[i] --#WAREHOUSE.Queueitem local _item=queue[i] --#WAREHOUSE.Queueitem

View File

@@ -10,7 +10,7 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
_DATABASE = DATABASE:New() -- Core.Database#DATABASE _DATABASE = DATABASE:New() -- Core.Database#DATABASE
--- Settings --- Settings
_SETTINGS = SETTINGS:Set() _SETTINGS = SETTINGS:Set() -- Core.Settings#SETTINGS
_SETTINGS:SetPlayerMenuOn() _SETTINGS:SetPlayerMenuOn()
--- Register cargos. --- Register cargos.

View File

@@ -1,8 +1,6 @@
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __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/Utils.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.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/FiFo.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' )

View File

@@ -19,7 +19,7 @@
-- * Option to present information in imperial or metric units -- * Option to present information in imperial or metric units
-- * Runway length and airfield elevation (optional) -- * Runway length and airfield elevation (optional)
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (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 -- ## Nevada: Nellis AFB
-- --
-- -- ATIS Nellis AFB on 270.10 MHz AM. -- -- 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:SetRadioRelayUnitName("Radio Relay Nellis")
-- atisNellis:SetActiveRunway("21L") -- atisNellis:SetActiveRunway("21L")
-- atisNellis:SetTowerFrequencies({327.000, 132.550}) -- atisNellis:SetTowerFrequencies({327.000, 132.550})
@@ -302,7 +302,7 @@
-- ## Persian Gulf: Abu Dhabi International Airport -- ## Persian Gulf: Abu Dhabi International Airport
-- --
-- -- ATIS Abu Dhabi International on 125.1 MHz AM. -- -- 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:SetRadioRelayUnitName("Radio Relay Abu Dhabi International Airport")
-- atisAbuDhabi:SetMetricUnits() -- atisAbuDhabi:SetMetricUnits()
-- atisAbuDhabi:SetActiveRunway("L") -- atisAbuDhabi:SetActiveRunway("L")
@@ -498,6 +498,9 @@ ATIS.Alphabet = {
-- @field #number Syria +5° (East). -- @field #number Syria +5° (East).
-- @field #number MarianaIslands +2° (East). -- @field #number MarianaIslands +2° (East).
-- @field #number SinaiMap +5° (East). -- @field #number SinaiMap +5° (East).
-- @field #number Kola +15° (East).
-- @field #number Afghanistan +3° (East).
-- @field #number Iraq +4.4° (East).
ATIS.RunwayM2T = { ATIS.RunwayM2T = {
Caucasus = 0, Caucasus = 0,
Nevada = 12, Nevada = 12,
@@ -508,6 +511,9 @@ ATIS.RunwayM2T = {
MarianaIslands = 2, MarianaIslands = 2,
Falklands = 12, Falklands = 12,
SinaiMap = 5, SinaiMap = 5,
Kola = 15,
Afghanistan = 3,
Iraq=4.4
} }
--- Whether ICAO phraseology is used for ATIS broadcasts. --- Whether ICAO phraseology is used for ATIS broadcasts.
@@ -521,6 +527,9 @@ ATIS.RunwayM2T = {
-- @field #boolean MarianaIslands true. -- @field #boolean MarianaIslands true.
-- @field #boolean Falklands true. -- @field #boolean Falklands true.
-- @field #boolean SinaiMap true. -- @field #boolean SinaiMap true.
-- @field #boolean Kola true.
-- @field #boolean Afghanistan true.
-- @field #boolean Iraq true.
ATIS.ICAOPhraseology = { ATIS.ICAOPhraseology = {
Caucasus = true, Caucasus = true,
Nevada = false, Nevada = false,
@@ -531,6 +540,9 @@ ATIS.ICAOPhraseology = {
MarianaIslands = true, MarianaIslands = true,
Falklands = true, Falklands = true,
SinaiMap = true, SinaiMap = true,
Kola = true,
Afghanistan = true,
Iraq = true,
} }
--- Nav point data. --- Nav point data.
@@ -619,83 +631,83 @@ ATIS.ICAOPhraseology = {
-- @field #ATIS.Soundfile TACANChannel -- @field #ATIS.Soundfile TACANChannel
-- @field #ATIS.Soundfile VORFrequency -- @field #ATIS.Soundfile VORFrequency
ATIS.Sound = { ATIS.Sound = {
ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 }, ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.85 },
ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 0.99 }, ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 1.50 },
ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 0.99 }, ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 1.38 },
AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 }, AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 2.98 },
Airport = { filename = "Airport.ogg", duration = 0.66 }, Airport = { filename = "Airport.ogg", duration = 0.55 },
Altimeter = { filename = "Altimeter.ogg", duration = 0.68 }, Altimeter = { filename = "Altimeter.ogg", duration = 0.91 },
At = { filename = "At.ogg", duration = 0.41 }, At = { filename = "At.ogg", duration = 0.32 },
CloudBase = { filename = "CloudBase.ogg", duration = 0.82 }, CloudBase = { filename = "CloudBase.ogg", duration = 0.69 },
CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 }, CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.53 },
CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 }, CloudsBroken = { filename = "CloudsBroken.ogg", duration = 0.81 },
CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 }, CloudsFew = { filename = "CloudsFew.ogg", duration = 0.74 },
CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 }, CloudsNo = { filename = "CloudsNo.ogg", duration = 0.69},
CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 }, CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.64 },
CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 }, CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.82 },
CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 }, CloudsScattered = { filename = "CloudsScattered.ogg", duration = 0.89 },
Decimal = { filename = "Decimal.ogg", duration = 0.54 }, Decimal = { filename = "Decimal.ogg", duration = 0.71 },
DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 }, DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.08 },
DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 }, DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.07 },
DewPoint = { filename = "DewPoint.ogg", duration = 0.65 }, DewPoint = { filename = "DewPoint.ogg", duration = 0.59 },
Dust = { filename = "Dust.ogg", duration = 0.54 }, Dust = { filename = "Dust.ogg", duration = 0.37 },
Elevation = { filename = "Elevation.ogg", duration = 0.78 }, Elevation = { filename = "Elevation.ogg", duration = 0.92 },
EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 }, EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.24 },
Feet = { filename = "Feet.ogg", duration = 0.45 }, Feet = { filename = "Feet.ogg", duration = 0.34 },
Fog = { filename = "Fog.ogg", duration = 0.47 }, Fog = { filename = "Fog.ogg", duration = 0.41 },
Gusting = { filename = "Gusting.ogg", duration = 0.55 }, Gusting = { filename = "Gusting.ogg", duration = 0.58 },
HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 }, HectoPascal = { filename = "HectoPascal.ogg", duration = 0.92 },
Hundred = { filename = "Hundred.ogg", duration = 0.47 }, Hundred = { filename = "Hundred.ogg", duration = 0.53 },
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 },
ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 }, ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 },
InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 }, InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.26 },
OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 }, Information = { filename = "Information.ogg", duration = 0.99 },
RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 }, InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.69 },
VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 }, Kilometers = { filename = "Kilometers.ogg", duration = 0.93 },
TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 }, Knots = { filename = "Knots.ogg", duration = 0.46 },
PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 }, Left = { filename = "Left.ogg", duration = 0.41 },
RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 }, MegaHertz = { filename = "MegaHertz.ogg", duration = 0.83 },
Zulu = { filename = "Zulu.ogg", duration = 0.62 }, 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 },
} }
--- ---
@@ -954,7 +966,7 @@ _ATIS = {}
--- ATIS class version. --- ATIS class version.
-- @field #string version -- @field #string version
ATIS.version = "1.0.0" ATIS.version = "1.0.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -2037,12 +2049,14 @@ function ATIS:onafterBroadcast( From, Event, To )
local sunrise = coord:GetSunrise() local sunrise = coord:GetSunrise()
--self:I(sunrise) --self:I(sunrise)
local SUNRISE = "no time" local SUNRISE = "no time"
local NorthPolar = true
if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then
sunrise = UTILS.Split( sunrise, ":" ) sunrise = UTILS.Split( sunrise, ":" )
SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] )
if self.useSRS then if self.useSRS then
SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours ) SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours )
end end
NorthPolar = false
end end
local sunset = coord:GetSunset() local sunset = coord:GetSunset()
@@ -2054,6 +2068,7 @@ function ATIS:onafterBroadcast( From, Event, To )
if self.useSRS then if self.useSRS then
SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours ) SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours )
end end
NorthPolar = false
end end
--------------------------------- ---------------------------------
@@ -2080,34 +2095,32 @@ function ATIS:onafterBroadcast( From, Event, To )
--------------- ---------------
-- Get mission weather info. Most of this is static. -- Get mission weather info. Most of this is static.
local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather() local clouds, visibility, turbulence, dustdens, 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. local dust=false
if fog and fog.thickness < height + 25 then local fog=false
fog = nil
end
-- Dust only up to 1500 ft = 457 m ASL.
if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then
dust = nil
end
------------------ ------------------
--- Visibility --- --- Visibility ---
------------------ ------------------
-- Get min visibility. if dustdens then
local visibilitymin = visibility
-- Dust only up to 1500 ft = 457 m ASL.
if fog then if UTILS.FeetToMeters( 1500 )> height+25 then
if fog.visibility < visibilitymin then dust=true
visibilitymin = fog.visibility visibility=math.min(visibility, dustdens)
end end
end
else -- As of DCS 2.9.10.3948 (December 2024), fog and dust are mutually exclusive!
if dust then
if dust < visibilitymin then -- Get current fog visibility and thickness
visibilitymin = dust 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
end end
@@ -2115,7 +2128,7 @@ function ATIS:onafterBroadcast( From, Event, To )
if self.metric then if self.metric then
-- Visibility in km. -- Visibility in km.
local reportedviz = UTILS.Round( visibilitymin / 1000 ) local reportedviz = UTILS.Round( visibility / 1000 )
-- max reported visibility 9999 m -- max reported visibility 9999 m
if reportedviz > 10 then if reportedviz > 10 then
reportedviz = 10 reportedviz = 10
@@ -2123,7 +2136,7 @@ function ATIS:onafterBroadcast( From, Event, To )
VISIBILITY = string.format( "%d", reportedviz ) VISIBILITY = string.format( "%d", reportedviz )
else else
-- max reported visibility 10 NM -- max reported visibility 10 NM
local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) ) local reportedviz = UTILS.Round( UTILS.MetersToSM( visibility ) )
if reportedviz > 10 then if reportedviz > 10 then
reportedviz = 10 reportedviz = 10
end end
@@ -2395,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To )
local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale) local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale)
--subtitle = string.format( "Sunrise at %s local time", SUNRISE ) --subtitle = string.format( "Sunrise at %s local time", SUNRISE )
subtitle = string.format( sunrise, 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:Transmission( self.Sound.SunriseAt, 0.5, subtitle )
self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 )
self:Transmission( self.Sound.TimeLocal, 0.2 ) self:Transmission( self.Sound.TimeLocal, 0.2 )
@@ -2406,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To )
local sunset = self.gettext:GetEntry("SUNSETAT",self.locale) local sunset = self.gettext:GetEntry("SUNSETAT",self.locale)
--subtitle = string.format( "Sunset at %s local time", SUNSET ) --subtitle = string.format( "Sunset at %s local time", SUNSET )
subtitle = string.format( sunset, 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:Transmission( self.Sound.SunsetAt, 0.5, subtitle )
self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 )
self:Transmission( self.Sound.TimeLocal, 0.2 ) self:Transmission( self.Sound.TimeLocal, 0.2 )
@@ -3350,28 +3363,13 @@ function ATIS:GetMissionWeather()
dust = weather.dust_density dust = weather.dust_density
end 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( "FF weather:" )
self:T( { clouds = clouds } ) self:T( { clouds = clouds } )
self:T( { visibility = visibility } ) self:T( { visibility = visibility } )
self:T( { turbulence = turbulence } ) self:T( { turbulence = turbulence } )
self:T( { fog = fog } )
self:T( { dust = dust } ) self:T( { dust = dust } )
self:T( { static = static } ) self:T( { static = static } )
return clouds, visibility, turbulence, fog, dust, static return clouds, visibility, turbulence, dust, static
end end
--- Get thousands of a number. --- Get thousands of a number.

View File

@@ -187,7 +187,7 @@ AIRWING = {
--- AIRWING class version. --- AIRWING class version.
-- @field #string version -- @field #string version
AIRWING.version="0.9.5" AIRWING.version="0.9.6"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -1365,16 +1365,20 @@ function AIRWING:CheckRescuhelo()
local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO})
local name=self.airbase:GetName() if self.airbase then
local carrier=UNIT:FindByName(name) local name=self.airbase:GetName()
for i=1,self.nflightsRescueHelo-N do local carrier=UNIT:FindByName(name)
local mission=AUFTRAG:NewRESCUEHELO(carrier) for i=1,self.nflightsRescueHelo-N do
self:AddMission(mission) local mission=AUFTRAG:NewRESCUEHELO(carrier)
self:AddMission(mission)
end
end end
return self return self

View File

@@ -3615,7 +3615,7 @@ function AIRBOSS:onafterStart( From, Event, To )
-- Handle events. -- Handle events.
self:HandleEvent( EVENTS.Birth ) self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.Land ) self:HandleEvent( EVENTS.RunwayTouch )
self:HandleEvent( EVENTS.EngineShutdown ) self:HandleEvent( EVENTS.EngineShutdown )
self:HandleEvent( EVENTS.Takeoff ) self:HandleEvent( EVENTS.Takeoff )
self:HandleEvent( EVENTS.Crash ) self:HandleEvent( EVENTS.Crash )
@@ -4381,7 +4381,7 @@ function AIRBOSS:onafterStop( From, Event, To )
-- Unhandle events. -- Unhandle events.
self:UnHandleEvent( EVENTS.Birth ) self:UnHandleEvent( EVENTS.Birth )
self:UnHandleEvent( EVENTS.Land ) self:UnHandleEvent( EVENTS.RunwayTouch )
self:UnHandleEvent( EVENTS.EngineShutdown ) self:UnHandleEvent( EVENTS.EngineShutdown )
self:UnHandleEvent( EVENTS.Takeoff ) self:UnHandleEvent( EVENTS.Takeoff )
self:UnHandleEvent( EVENTS.Crash ) self:UnHandleEvent( EVENTS.Crash )
@@ -8291,7 +8291,7 @@ end
--- Airboss event handler for event land. --- Airboss event handler for event land.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AIRBOSS:OnEventLand( EventData ) function AIRBOSS:OnEventRunwayTouch( EventData )
self:F3( { eventland = EventData } ) self:F3( { eventland = EventData } )
-- Nil checks. -- Nil checks.

View File

@@ -68,7 +68,7 @@ ARMYGROUP = {
--- Army Group version. --- Army Group version.
-- @field #string version -- @field #string version
ARMYGROUP.version="1.0.1" ARMYGROUP.version="1.0.3"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -2049,114 +2049,70 @@ end
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #table Template Template used to init the group. Default is `self.template`. -- @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 -- @return #ARMYGROUP self
function ARMYGROUP:_InitGroup(Template, Delay) function ARMYGROUP:_InitGroup(Template, Delay)
if Delay and Delay>0 then if Delay and Delay>0 then
self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0) self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0)
else 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. -- First check if group was already initialized.
if self.groupinitialized then if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
return return
end end
self:T(self.lid.."FF Initializing Group")
-- Get template of group. -- Get template of group.
local template=Template or self:_GetTemplate() local template=Template or self:_GetTemplate()
-- Ground are always AI. -- Ground are always AI.
self.isAI=true self.isAI=true
-- Is (template) group late activated. -- Is (template) group late activated.
self.isLateActivated=template.lateActivation self.isLateActivated=template.lateActivation
-- Ground groups cannot be uncontrolled. -- Ground groups cannot be uncontrolled.
self.isUncontrolled=false self.isUncontrolled=false
-- Max speed in km/h. -- Max speed in km/h.
self.speedMax=self.group:GetSpeedMax() self.speedMax=self.group:GetSpeedMax()
-- Is group mobile? -- Is group mobile?
if self.speedMax>3.6 then if self.speedMax and self.speedMax>3.6 then
self.isMobile=true self.isMobile=true
else else
self.isMobile=false self.isMobile=false
self.speedMax = 0
end end
-- Cruise speed in km/h -- Cruise speed in km/h
self.speedCruise=self.speedMax*0.7 self.speedCruise=self.speedMax*0.7
-- Group ammo. -- Group ammo.
self.ammo=self:GetAmmoTot() self.ammo=self:GetAmmoTot()
-- Radio parameters from template. -- Radio parameters from template.
self.radio.On=false -- Radio is always OFF for ground. self.radio.On=false -- Radio is always OFF for ground.
self.radio.Freq=133 self.radio.Freq=133
self.radio.Modu=radio.modulation.AM self.radio.Modu=radio.modulation.AM
-- Set default radio. -- Set default radio.
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
-- Get current formation from first waypoint. -- Get current formation from first waypoint.
self.option.Formation=template.route.points[1].action self.option.Formation=template.route.points[1].action
-- Set default formation to "on road". -- Set default formation to "on road".
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
-- Default TACAN off. -- Default TACAN off.
self:SetDefaultTACAN(nil, nil, nil, nil, true) if not self.tacanDefault then
self.tacan=UTILS.DeepCopy(self.tacanDefault) self:SetDefaultTACAN(nil, nil, nil, nil, true)
end
if not self.tacan then
self.tacan=UTILS.DeepCopy(self.tacanDefault)
end
-- Units of the group. -- Units of the group.
local units=self.group:GetUnits() local units=self.group:GetUnits()
@@ -2177,7 +2133,6 @@ function ARMYGROUP:_InitGroup(Template, Delay)
self:_AddElementByName(unitname) self:_AddElementByName(unitname)
end end
-- Init done. -- Init done.
self.groupinitialized=true self.groupinitialized=true
end end

View File

@@ -162,6 +162,7 @@
-- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...). -- @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 missionWaypointCoord Mission waypoint coordinate.
-- @field Core.Point#COORDINATE missionEgressCoord Mission egress 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 #number missionWaypointRadius Random radius in meters.
-- @field #boolean legionReturn If `true`, assets return to their legion (default). If `false`, they will stay alive. -- @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 #AUFTRAG self
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. -- @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 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 -- @return #AUFTRAG self
function AUFTRAG:NewSTRIKE(Target, Altitude) function AUFTRAG:NewSTRIKE(Target, Altitude, EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE)
mission:_TargetFromObject(Target) mission:_TargetFromObject(Target)
-- DCS Task options: -- DCS Task options:
mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000)
@@ -1749,15 +1751,16 @@ end
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. -- @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 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 -- @return #AUFTRAG self
function AUFTRAG:NewBOMBING(Target, Altitude) function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
mission:_TargetFromObject(Target) mission:_TargetFromObject(Target)
-- DCS task options: -- DCS task options:
mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
@@ -1819,7 +1822,7 @@ end
--- **[AIR]** Create a BOMBRUNWAY mission. --- **[AIR]** Create a BOMBRUNWAY mission.
-- @param #AUFTRAG self -- @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. -- @param #number Altitude Engage altitude in feet. Default 25000 ft.
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) 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 #AUFTRAG self
-- @param Core.Point#COORDINATE Target Center of the firing solution. -- @param Core.Point#COORDINATE Target Center of the firing solution.
-- @param #number Nshots Number of shots to be fired. Default `#nil`. -- @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.optionAlarm=0
mission.missionFraction=0.0 mission.missionFraction=0.0
mission.missionWaypointRadius=0.0
-- Evaluate after 8 min. -- Evaluate after 8 min.
mission.dTevaluate=8*60 mission.dTevaluate=8*60
@@ -2302,7 +2310,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation)
params.formation=Formation or "Off Road" params.formation=Formation or "Off Road"
params.zone=mission:GetObjective() params.zone=mission:GetObjective()
params.altitude=mission.missionAltitude params.altitude=mission.missionAltitude
params.speed=mission.missionSpeed params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
mission.DCStask.params=params mission.DCStask.params=params
@@ -2352,7 +2360,7 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
mission.DCStask=mission:GetDCSMissionTask() 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 mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee
return mission return mission
@@ -3481,7 +3489,7 @@ end
--- Set Rules of Engagement (ROE) for this mission. --- Set Rules of Engagement (ROE) for this mission.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #string roe Mission ROE. -- @param #number roe Mission ROE, e.g. `ENUMS.ROE.ReturnFire` (whiche equals 3)
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:SetROE(roe) function AUFTRAG:SetROE(roe)
@@ -3493,7 +3501,7 @@ end
--- Set Reaction on Threat (ROT) for this mission. --- Set Reaction on Threat (ROT) for this mission.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #string rot Mission ROT. -- @param #number rot Mission ROT, e.g. `ENUMS.ROT.NoReaction` (whiche equals 0)
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:SetROT(rot) function AUFTRAG:SetROT(rot)
@@ -4657,6 +4665,16 @@ function AUFTRAG:SetGroupWaypointCoordinate(opsgroup, coordinate)
return self return self
end 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 --- Get mission (ingress) waypoint coordinate of OPS group
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group. -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group.
@@ -5712,7 +5730,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes)
return text return text
end 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 #AUFTRAG self
-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed.
-- @return #AUFTRAG self -- @return #AUFTRAG self
@@ -5740,8 +5758,9 @@ end
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Coordinate Egrees coordinate. -- @param Core.Point#COORDINATE Coordinate Egrees coordinate.
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of 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 -- @return #AUFTRAG self
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude, Speed)
-- Obviously a zone was passed. We get the coordinate. -- Obviously a zone was passed. We get the coordinate.
if Coordinate:IsInstanceOf("ZONE_BASE") then if Coordinate:IsInstanceOf("ZONE_BASE") then
@@ -5752,7 +5771,64 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
if Altitude then if Altitude then
self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude)
self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude)
end 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 end
--- Get the mission egress coordinate if this was defined. --- Get the mission egress coordinate if this was defined.
@@ -5762,6 +5838,20 @@ function AUFTRAG:GetMissionEgressCoord()
return self.missionEgressCoord return self.missionEgressCoord
end 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. --- Get coordinate which was set as mission waypoint coordinate.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`. -- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`.
@@ -5796,10 +5886,27 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes)
end end
return coord return coord
end 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. -- Create waypoint coordinate half way between us and the target.
local waypointcoord=COORDINATE:New(0,0,0) local waypointcoord=COORDINATE:New(0,0,0)
local coord=group:GetCoordinate()
if coord then if coord then
waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction)
else else
@@ -6099,7 +6206,7 @@ function AUFTRAG:GetDCSMissionTask()
local param={} local param={}
param.zone=self:GetObjective() param.zone=self:GetObjective()
param.altitude=self.missionAltitude param.altitude=self.missionAltitude
param.speed=self.missionSpeed param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
DCStask.params=param DCStask.params=param
@@ -6179,7 +6286,7 @@ function AUFTRAG:GetDCSMissionTask()
local param={} local param={}
param.target=self.engageTarget param.target=self.engageTarget
param.altitude=self.missionAltitude param.altitude=self.missionAltitude
param.speed=self.missionSpeed param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
param.lastindex=nil param.lastindex=nil
DCStask.params=param DCStask.params=param
@@ -6352,7 +6459,7 @@ function AUFTRAG:GetDCSMissionTask()
local param={} local param={}
param.zone=self:GetObjective() param.zone=self:GetObjective()
param.altitude=self.missionAltitude param.altitude=self.missionAltitude
param.speed=self.missionSpeed param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
DCStask.params=param DCStask.params=param
@@ -6388,7 +6495,7 @@ function AUFTRAG:GetDCSMissionTask()
local param={} local param={}
param.zone=self:GetObjective() param.zone=self:GetObjective()
param.altitude=self.missionAltitude param.altitude=self.missionAltitude
param.speed=self.missionSpeed param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
DCStask.params=param DCStask.params=param
@@ -6408,7 +6515,7 @@ function AUFTRAG:GetDCSMissionTask()
local param={} local param={}
param.target=self:GetTargetData() param.target=self:GetTargetData()
param.action="Wedge" param.action="Wedge"
param.speed=self.missionSpeed param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
DCStask.params=param DCStask.params=param

View File

@@ -17,7 +17,7 @@
-- === -- ===
-- --
-- ### Author: **applevangelist** -- ### Author: **applevangelist**
-- @date Last Update July 2024 -- @date Last Update Jan 2025
-- @module Ops.AWACS -- @module Ops.AWACS
-- @image OPS_AWACS.jpg -- @image OPS_AWACS.jpg
@@ -122,6 +122,7 @@ do
-- @field #number TacticalModulation -- @field #number TacticalModulation
-- @field #number TacticalInterval -- @field #number TacticalInterval
-- @field Core.Set#SET_GROUP DetectionSet -- @field Core.Set#SET_GROUP DetectionSet
-- @field #number MaxMissionRange
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -183,7 +184,7 @@ do
-- --
-- Add Escorts Squad (recommended, optional) -- 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:AddMissionCapability({AUFTRAG.Type.ESCORT})
-- Squad_Two:SetFuelLowRefuel(true) -- Squad_Two:SetFuelLowRefuel(true)
-- Squad_Two:SetFuelLowThreshold(0.3) -- 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". -- -- 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. -- -- 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 ) -- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM )
-- -- set two escorts -- -- set one escort group; this example has two units in the template group, so they can fly a nice formation.
-- testawacs:SetEscort(2) -- 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. -- -- 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) -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25)
-- -- Set up SRS on port 5010 - change the below to your path and port -- -- Set up SRS on port 5010 - change the below to your path and port
@@ -508,7 +509,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.65", -- #string version = "0.2.71", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@@ -605,6 +606,7 @@ AWACS = {
TacticalModulation = radio.modulation.AM, TacticalModulation = radio.modulation.AM,
TacticalInterval = 120, TacticalInterval = 120,
DetectionSet = nil, DetectionSet = nil,
MaxMissionRange = 125,
} }
--- ---
@@ -1572,6 +1574,15 @@ function AWACS:SetLocale(Locale)
return self return self
end 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. --- [User] Add additional frequency and modulation for AWACS SRS output.
-- @param #AWACS self -- @param #AWACS self
-- @param #number Frequency The frequency to add, e.g. 132.5 -- @param #number Frequency The frequency to add, e.g. 132.5
@@ -1762,7 +1773,7 @@ function AWACS:_EventHandler(EventData)
end end
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? -- check known player?
self:T("Player group left unit: " .. Event.IniGroupName) self:T("Player group left unit: " .. Event.IniGroupName)
self:T("Player name left: " .. Event.IniPlayerName) self:T("Player name left: " .. Event.IniPlayerName)
@@ -2158,9 +2169,12 @@ end
--- [User] Set AWACS Escorts Template --- [User] Set AWACS Escorts Template
-- @param #AWACS self -- @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 -- @return #AWACS self
function AWACS:SetEscort(EscortNumber) function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance)
self:T(self.lid.."SetEscort") self:T(self.lid.."SetEscort")
if EscortNumber and EscortNumber > 0 then if EscortNumber and EscortNumber > 0 then
self.HasEscorts = true self.HasEscorts = true
@@ -2169,6 +2183,9 @@ function AWACS:SetEscort(EscortNumber)
self.HasEscorts = false self.HasEscorts = false
self.EscortNumber = 0 self.EscortNumber = 0
end end
self.EscortFormation = Formation
self.OffsetVec = OffsetVector or {x=500,y=100,z=500}
self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45
return self return self
end end
@@ -2223,11 +2240,26 @@ function AWACS:_StartEscorts(Shiftchange)
local group = AwacsFG:GetGroup() local group = AwacsFG:GetGroup()
local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds 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 for i=1,self.EscortNumber do
-- every -- 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"}) 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"})
escort:SetRequiredAssets(1) --local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"})
--escort:SetRequiredAssets(self.EscortNumber)
escort:SetTime(nil,timeonstation) escort:SetTime(nil,timeonstation)
if self.Escortformation then
escort:SetFormation(self.Escortformation)
end
escort:SetMissionRange(self.MaxMissionRange)
self.AirWing:AddMission(escort) self.AirWing:AddMission(escort)
self.CatchAllMissions[#self.CatchAllMissions+1] = escort self.CatchAllMissions[#self.CatchAllMissions+1] = escort
@@ -2434,7 +2466,7 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer)
local callsign = "Ghost 1" local callsign = "Ghost 1"
if Group and Group:IsAlive() then 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 end
return callsign return callsign
end end
@@ -2443,10 +2475,12 @@ end
-- @param #AWACS self -- @param #AWACS self
-- @param #boolean ShortCallsign If true, only call out the major flight number -- @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 #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. -- 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 -- @return #AWACS self
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign == false then if not ShortCallsign or ShortCallsign == false then
self.callsignshort = false self.callsignshort = false
else else
@@ -2454,6 +2488,8 @@ function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
end end
self.keepnumber = Keepnumber or false self.keepnumber = Keepnumber or false
self.callsignTranslations = CallsignTranslations self.callsignTranslations = CallsignTranslations
self.callsignCustomFunc = CallsignCustomFunc
self.callsignCustomArgs = arg or {}
return self return self
end end
@@ -3626,7 +3662,7 @@ function AWACS:_CheckIn(Group)
managedgroup.LastTasking = timer.getTime() managedgroup.LastTasking = timer.getTime()
GID = managedgroup.GID GID = managedgroup.GID
self.ManagedGrps[self.ManagedGrpID]=managedgroup self.ManagedGrps[self.ManagedGrpID]=managedgroup
local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate())
local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true) local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true)
@@ -3893,6 +3929,12 @@ function AWACS:_SetClientMenus()
checkin = checkin, checkin = checkin,
} }
self.clientmenus:Push(menus,cgrpname) 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
end end
else else
@@ -5739,7 +5781,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets)
local intercept = AUFTRAG:NewINTERCEPT(Target.Target) local intercept = AUFTRAG:NewINTERCEPT(Target.Target)
intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL)
intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) intercept:SetWeaponType(ENUMS.WeaponFlag.Auto)
intercept:SetMissionRange(self.MaxMissionRange)
-- TODO -- TODO
-- now this is going to be interesting... -- now this is going to be interesting...
-- Check if the target left the "hot" area or is dead already -- 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) AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels)
local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{})
capauftrag:SetMissionRange(self.MaxMissionRange)
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
Pilot.FlightGroup:AddMission(capauftrag) Pilot.FlightGroup:AddMission(capauftrag)
@@ -5904,6 +5947,8 @@ function AWACS:onafterStart(From, Event, To)
-- set up the AWACS and let it orbit -- set up the AWACS and let it orbit
local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) 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 local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
mission:SetTime(nil,timeonstation) mission:SetTime(nil,timeonstation)
self.CatchAllMissions[#self.CatchAllMissions+1] = mission self.CatchAllMissions[#self.CatchAllMissions+1] = mission
@@ -6046,6 +6091,7 @@ function AWACS:_CheckAwacsStatus()
end end
end end
end end
-------------------------------- --------------------------------
-- AWACS -- AWACS
-------------------------------- --------------------------------
@@ -6194,12 +6240,13 @@ function AWACS:_CheckAwacsStatus()
report:Add("====================") report:Add("====================")
local RESMission
-- Check for replacement mission - if any -- Check for replacement mission - if any
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG
ESmission = self.EscortMissionReplacement[i] RESMission = self.EscortMissionReplacement[i]
local esstatus = ESmission:GetState() local esstatus = RESMission:GetState()
local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp)
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) 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("Auftrag Status: %s",esstatus))
report:Add(string.format("TOS Left: %d min",ESTOSLeft)) 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 local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
if OpsGroup then if OpsGroup then
local OpsName = OpsGroup:GetName() or "Unknown" local OpsName = OpsGroup:GetName() or "Unknown"
@@ -6219,13 +6266,13 @@ function AWACS:_CheckAwacsStatus()
report:Add("***** Cannot obtain (yet) this missions OpsGroup!") report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end end
if ESmission:IsExecuting() then if RESMission and RESMission:IsExecuting() then
-- make the actual change in the queue -- make the actual change in the queue
self.ShiftChangeEscortsFlag = false self.ShiftChangeEscortsFlag = false
self.ShiftChangeEscortsRequested = false self.ShiftChangeEscortsRequested = false
-- cancel old mission -- cancel old mission
if ESmission and ESmission:IsNotOver() then if ESmission and ESmission:IsNotOver() then
ESmission:Cancel() ESmission:__Cancel(1)
end end
self.EscortMission[i] = self.EscortMissionReplacement[i] self.EscortMission[i] = self.EscortMissionReplacement[i]
self.EscortMissionReplacement[i] = nil self.EscortMissionReplacement[i] = nil
@@ -6477,6 +6524,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo
if auftragtype == AUFTRAG.Type.ALERT5 then if auftragtype == AUFTRAG.Type.ALERT5 then
-- all correct -- all correct
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) 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:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
capauftrag:AddAsset(managedgroup.FlightGroup) capauftrag:AddAsset(managedgroup.FlightGroup)
self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag
@@ -6840,7 +6888,8 @@ function AWACS:onafterAwacsShiftChange(From,Event,To)
self.CatchAllMissions[#self.CatchAllMissions+1] = mission self.CatchAllMissions[#self.CatchAllMissions+1] = mission
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
mission:SetTime(nil,timeonstation) mission:SetTime(nil,timeonstation)
mission:SetMissionRange(self.MaxMissionRange)
AwacsAW:AddMission(mission) AwacsAW:AddMission(mission)
self.AwacsMissionReplacement = mission self.AwacsMissionReplacement = mission

View File

@@ -31,7 +31,7 @@
-- @image OPS_CSAR.jpg -- @image OPS_CSAR.jpg
--- ---
-- Last Update Sep 2024 -- Last Update Jan 2025
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -92,7 +92,7 @@
-- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal.
-- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- 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.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.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.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. -- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons.
@@ -308,12 +308,12 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2
CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH-58D"] = 2 CSAR.AircraftType["OH58D"] = 2
CSAR.AircraftType["CH-47Fbl1"] = 31 CSAR.AircraftType["CH-47Fbl1"] = 31
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.29" CSAR.version="1.0.30"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -809,6 +809,8 @@ end
-- @param #boolean noMessage -- @param #boolean noMessage
-- @param #string _description Description -- @param #string _description Description
-- @param #boolean forcedesc Use the description only for the pilot track entry -- @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 ) function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc )
self:T(self.lid .. " _AddCsar") self:T(self.lid .. " _AddCsar")
self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description})
@@ -841,9 +843,9 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
local BeaconName local BeaconName
if _playerName then if _playerName then
BeaconName = _unitName..math.random(1,10000)
elseif _unitName then
BeaconName = _playerName..math.random(1,10000) BeaconName = _playerName..math.random(1,10000)
elseif _unitName then
BeaconName = _unitName..math.random(1,10000)
else else
BeaconName = "Ghost-1-1"..math.random(1,10000) BeaconName = "Ghost-1-1"..math.random(1,10000)
end end
@@ -878,7 +880,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc.
return self return _spawnedGroup, _alias
end 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. --- (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.
@@ -1829,8 +1831,9 @@ end
--- (Internal) Function to get string of a group\'s position. --- (Internal) Function to get string of a group\'s position.
-- @param #CSAR self -- @param #CSAR self
-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object.
-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit
-- @return #string Coordinates as Text -- @return #string Coordinates as Text
function CSAR:_GetPositionOfWounded(_woundedGroup) function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit)
self:T(self.lid .. " _GetPositionOfWounded") self:T(self.lid .. " _GetPositionOfWounded")
local _coordinate = _woundedGroup:GetCoordinate() local _coordinate = _woundedGroup:GetCoordinate()
local _coordinatesText = "None" local _coordinatesText = "None"
@@ -1845,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup)
_coordinatesText = _coordinate:ToStringBULLS(self.coalition) _coordinatesText = _coordinate:ToStringBULLS(self.coalition)
end end
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 return _coordinatesText
end end
@@ -1870,13 +1893,17 @@ function CSAR:_DisplayActiveSAR(_unitName)
self:T({Table=_value}) self:T({Table=_value})
local _woundedGroup = _value.group local _woundedGroup = _value.group
if _woundedGroup and _value.alive then if _woundedGroup and _value.alive then
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli)
local _helicoord = _heli:GetCoordinate() local _helicoord = _heli:GetCoordinate()
local _woundcoord = _woundedGroup:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate()
local _distance = self:_GetDistance(_helicoord, _woundcoord) local _distance = self:_GetDistance(_helicoord, _woundcoord)
self:T({_distance = _distance}) self:T({_distance = _distance})
local distancetext = "" 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)) distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance))
else else
distancetext = string.format("%.1fkm", _distance/1000.0) distancetext = string.format("%.1fkm", _distance/1000.0)
@@ -2425,7 +2452,22 @@ function CSAR:onafterStart(From, Event, To)
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end 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 if not self.coordinate then
local csarhq = self.mash:GetRandom() local csarhq = self.mash:GetRandom()

File diff suppressed because it is too large Load Diff

View File

@@ -88,7 +88,10 @@ COHORT = {
--- COHORT class version. --- COHORT class version.
-- @field #string 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 -- TODO list
@@ -110,6 +113,17 @@ COHORT.version="0.3.5"
-- @return #COHORT self -- @return #COHORT self
function COHORT:New(TemplateGroupName, Ngroups, CohortName) 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. -- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #COHORT local self=BASE:Inherit(self, FSM:New()) -- #COHORT
@@ -117,7 +131,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
self.templatename=TemplateGroupName self.templatename=TemplateGroupName
-- Cohort name. -- Cohort name.
self.name=tostring(CohortName or TemplateGroupName) self.name=name
-- Set some string id for output to DCS.log file. -- Set some string id for output to DCS.log file.
self.lid=string.format("COHORT %s | ", self.name) self.lid=string.format("COHORT %s | ", self.name)

View File

@@ -1774,8 +1774,10 @@ function COMMANDER:RecruitAssetsForMission(Mission)
MaxWeight=cohort.cargobayLimit MaxWeight=cohort.cargobayLimit
end end
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 end
local legions=self.legions local legions=self.legions
@@ -2165,4 +2167,4 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -50,7 +50,7 @@
-- @field #number capleg -- @field #number capleg
-- @field #number maxinterceptsize -- @field #number maxinterceptsize
-- @field #number missionrange -- @field #number missionrange
-- @field #number noaltert5 -- @field #number noalert5
-- @field #table ManagedAW -- @field #table ManagedAW
-- @field #table ManagedSQ -- @field #table ManagedSQ
-- @field #table ManagedCP -- @field #table ManagedCP
@@ -167,7 +167,7 @@
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM. -- * @{#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.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.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.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.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. -- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
@@ -197,7 +197,7 @@ EASYGCICAP = {
capleg = 15, capleg = 15,
maxinterceptsize = 2, maxinterceptsize = 2,
missionrange = 100, missionrange = 100,
noaltert5 = 4, noalert5 = 4,
ManagedAW = {}, ManagedAW = {},
ManagedSQ = {}, ManagedSQ = {},
ManagedCP = {}, ManagedCP = {},
@@ -252,7 +252,7 @@ EASYGCICAP = {
--- EASYGCICAP class version. --- EASYGCICAP class version.
-- @field #string version -- @field #string version
EASYGCICAP.version="0.1.13" EASYGCICAP.version="0.1.17"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -269,7 +269,7 @@ EASYGCICAP.version="0.1.13"
-- @param #string Alias A Name for this GCICAP -- @param #string Alias A Name for this GCICAP
-- @param #string AirbaseName Name of the Home Airbase -- @param #string AirbaseName Name of the Home Airbase
-- @param #string Coalition Coalition, e.g. "blue" or "red" -- @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 -- @return #EASYGCICAP self
function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
-- Inherit everything from FSM class. -- Inherit everything from FSM class.
@@ -278,9 +278,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
-- defaults -- defaults
self.alias = Alias or AirbaseName.." CAP Wing" self.alias = Alias or AirbaseName.." CAP Wing"
self.coalitionname = string.lower(Coalition) or "blue" 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.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.CapZoneName = CapZoneName
self.airbasename = AirbaseName self.airbasename = AirbaseName
self.airbase = AIRBASE:FindByName(self.airbasename) self.airbase = AIRBASE:FindByName(self.airbasename)
@@ -293,7 +294,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self.capleg = 15 self.capleg = 15
self.capgrouping = 2 self.capgrouping = 2
self.missionrange = 100 self.missionrange = 100
self.noaltert5 = 2 self.noalert5 = 2
self.MaxAliveMissions = 8 self.MaxAliveMissions = 8
self.engagerange = 50 self.engagerange = 50
self.repeatsonfailure = 3 self.repeatsonfailure = 3
@@ -347,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
return self return self
end end
--- Set Maximum of alive missions to stop airplanes spamming the map --- Count alive missions in our internal stack.
-- @param #EASYGCICAP self -- @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 -- @return #EASYGCICAP self
function EASYGCICAP:SetMaxAliveMissions(Maxiumum) function EASYGCICAP:SetMaxAliveMissions(Maxiumum)
self:T(self.lid.."SetMaxAliveMissions") self:T(self.lid.."SetMaxAliveMissions")
@@ -441,9 +456,9 @@ end
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #number Airframes defaults to 2 -- @param #number Airframes defaults to 2
-- @return #EASYGCICAP self -- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes) function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes)
self:T(self.lid.."SetDefaultNumberAlter5Standby") self:T(self.lid.."SetDefaultNumberAlert5Standby")
self.noaltert5 = math.abs(Airframes) or 2 self.noalert5 = math.abs(Airframes) or 2
return self return self
end end
@@ -452,7 +467,7 @@ end
-- @param #number Range defaults to 50NM -- @param #number Range defaults to 50NM
-- @return #EASYGCICAP self -- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultEngageRange(Range) function EASYGCICAP:SetDefaultEngageRange(Range)
self:T(self.lid.."SetDefaultNumberAlter5Standby") self:T(self.lid.."SetDefaultEngageRange")
self.engagerange = Range or 50 self.engagerange = Range or 50
return self return self
end end
@@ -584,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
local TankerInvisible = self.TankerInvisible 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 local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
if DespawnAfterLanding then if DespawnAfterLanding then
flightgroup:SetDespawnAfterLanding() flightgroup:SetDespawnAfterLanding()
@@ -614,15 +629,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
flightgroup:SetFuelLowRTB(true) flightgroup:SetFuelLowRTB(true)
Intel:AddAgent(flightgroup) Intel:AddAgent(flightgroup)
if DespawnAfterHolding then if DespawnAfterHolding then
function flightgroup:OnAfterHolding(From,Event,To) function flightgroup:onbeforeHolding(From,Event,To)
self:Despawn(1,true) self:Despawn(1,true)
end end
end end
end end
if self.noaltert5 > 0 then if self.noalert5 > 0 then
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
alert:SetRequiredAssets(self.noaltert5) alert:SetRequiredAssets(self.noalert5)
alert:SetRepeat(99) alert:SetRepeat(99)
CAP_Wing:AddMission(alert) CAP_Wing:AddMission(alert)
table.insert(self.ListOfAuftrag,alert) table.insert(self.ListOfAuftrag,alert)
@@ -1176,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local wings = self.wings local wings = self.wings
local ctlpts = self.ManagedCP local ctlpts = self.ManagedCP
local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
local nogozoneset = self.NoGoZoneSet local nogozoneset = self.NoGoZoneSet
local ReadyFlightGroups = self.ReadyFlightGroups local ReadyFlightGroups = self.ReadyFlightGroups
@@ -1241,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster)
-- Do we have a matching airwing? -- Do we have a matching airwing?
if targetairwing then if targetairwing then
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort) local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
local missioncount = self:_CountAliveAuftrags()
-- Enough airframes on mission already? -- Enough airframes on mission already?
self:T(self.lid.." Assets on Mission "..AssetCount) self:T(self.lid.." Assets on Mission "..AssetCount)
if AssetCount <= MaxAliveMissions then if missioncount < MaxAliveMissions then
local repeats = repeatsonfailure local repeats = repeatsonfailure
local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group) local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group)
:SetMissionRange(150) :SetMissionRange(150)
@@ -1291,11 +1307,11 @@ function EASYGCICAP:_StartIntel()
self:T(self.lid.."_StartIntel") self:T(self.lid.."_StartIntel")
-- Border GCI Detection -- Border GCI Detection
local BlueAir_DetectionSetGroup = SET_GROUP:New() local BlueAir_DetectionSetGroup = SET_GROUP:New()
BlueAir_DetectionSetGroup:FilterPrefixes( { self.EWRName } ) BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
BlueAir_DetectionSetGroup:FilterStart() BlueAir_DetectionSetGroup:FilterStart()
-- Intel type detection -- 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:SetClusterAnalysis(true,false,false)
BlueIntel:SetForgetTime(300) BlueIntel:SetForgetTime(300)
BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetAcceptZones(self.GoZoneSet)
@@ -1311,7 +1327,7 @@ function EASYGCICAP:_StartIntel()
self:_AssignIntercept(Cluster) self:_AssignIntercept(Cluster)
end end
function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
AssignCluster(Cluster) AssignCluster(Cluster)
end end
@@ -1428,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To)
local text = "GCICAP "..self.alias local text = "GCICAP "..self.alias
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
text = text.."\nThreats: "..threatcount 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 - CAP: "..capmission
text = text.."\n - Intercept: "..interceptmission
text = text.."\n - AWACS: "..awacsmission text = text.."\n - AWACS: "..awacsmission
text = text.."\n - TANKER: "..tankermission text = text.."\n - TANKER: "..tankermission
text = text.."\n - Recon: "..reconmission 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) MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug)
end end
self:__Status(30) self:__Status(30)

View File

@@ -217,7 +217,7 @@ FLIGHTGROUP.Players={}
--- FLIGHTGROUP class version. --- FLIGHTGROUP class version.
-- @field #string version -- @field #string version
FLIGHTGROUP.version="1.0.2" FLIGHTGROUP.version="1.0.3"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -1277,6 +1277,25 @@ function FLIGHTGROUP:Status()
end end
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, check if DCS task needs to be updated.
if mission and mission.updateDCSTask then if mission and mission.updateDCSTask then
@@ -3803,114 +3822,124 @@ end
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
-- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP self
-- @param #table Template Template used to init the group. Default is `self.template`. -- @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 -- @return #FLIGHTGROUP self
function FLIGHTGROUP:_InitGroup(Template) function FLIGHTGROUP:_InitGroup(Template, Delay)
-- First check if group was already initialized. if Delay and Delay>0 then
if self.groupinitialized then self:ScheduleOnce(Delay, FLIGHTGROUP._InitGroup, self, Template, 0)
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
else else
self.isMobile=false
self.speedMax = 0
end
-- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings. -- First check if group was already initialized.
local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380) if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
-- Cruise speed: 70% of max speed but within limit. return
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 end
self.callsign.NumberSquad=tonumber(callsign[1])
self.callsign.NumberGroup=tonumber(callsign[2]) -- Group object.
self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) local group=self.group --Wrapper.Group#GROUP
-- Helo group.
self.isHelo=group:IsHelicopter()
-- Max speed in km/h.
self.speedMax=group:GetSpeedMax()
end -- Is group mobile?
if self.speedMax and self.speedMax>3.6 then
-- Set default formation. self.isMobile=true
if self.isHelo then else
self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 self.isMobile=false
else self.speedMax = 0
self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group end
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()
-- DCS group. -- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings.
local dcsgroup=Group.getByName(self.groupname) local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380)
local size0=dcsgroup:getInitialSize()
-- Quick check. -- Cruise speed: 70% of max speed but within limit.
if #units~=size0 then self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit)
self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
end -- Group ammo.
self.ammo=self:GetAmmoTot()
-- Add elemets.
for _,unit in pairs(units) do -- Get template of group.
self:_AddElementByName(unit:GetName()) 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 end
-- Init done.
self.groupinitialized=true
return self return self
end end
@@ -4179,51 +4208,6 @@ function FLIGHTGROUP:IsLandingAirbase(wp)
return nil return nil
end 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. --- Add an AIR waypoint to the flight plan.
-- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP self

View File

@@ -53,7 +53,7 @@ LEGION.RandomAssetScore=1
--- LEGION class version. --- LEGION class version.
-- @field #string version -- @field #string version
LEGION.version="0.5.0" LEGION.version="0.5.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -445,6 +445,21 @@ function LEGION:DelCohort(Cohort)
return self return self
end 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. --- Relocate a cohort to another legion.
-- Assets in stock are spawned and routed to the new 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 if self.commander and self.commander.chief then
self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname})
end 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 mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function
-- Remove asset from squadron same -- Remove asset from squadron same
@@ -1827,6 +1845,9 @@ function LEGION:_CreateFlightGroup(asset)
-- Set home base. -- Set home base.
opsgroup.homebase=self.airbase opsgroup.homebase=self.airbase
-- Set destination base
opsgroup.destbase=self.airbase
-- Set home zone. -- Set home zone.
opsgroup.homezone=self.spawnzone opsgroup.homezone=self.spawnzone

View File

@@ -91,7 +91,7 @@ NAVYGROUP = {
--- NavyGroup version. --- NavyGroup version.
-- @field #string version -- @field #string version
NAVYGROUP.version="1.0.2" NAVYGROUP.version="1.0.3"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -395,7 +395,7 @@ function NAVYGROUP:New(group)
self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.Dead, self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit)
-- Start the status monitoring. -- Start the status monitoring.
self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) self.timerStatus=TIMER:New(self.Status, self):Start(1, 30)
@@ -775,7 +775,7 @@ end
--- Update status. --- Update status.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
function NAVYGROUP:Status(From, Event, To) function NAVYGROUP:Status()
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
@@ -978,6 +978,35 @@ function NAVYGROUP:Status(From, Event, To)
end 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 -- Engage Detected Targets
--- ---
@@ -1041,7 +1070,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
-- Debug info. -- Debug info.
if self.verbose>=1 then 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("Unit type = %s\n", self.actype)
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) 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)) 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 if self.verbose>=10 then
for i=1,#waypoints do for i=1,#waypoints do
local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint 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) self:I(self.lid..text)
COORDINATE:NewFromWaypoint(wp):MarkToAll(text) COORDINATE:NewFromWaypoint(wp):MarkToAll(text)
end end
@@ -1841,81 +1870,96 @@ end
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param #table Template Template used to init the group. Default is `self.template`. -- @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 -- @return #NAVYGROUP self
function NAVYGROUP:_InitGroup(Template) function NAVYGROUP:_InitGroup(Template, Delay)
-- First check if group was already initialized. if Delay and Delay>0 then
if self.groupinitialized then -- Delayed call
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") self:ScheduleOnce(Delay, NAVYGROUP._InitGroup, self, Template, 0)
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
else else
self.isMobile=false
self.speedMax = 0
end
-- Cruise speed: 70% of max speed. -- First check if group was already initialized.
self.speedCruise=self.speedMax*0.7 if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
return
end
-- Group ammo. -- Get template of group.
self.ammo=self:GetAmmoTot() local template=Template or self:_GetTemplate()
-- Radio parameters from template. Default is set on spawn if not modified by the user. -- Ships are always AI.
self.radio.On=true -- Radio is always on for ships. self.isAI=true
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
self.radio.Modu=tonumber(template.units[1].modulation) -- 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. -- Default TACAN off (we check if something is set already to keep those values in case of respawn)
self.optionDefault.Formation="Off Road" if not self.tacanDefault then
self.option.Formation=self.optionDefault.Formation self:SetDefaultTACAN(nil, nil, nil, nil, true)
end
-- Default TACAN off. if not self.tacan then
self:SetDefaultTACAN(nil, nil, nil, nil, true) self.tacan=UTILS.DeepCopy(self.tacanDefault)
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. -- DCS group.
self:SetDefaultICLS(nil, nil, nil, true) local dcsgroup=Group.getByName(self.groupname)
self.icls=UTILS.DeepCopy(self.iclsDefault) local size0=dcsgroup:getInitialSize()
-- Get all units of the group. -- Quick check.
local units=self.group:GetUnits() if #units~=size0 then
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
-- DCS group. end
local dcsgroup=Group.getByName(self.groupname)
local size0=dcsgroup:getInitialSize() -- Add elemets.
for _,unit in pairs(units) do
-- Quick check. self:_AddElementByName(unit:GetName())
if #units~=size0 then end
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
-- Init done.
self.groupinitialized=true
end end
-- Add elemets.
for _,unit in pairs(units) do
self:_AddElementByName(unit:GetName())
end
-- Init done.
self.groupinitialized=true
return self return self
end end

View File

@@ -512,7 +512,7 @@ OPSGROUP.CargoStatus={
--- OpsGroup version. --- OpsGroup version.
-- @field #string version -- @field #string version
OPSGROUP.version="1.0.1" OPSGROUP.version="1.0.4"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -1041,7 +1041,7 @@ function OPSGROUP:SetReturnToLegion(Switch)
else else
self.legionReturn=true self.legionReturn=true
end 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 return self
end end
@@ -1333,8 +1333,9 @@ end
-- @param Core.Point#COORDINATE TargetCoord Coordinate of the target. -- @param Core.Point#COORDINATE TargetCoord Coordinate of the target.
-- @param #number WeaponBitType Weapon type. -- @param #number WeaponBitType Weapon type.
-- @param Core.Point#COORDINATE RefCoord Reference coordinate. -- @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 -- @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 local coordInRange=nil --Core.Point#COORDINATE
@@ -1343,35 +1344,58 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord)
-- Get weapon range. -- Get weapon range.
local weapondata=self:GetWeaponData(WeaponBitType) 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 if weapondata then
-- Heading to target. -- Heading to target.
local heading=RefCoord:HeadingTo(TargetCoord) local heading=TargetCoord:HeadingTo(RefCoord)
-- Distance to target. -- Distance to target.
local dist=RefCoord:Get2DDistance(TargetCoord) 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. -- Check if we are within range.
if dist>weapondata.RangeMax then if range then
local d=(dist-weapondata.RangeMax)*1.05 for _,delta in pairs(dh) do
-- New waypoint coord. local h=heading+delta
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
-- New waypoint coord.
coordInRange=TargetCoord:Translate(range, h)
if _checkSurface(coordInRange) then
break
end
end
else
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Already in range for weapon %s", tostring(WeaponBitType))) self:T(self.lid..string.format("Already in range for weapon %s", tostring(WeaponBitType)))
end end
@@ -1450,11 +1474,14 @@ end
-- @param #number RangeMin Minimum range in nautical miles. Default 0 NM. -- @param #number RangeMin Minimum range in nautical miles. Default 0 NM.
-- @param #number RangeMax Maximum range in nautical miles. Default 10 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 #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 -- @return #OPSGROUP self
function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType) function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType, ConversionToMeters)
RangeMin=UTILS.NMToMeters(RangeMin or 0) ConversionToMeters=ConversionToMeters or UTILS.NMToMeters
RangeMax=UTILS.NMToMeters(RangeMax or 10)
RangeMin=ConversionToMeters(RangeMin or 0)
RangeMax=ConversionToMeters(RangeMax or 10)
local weapon={} --#OPSGROUP.WeaponData local weapon={} --#OPSGROUP.WeaponData
@@ -4169,7 +4196,7 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task)
if self:IsWaiting() then if self:IsWaiting() then
-- Group is already waiting -- Group is already waiting
else else
-- Wait indefinately. -- Wait indefinitely.
local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil
self:Wait(nil, alt) self:Wait(nil, alt)
end end
@@ -4480,7 +4507,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
-- Set speed. Default max. -- Set speed. Default max.
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
if Task.dcstask.params.speed then if Task.dcstask.params.speed then
speed=Task.dcstask.params.speed speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
end end
if target then if target then
@@ -6073,17 +6100,16 @@ function OPSGROUP:RouteToMission(mission, delay)
-- Target Coord. -- Target Coord.
local targetcoord=mission:GetTargetCoordinate() local targetcoord=mission:GetTargetCoordinate()
-- In range already? -- In range already?
local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType) local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType, waypointcoord)
if inRange then if inRange then
waypointcoord=self:GetCoordinate(true) --waypointcoord=self:GetCoordinate(true)
else else
local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord) local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord, surfacetypes)
if coordInRange then if coordInRange then
@@ -6118,7 +6144,32 @@ function OPSGROUP:RouteToMission(mission, delay)
-- Add mission execution (ingress) waypoint. -- Add mission execution (ingress) waypoint.
local waypoint=nil --#OPSGROUP.Waypoint local waypoint=nil --#OPSGROUP.Waypoint
if self:IsFlightgroup() then 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) waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
elseif self:IsArmygroup() then elseif self:IsArmygroup() then
@@ -6157,7 +6208,7 @@ function OPSGROUP:RouteToMission(mission, delay)
if egresscoord then if egresscoord then
local Ewaypoint=nil --#OPSGROUP.Waypoint local Ewaypoint=nil --#OPSGROUP.Waypoint
if self:IsFlightgroup() then 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 elseif self:IsArmygroup() then
Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false) Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false)
elseif self:IsNavygroup() then elseif self:IsNavygroup() then
@@ -7675,6 +7726,7 @@ function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission)
unit.heading=math.rad(heading) unit.heading=math.rad(heading)
unit.psi=-unit.heading unit.psi=-unit.heading
else else
-- Remove unit from spawn template because it is already dead
table.remove(units, i) table.remove(units, i)
end end
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. -- Despawn old group. Dont trigger any remove unit event since this is a respawn.
self:Despawn(0, true) 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 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. -- Debug output.
self:T({Template=Template}) self:T2({Template=Template})
-- Spawn new group. -- Spawn new group.
self.group=_DATABASE:Spawn(Template) 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. -- Set DCS group and controller.
self.dcsgroup=self:GetDCSGroup() self.dcsgroup=self:GetDCSGroup()
@@ -7794,7 +7862,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
self.isDead=false self.isDead=false
self.isDestroyed=false self.isDestroyed=false
self.groupinitialized=false self.groupinitialized=false
self.wpcounter=1 self.wpcounter=1
self.currentwp=1 self.currentwp=1
@@ -7802,15 +7869,12 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
-- Init waypoints. -- Init waypoints.
self:_InitWaypoints() self:_InitWaypoints()
-- Init Group. -- 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) self:_InitGroup(Template, 0.001)
-- Reset events. -- Reset events.
--self:ResetEvents() --self:ResetEvents()
end end
return self
end end
--- On after "InUtero" event. --- On after "InUtero" event.
@@ -7830,24 +7894,6 @@ end
-- @param #string To To state. -- @param #string To To state.
function OPSGROUP:onafterDamaged(From, Event, To) function OPSGROUP:onafterDamaged(From, Event, To)
self:T(self.lid..string.format("Group damaged at t=%.3f", timer.getTime())) 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 end
--- On after "Destroyed" event. --- On after "Destroyed" event.
@@ -11444,10 +11490,10 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
if self:IsFlightgroup() then if self:IsFlightgroup() then
-- Get home and destination airbases from waypoints. -- 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() local destbase=self:GetDestinationFromWaypoints()
self.destbase=self.destbase or destbase 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 home base "..(self.homebase and self.homebase:GetName() or "unknown"))
--env.info("FF dest base "..(self.destbase and self.destbase: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 end
-- Set destination to homebase. -- Set destination to homebase.
if self.destbase==nil then if self.destbase==nil then -- Skipped To fix RTB issue
self.destbase=self.homebase self.destbase=self.homebase
end end

View File

@@ -490,6 +490,19 @@ function OPSZONE:SetDrawZone(Switch)
return self return self
end 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. --- Set if a marker on the F10 map shows the current zone status.
-- @param #OPSZONE self -- @param #OPSZONE self
-- @param #boolean Switch If `true`, zone is marked. If `false` or `nil`, zone is not marked. -- @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 From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @return #OPSZONE self
function OPSZONE:onafterStart(From, Event, To) function OPSZONE:onafterStart(From, Event, To)
-- Info. -- Info.
@@ -726,6 +740,7 @@ function OPSZONE:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.BaseCaptured) self:HandleEvent(EVENTS.BaseCaptured)
end end
return self
end end
--- Stop OPSZONE FSM. --- Stop OPSZONE FSM.
@@ -837,8 +852,12 @@ function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition)
self.zone:UndrawZone() self.zone:UndrawZone()
local color=self:_GetZoneColor() 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
for _,_chief in pairs(self.chiefs) do for _,_chief in pairs(self.chiefs) do
@@ -913,8 +932,12 @@ function OPSZONE:onenterGuarded(From, Event, To)
self.zone:UndrawZone() self.zone:UndrawZone()
local color=self:_GetZoneColor() 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
end end
@@ -954,9 +977,13 @@ function OPSZONE:onenterAttacked(From, Event, To, AttackerCoalition)
-- Color. -- Color.
local color={1, 204/255, 204/255} local color={1, 204/255, 204/255}
local coalition = nil
if self.drawZoneForCoalition then
coalition = self.ownerCurrent
end
-- Draw zone. -- Draw zone.
self.zone:DrawZone(nil, color, 1.0, color, 0.5) self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
end end
self:_CleanMissionTable() self:_CleanMissionTable()
@@ -987,8 +1014,12 @@ function OPSZONE:onenterEmpty(From, Event, To)
self.zone:UndrawZone() self.zone:UndrawZone()
local color=self:_GetZoneColor() 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
end end
@@ -1285,7 +1316,7 @@ function OPSZONE:EvaluateZone()
if Nblu>0 then 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) self:Attacked(coalition.side.BLUE)
end end
@@ -1337,7 +1368,7 @@ function OPSZONE:EvaluateZone()
if Nred>0 then 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. -- Red is attacking blue zone.
self:Attacked(coalition.side.RED) self:Attacked(coalition.side.RED)
end end

View File

@@ -80,6 +80,7 @@
-- @field #boolean smokeownposition -- @field #boolean smokeownposition
-- @field #table SmokeOwn -- @field #table SmokeOwn
-- @field #boolean smokeaveragetargetpos -- @field #boolean smokeaveragetargetpos
-- @field #boolean reporttostringbullsonly
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- ---
@@ -105,7 +106,7 @@ PLAYERRECCE = {
ClassName = "PLAYERRECCE", ClassName = "PLAYERRECCE",
verbose = true, verbose = true,
lid = nil, lid = nil,
version = "0.1.23", version = "0.1.26",
ViewZone = {}, ViewZone = {},
ViewZoneVisual = {}, ViewZoneVisual = {},
ViewZoneLaser = {}, ViewZoneLaser = {},
@@ -133,7 +134,8 @@ PLAYERRECCE = {
TargetCache = nil, TargetCache = nil,
smokeownposition = false, smokeownposition = false,
SmokeOwn = {}, SmokeOwn = {},
smokeaveragetargetpos = false, smokeaveragetargetpos = true,
reporttostringbullsonly = true,
} }
--- ---
@@ -152,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = {
["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 },
["SA342L"] = { 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"] = { 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, ["SA342Minigun"] = 8000,
["SA342L"] = 8000, ["SA342L"] = 8000,
["Ka-50"] = 8000, ["Ka-50"] = 8000,
["Ka-50_3"] = 8000, ["Ka-50_3"] = 8000,
["OH58D"] = 8000,
} }
--- ---
@@ -176,7 +180,8 @@ PLAYERRECCE.Cameraheight = {
["SA342Minigun"] = 2.85, ["SA342Minigun"] = 2.85,
["SA342L"] = 2.85, ["SA342L"] = 2.85,
["Ka-50"] = 0.5, ["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 ["SA342Minigun"] = false, -- no optics
["SA342L"] = true, ["SA342L"] = true,
["Ka-50"] = 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.minthreatlevel = 0
self.reporttostringbullsonly = true
self.TForget = 600 self.TForget = 600
self.TargetCache = FIFO:New() self.TargetCache = FIFO:New()
@@ -542,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet)
return self return self
end end
---[Internal] Check Gazelle camera in on ---[Internal] Check Helicopter camera in on
-- @param #PLAYERRECCE self -- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client -- @param Wrapper.Client#CLIENT client
-- @param #string playername -- @param #string playername
@@ -558,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername)
if vivihorizontal < -0.7 or vivihorizontal > 0.7 then if vivihorizontal < -0.7 or vivihorizontal > 0.7 then
camera = false camera = false
end 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 elseif string.find(typename,"Ka-50") then
camera = true camera = true
end end
@@ -565,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername)
return camera return camera
end 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 --- [Internal] Get the view parameters from a Gazelle camera
-- @param #PLAYERRECCE self -- @param #PLAYERRECCE self
-- @param Wrapper.Unit#UNIT Gazelle -- @param Wrapper.Unit#UNIT Gazelle
@@ -593,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle)
vivioff = true vivioff = true
return 0,0,0,false return 0,0,0,false
end end
vivivertical = vivivertical / 1.10731 -- normalize
local horizontalview = vivihorizontal * -180 local horizontalview = vivihorizontal * -180
local verticalview = vivivertical * 30 -- ca +/- 30° --local verticalview = vivivertical * 30 -- ca +/- 30°
--self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical)) local verticalview = math.atan(vivivertical)
--self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview))
local heading = unit:GetHeading() local heading = unit:GetHeading()
local viviheading = (heading+horizontalview)%360 local viviheading = (heading+horizontalview)%360
local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) 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 if maxview > 8000 then maxview = 8000 end
--self:I(string.format("corrected maxview=%.5f",maxview))
return viviheading, verticalview,maxview, not vivioff return viviheading, verticalview,maxview, not vivioff
end end
return 0,0,0,false return 0,0,0,false
@@ -647,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff)
if unit and unit:IsAlive() then if unit and unit:IsAlive() then
local typename = unit:GetTypeName() local typename = unit:GetTypeName()
maxview = self.MaxViewDistance[typename] or 8000 maxview = self.MaxViewDistance[typename] or 8000
local CamHeight = self.Cameraheight[typename] or 0 local CamHeight = self.Cameraheight[typename] or 1
if vnod < 0 then if vnod < -2 then
-- Looking down -- Looking down
-- determine max distance we're looking at -- determine max distance we're looking at
local beta = 90 local beta = 90
local gamma = math.floor(90-vnod) local gamma = 90-math.abs(vnod)
local alpha = math.floor(180-beta-gamma) local alpha = 90-gamma
local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight
local b = a / math.sin(math.rad(alpha)) local b = a / math.sin(math.rad(alpha))
local c = b * math.sin(math.rad(gamma)) local c = b * math.sin(math.rad(gamma))
maxview = c*1.2 -- +20% maxview = c*1.2 -- +20%
end end
end end
return math.abs(maxview) return math.ceil(math.abs(maxview))
end end
--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. --- [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 #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 -- @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. -- 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 -- @return #PLAYERRECCE self
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign == false then if not ShortCallsign or ShortCallsign == false then
self.ShortCallsign = false self.ShortCallsign = false
else else
@@ -678,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla
end end
self.Keepnumber = Keepnumber or false self.Keepnumber = Keepnumber or false
self.CallsignTranslations = CallsignTranslations self.CallsignTranslations = CallsignTranslations
self.CallsignCustomFunc = CallsignCustomFunc
self.CallsignCustomArgs = arg or {}
return self return self
end end
@@ -799,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
local minview = 0 local minview = 0
local typename = unit:GetTypeName() local typename = unit:GetTypeName()
local playername = unit:GetPlayerName() 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 heading,nod,maxview,angle = 0,30,8000,10
local camon = false local camon = false
local name = unit:GetName() local name = unit:GetName()
@@ -807,16 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit)
angle=10 angle=10
-- Model nod and actual TV view don't compute -- 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 elseif string.find(typename,"Ka-50") and camera then
heading = unit:GetHeading() heading = unit:GetHeading()
nod,maxview,camon = 10,1000,true nod,maxview,camon = 10,1000,true
angle = 10 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 else
-- visual -- visual
heading = unit:GetHeading() heading = unit:GetHeading()
nod,maxview,camon = 10,1000,true nod,maxview,camon = 10,3000,true
maxview = self.MaxViewDistance[typename] or 3000
angle = 45 angle = 45
end end
if laser then 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 if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
-- lost LOS or dead -- lost LOS or dead
laser:LaseOff() 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:__Shack(-1,client,oldtarget)
--self.LaserTarget[playername] = nil --self.LaserTarget[playername] = nil
else else
@@ -1274,6 +1323,9 @@ self:T(self.lid.."_ReportLaserTargets")
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
if self.reporttostringbullsonly ~= true then
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
end
else else
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
end end
@@ -1317,8 +1369,14 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername)
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
if self.reporttostringbullsonly ~= true then
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
end
else else
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) 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 end
report:Add(string.rep("-",15)) report:Add(string.rep("-",15))
local text = report:Text() local text = report:Text()
@@ -1347,6 +1405,7 @@ function PLAYERRECCE:_BuildMenus(Client)
local client = _client -- Wrapper.Client#CLIENT local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then if client and client:IsAlive() then
local playername = client:GetPlayerName() local playername = client:GetPlayerName()
self:T("Menu for "..playername)
if not self.UnitLaserCodes[playername] then if not self.UnitLaserCodes[playername] then
self:_SetClientLaserCode(nil,nil,playername,1688) self:_SetClientLaserCode(nil,nil,playername,1688)
end end
@@ -1355,6 +1414,7 @@ function PLAYERRECCE:_BuildMenus(Client)
end end
local group = client:GetGroup() local group = client:GetGroup()
if not self.ClientMenus[playername] then if not self.ClientMenus[playername] then
self:T("Start Menubuild for "..playername)
local canlase = self.CanLase[client:GetTypeName()] local canlase = self.CanLase[client:GetTypeName()]
self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE") self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE")
local txtonstation = self.OnStation[playername] and "ON" or "OFF" 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. -- 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 #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 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 -- @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:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
self.Gender = Gender or MSRS.gender or "male" -- 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:SetCulture(self.Culture)
self.SRS:SetPort(self.Port) self.SRS:SetPort(self.Port)
self.SRS:SetVolume(self.Volume) self.SRS:SetVolume(self.Volume)
if Backend then
self.SRS:SetBackend(Backend)
end
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE) self.SRS:SetProvider(MSRS.Provider.GOOGLE)
@@ -1552,6 +1616,16 @@ function PLAYERRECCE:SetMenuName(Name)
return self return self
end 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 --- [User] Enable smoking of own position
-- @param #PLAYERRECCE self -- @param #PLAYERRECCE self
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
@@ -1561,6 +1635,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition()
return self return self
end 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 --- [User] Disable smoking of own position
-- @param #PLAYERRECCE self -- @param #PLAYERRECCE self
-- @return #PLAYERRECCE -- @return #PLAYERRECCE
@@ -1718,7 +1801,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
self:T({From, Event, To}) 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 coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition) local coordtext = coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then if self.ReferencePoint then
@@ -1727,7 +1810,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
end end
local text1 = "Party time!" local text1 = "Party time!"
local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) 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) text2tts = self:_GetTextForSpeech(text2tts)
if self.debug then if self.debug then
self:T(text2.."\n"..text2tts) self:T(text2.."\n"..text2tts)
@@ -1758,7 +1841,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
self:T({From, Event, To}) 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 coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition) local coordtext = coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then if self.ReferencePoint then
@@ -1898,7 +1981,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet) function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet)
self:T({From, Event, To}) 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 coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition) local coordtext = coord:ToStringBULLS(self.Coalition)
if self.AttackSet then if self.AttackSet then
@@ -1941,7 +2024,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet)
self:T({From, Event, To}) 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 coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition) local coordtext = coord:ToStringBULLS(self.Coalition)
if self.AttackSet then if self.AttackSet then
@@ -1984,7 +2067,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet)
self:T({From, Event, To}) 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 coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition) local coordtext = coord:ToStringBULLS(self.Coalition)
if self.AttackSet then if self.AttackSet then
@@ -2028,7 +2111,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime)
self:T({From, Event, To}) 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 Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
local coord = Client:GetCoordinate() local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition,Settings) local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
@@ -2075,7 +2158,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterShack(From, Event, To, Client, Target) function PLAYERRECCE:onafterShack(From, Event, To, Client, Target)
self:T({From, Event, To}) 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 Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
local coord = Client:GetCoordinate() local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition,Settings) local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
@@ -2122,7 +2205,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target)
self:T({From, Event, To}) 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 Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
local coord = Client:GetCoordinate() local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition,Settings) local coordtext = coord:ToStringBULLS(self.Coalition,Settings)

File diff suppressed because it is too large Load Diff

View File

@@ -997,6 +997,8 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
-- Init status updates in 10 seconds. -- Init status updates in 10 seconds.
self:__Status(10) self:__Status(10)
return self
end end

View File

@@ -963,6 +963,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- Init status check -- Init status check
self:__Status(1) self:__Status(1)
return self
end end
--- On after Status event. Checks player status. --- On after Status event. Checks player status.

View File

@@ -153,13 +153,13 @@ _TARGETID=0
--- TARGET class version. --- TARGET class version.
-- @field #string version -- @field #string version
TARGET.version="0.6.0" TARGET.version="0.7.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- 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: Add pseudo functions.
-- DONE: Initial object can be nil. -- DONE: Initial object can be nil.
@@ -243,6 +243,36 @@ function TARGET:New(TargetObject)
-- @function [parent=#TARGET] __Status -- @function [parent=#TARGET] __Status
-- @param #TARGET self -- @param #TARGET self
-- @param #number delay Delay in seconds. -- @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 --- 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 -- @function [parent=#TARGET] OnAfterObjectDamaged
@@ -267,22 +297,23 @@ function TARGET:New(TargetObject)
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #TARGET.Object Target Target object. -- @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 -- @function [parent=#TARGET] OnAfterDamaged
-- @param #TARGET self -- @param #TARGET self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @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 -- @function [parent=#TARGET] OnAfterDestroyed
-- @param #TARGET self -- @param #TARGET self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @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 -- @function [parent=#TARGET] OnAfterDead
-- @param #TARGET self -- @param #TARGET self
-- @param #string From From state. -- @param #string From From state.
@@ -290,7 +321,7 @@ function TARGET:New(TargetObject)
-- @param #string To To state. -- @param #string To To state.
-- Start. -- Start.
self:__Start(-1) self:__Start(-0.1)
return self return self
end end
@@ -356,6 +387,8 @@ function TARGET:AddObject(Object)
if Object:IsInstanceOf("OPSGROUP") then if Object:IsInstanceOf("OPSGROUP") then
self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object. self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object.
--elseif Object:IsInstanceOf("OPSZONE") then
--self:_AddObject(Object:GetZone())
else else
self:_AddObject(Object) self:_AddObject(Object)
end end
@@ -527,6 +560,11 @@ function TARGET:IsAlive()
for _,_target in pairs(self.targets) do for _,_target in pairs(self.targets) do
local target=_target --Ops.Target#TARGET.Object local target=_target --Ops.Target#TARGET.Object
if target.Status~=TARGET.ObjectStatus.DEAD then 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 return true
end end
end end
@@ -550,6 +588,25 @@ function TARGET:IsDead()
return is return is
end 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 -- Start & Status
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -581,7 +638,8 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function TARGET:onafterStatus(From, Event, To) function TARGET:onafterStatus(From, Event, To)
self:T({From, Event, To}) --self:T({From, Event, To})
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
@@ -592,6 +650,7 @@ function TARGET:onafterStatus(From, Event, To)
-- old life -- old life
local life=target.Life local life=target.Life
-- curr life -- curr life
target.Life=self:GetTargetLife(target) target.Life=self:GetTargetLife(target)
@@ -603,13 +662,14 @@ function TARGET:onafterStatus(From, Event, To)
self.life0 = self.life0+delta self.life0 = self.life0+delta
end end
-- Check if life decreased ==> damaged
if target.Life<life then if target.Life<life then
target.Status = TARGET.ObjectStatus.DAMAGED --target.Status = TARGET.ObjectStatus.DAMAGED
self:ObjectDamaged(target) self:ObjectDamaged(target)
damaged=true damaged=true
end 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: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) self:ObjectDead(target)
damaged = true damaged = true
@@ -624,11 +684,12 @@ function TARGET:onafterStatus(From, Event, To)
-- Log output verbose=1. -- Log output verbose=1.
if self.verbose>=1 then 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 if self:CountTargets() == 0 or self:GetDamage() >= 100 then
text=text.." Dead!" text=text.." - Dead!"
elseif damaged then elseif damaged then
text=text.." Damaged!" text=text.." - Damaged!"
end end
self:I(self.lid..text) self:I(self.lid..text)
end end
@@ -639,19 +700,35 @@ function TARGET:onafterStatus(From, Event, To)
for i,_target in pairs(self.targets) do for i,_target in pairs(self.targets) do
local target=_target --#TARGET.Object local target=_target --#TARGET.Object
local damage=(1-target.Life/target.Life0)*100 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 end
self:I(self.lid..text) self:I(self.lid..text)
end 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() self:Dead()
end 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. -- Update status again in 30 sec.
if self:IsAlive() then if self:IsAlive() then
self:__Status(-self.TStatus) 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 end
return self return self
end end
@@ -687,6 +764,10 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
-- Increase destroyed counter. -- Increase destroyed counter.
self.Ndestroyed=self.Ndestroyed+1 self.Ndestroyed=self.Ndestroyed+1
Target.Ndestroyed=Target.Ndestroyed+1
Target.Life=0
-- Call object dead event. -- Call object dead event.
self:ObjectDead(Target) self:ObjectDead(Target)
@@ -707,6 +788,12 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
-- Set target status. -- Set target status.
Target.Status=TARGET.ObjectStatus.DEAD 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. -- Increase dead counter.
self.Ndead=self.Ndead+1 self.Ndead=self.Ndead+1
@@ -716,6 +803,7 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
local target=_target --#TARGET.Object local target=_target --#TARGET.Object
if target.Status==TARGET.ObjectStatus.ALIVE then if target.Status==TARGET.ObjectStatus.ALIVE then
dead=false dead=false
break -- break the loop because we now we are not dead
end end
end end
@@ -759,7 +847,6 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function TARGET:onafterDestroyed(From, Event, To) function TARGET:onafterDestroyed(From, Event, To)
self:T({From, Event, To}) self:T({From, Event, To})
self:T(self.lid..string.format("TARGET destroyed")) self:T(self.lid..string.format("TARGET destroyed"))
@@ -813,23 +900,27 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
-- Check if we could find a target object. -- Check if we could find a target object.
if target then if target then
local Ndead=target.Ndead
local Ndestroyed=target.Ndestroyed
if EventData.id==EVENTS.RemoveUnit then if EventData.id==EVENTS.RemoveUnit then
target.Ndead=target.Ndead+1 Ndead=Ndead+1
else else
target.Ndestroyed=target.Ndestroyed+1 Ndestroyed=Ndestroyed+1
target.Ndead=target.Ndead+1 Ndead=Ndead+1
end 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. -- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name))) self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
target.Life = 0 target.Life = 0
-- Trigger object destroyed event. -- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
self:ObjectDestroyed(target) self:ObjectDestroyed(target)
else else
@@ -839,7 +930,7 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
target.Life = 0 target.Life = 0
-- Trigger object dead event. -- Trigger object dead event. This sets the Life to zero and increases Ndead counter
self:ObjectDead(target) self:ObjectDead(target)
end end
@@ -979,6 +1070,8 @@ function TARGET:_AddObject(Object)
target.Life0=1 target.Life0=1
target.Life=1 target.Life=1
target.N0=target.N0+1
elseif Object:IsInstanceOf("ZONE_BASE") then elseif Object:IsInstanceOf("ZONE_BASE") then
@@ -992,6 +1085,8 @@ function TARGET:_AddObject(Object)
target.Life0=1 target.Life0=1
target.Life=1 target.Life=1
target.N0=target.N0+1
elseif Object:IsInstanceOf("OPSZONE") then elseif Object:IsInstanceOf("OPSZONE") then
@@ -1203,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target)
return 0 return 0
elseif Target.Type==TARGET.ObjectType.ZONE then 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 else
self:E("ERROR: unknown target object type in GetTargetThreatLevel!") self:E("ERROR: unknown target object type in GetTargetThreatLevel!")
return 0
end end
return self return self
@@ -1919,12 +2030,16 @@ function TARGET:CountObjectives(Target, Coalitions)
elseif Target.Type==TARGET.ObjectType.COORDINATE then 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 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 elseif Target.Type==TARGET.ObjectType.OPSZONE then
local target=Target.Object --Ops.OpsZone#OPSZONE local target=Target.Object --Ops.OpsZone#OPSZONE

View File

@@ -52,6 +52,7 @@
-- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`. -- @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 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 #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 -- @extends Core.Base#BASE
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -256,15 +257,145 @@ MSRS = {
ConfigFilePath = "Config\\", ConfigFilePath = "Config\\",
ConfigLoaded = false, ConfigLoaded = false,
poptions = {}, poptions = {},
UsePowerShell = false,
} }
--- MSRS class version. --- MSRS class version.
-- @field #string version -- @field #string version
MSRS.version="0.3.0" MSRS.version="0.3.3"
--- Voices --- Voices
-- @type MSRS.Voices -- @type MSRS.Voices
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 Microsoft = { -- working ones if not using gRPC and MS
["Hedda"] = "Microsoft Hedda Desktop", -- de-DE ["Hedda"] = "Microsoft Hedda Desktop", -- de-DE
["Hazel"] = "Microsoft Hazel Desktop", -- en-GB ["Hazel"] = "Microsoft Hazel Desktop", -- en-GB
@@ -316,11 +447,14 @@ MSRS.Voices = {
["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE
["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE
["en_GB_Standard_A"] = 'en-GB-Standard-A', -- [9] FEMALE -- 2025 changes
["en_GB_Standard_B"] = 'en-GB-Standard-B', -- [10] MALE ["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE
["en_GB_Standard_C"] = 'en-GB-Standard-C', -- [11] FEMALE ["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE
["en_GB_Standard_D"] = 'en-GB-Standard-D', -- [12] MALE ["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE
["en_GB_Standard_F"] = 'en-GB-Standard-F', -- [13] 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_A"] = 'en-US-Standard-A', -- [14] MALE
["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE
["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE
@@ -331,25 +465,36 @@ MSRS.Voices = {
["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE
["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE
["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE
["fr_FR_Standard_A"] = "fr-FR-Standard-A", -- Female -- 2025 catalog changes
["fr_FR_Standard_B"] = "fr-FR-Standard-B", -- Male ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female
["fr_FR_Standard_C"] = "fr-FR-Standard-C", -- Female ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male
["fr_FR_Standard_D"] = "fr-FR-Standard-D", -- Male ["fr_FR_Standard_C"] = "fr-FR-Standard-F", -- Female
["fr_FR_Standard_E"] = "fr-FR-Standard-E", -- Female ["fr_FR_Standard_D"] = "fr-FR-Standard-G", -- Male
["de_DE_Standard_A"] = "de-DE-Standard-A", -- Female ["fr_FR_Standard_E"] = "fr-FR-Standard-F", -- Female
["de_DE_Standard_B"] = "de-DE-Standard-B", -- Male ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male
["de_DE_Standard_C"] = "de-DE-Standard-C", -- Female ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female
["de_DE_Standard_D"] = "de-DE-Standard-D", -- Male -- 2025 catalog changes
["de_DE_Standard_E"] = "de-DE-Standard-E", -- Male ["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female
["de_DE_Standard_F"] = "de-DE-Standard-F", -- Female ["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male
["es_ES_Standard_A"] = "es-ES-Standard-A", -- Female ["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female
["es_ES_Standard_B"] = "es-ES-Standard-B", -- Male ["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male
["es_ES_Standard_C"] = "es-ES-Standard-C", -- Female ["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male
["es_ES_Standard_D"] = "es-ES-Standard-D", -- Female ["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female
["it_IT_Standard_A"] = "it-IT-Standard-A", -- Female ["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male
["it_IT_Standard_B"] = "it-IT-Standard-B", -- Female ["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female
["it_IT_Standard_C"] = "it-IT-Standard-C", -- Male ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female
["it_IT_Standard_D"] = "it-IT-Standard-D", -- Male ["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 = { Wavenet = {
["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE
@@ -360,12 +505,15 @@ MSRS.Voices = {
["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE
["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE -- 2025 changes
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE
["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] 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_B"] = 'en-US-Wavenet-B', -- [15] MALE
["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE
["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE
@@ -375,24 +523,35 @@ MSRS.Voices = {
["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE
["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-A", -- Female -- 2025 catalog changes
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-B", -- Male ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female
["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-C", -- Female ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male
["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-D", -- Male ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-F", -- Female
["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-E", -- Female ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-G", -- Male
["de_DE_Wavenet_A"] = "de-DE-Wavenet-A", -- Female ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-F", -- Female
["de_DE_Wavenet_B"] = "de-DE-Wavenet-B", -- Male ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male
["de_DE_Wavenet_C"] = "de-DE-Wavenet-C", -- Female ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female
["de_DE_Wavenet_D"] = "de-DE-Wavenet-D", -- Male -- 2025 catalog changes
["de_DE_Wavenet_E"] = "de-DE-Wavenet-E", -- Male ["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female
["de_DE_Wavenet_F"] = "de-DE-Wavenet-F", -- Female ["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male
["es_ES_Wavenet_B"] = "es-ES-Wavenet-B", -- Male ["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female
["es_ES_Wavenet_C"] = "es-ES-Wavenet-C", -- Female ["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male
["es_ES_Wavenet_D"] = "es-ES-Wavenet-D", -- Female ["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male
["it_IT_Wavenet_A"] = "it-IT-Wavenet-A", -- Female ["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female
["it_IT_Wavenet_B"] = "it-IT-Wavenet-B", -- Female ["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male
["it_IT_Wavenet_C"] = "it-IT-Wavenet-C", -- Male ["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female
["it_IT_Wavenet_D"] = "it-IT-Wavenet-D", -- Male ["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
} , } ,
}, },
} }
@@ -588,7 +747,7 @@ function MSRS:SetBackendSRSEXE()
end end
--- Set the default backend. --- Set the default backend.
-- @param #MSRS self -- @param #string Backend
function MSRS.SetDefaultBackend(Backend) function MSRS.SetDefaultBackend(Backend)
MSRS.backend=Backend or MSRS.Backend.SRSEXE MSRS.backend=Backend or MSRS.Backend.SRSEXE
end end
@@ -944,7 +1103,7 @@ end
-- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default) -- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default)
-- - `MSRS.Provider.GOOGLE`: Google Cloud -- - `MSRS.Provider.GOOGLE`: Google Cloud
-- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend) -- - `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. -- Note that all providers except Microsoft Windows need as additonal information the credentials of your account.
-- --
@@ -1154,7 +1313,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
-- Append file. -- Append file.
command=command..' --file="'..tostring(soundfile)..'"' command=command..' --file="'..tostring(soundfile)..'"'
command=string.gsub(command,"--ssml","-h")
-- Execute command. -- Execute command.
self:_ExecCommand(command) self:_ExecCommand(command)
@@ -1375,20 +1535,25 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
modus=modus:gsub("1", "FM") modus=modus:gsub("1", "FM")
-- Command. -- 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) 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. -- 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. -- Use a specific voice (no need for gender and/or culture.
command=command..string.format(" --voice=\"%s\"", tostring(voice)) command=command..string.format(" --voice=\"%s\"", tostring(voice))
pwsh=pwsh..string.format(" --voice=\"%s\"", tostring(voice))
else else
-- Add gender. -- Add gender.
if gender and gender~="female" then if gender and gender~="female" then
command=command..string.format(" -g %s", tostring(gender)) command=command..string.format(" -g %s", tostring(gender))
pwsh=pwsh..string.format(" -g %s", tostring(gender))
end end
-- Add culture. -- Add culture.
if culture and culture~="en-GB" then if culture and culture~="en-GB" then
command=command..string.format(" -l %s", tostring(culture)) command=command..string.format(" -l %s", tostring(culture))
pwsh=pwsh..string.format(" -l %s", tostring(culture))
end end
end end
@@ -1396,16 +1561,18 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
if coordinate then if coordinate then
local lat,lon,alt=self:_GetLatLongAlt(coordinate) local lat,lon,alt=self:_GetLatLongAlt(coordinate)
command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) 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 end
-- Set provider options -- Set provider options
if self.provider==MSRS.Provider.GOOGLE then if self.provider==MSRS.Provider.GOOGLE then
local pops=self:GetProviderOptions() local pops=self:GetProviderOptions()
command=command..string.format(' --ssml -G "%s"', pops.credentials) 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 elseif self.provider==MSRS.Provider.WINDOWS then
-- Nothing to do. -- Nothing to do.
else 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 end
if not UTILS.FileExists(fullPath) then if not UTILS.FileExists(fullPath) then
@@ -1415,8 +1582,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
-- Debug output. -- Debug output.
self:T("MSRS command from _GetCommand="..command) self:T("MSRS command from _GetCommand="..command)
return command if self.UsePowerShell == true then
return pwsh
else
return command
end
end end
--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. --- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
@@ -1424,7 +1595,7 @@ end
-- @param #string command Command to executer -- @param #string command Command to executer
-- @return #number Return value of os.execute() command. -- @return #number Return value of os.execute() command.
function MSRS:_ExecCommand(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 -- Skip this function if _GetCommand was not able to find the executable
if string.find(command, "CommandNotFound") then return 0 end if string.find(command, "CommandNotFound") then return 0 end
@@ -1432,7 +1603,13 @@ function MSRS:_ExecCommand(command)
local batContent = command.." && exit" local batContent = command.." && exit"
-- Create a tmp file. -- Create a tmp file.
local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" 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+") local script=io.open(filename, "w+")
script:write(batContent) script:write(batContent)
script:close() script:close()
@@ -1441,7 +1618,7 @@ function MSRS:_ExecCommand(command)
self:T("MSRS batch content: "..batContent) self:T("MSRS batch content: "..batContent)
local res=nil local res=nil
if true then if self.UsePowerShell ~= true then
-- Create a tmp file. -- Create a tmp file.
local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs" local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs"
@@ -1469,23 +1646,20 @@ function MSRS:_ExecCommand(command)
timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1) timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1)
self:T("MSRS vbs and batch file removed") self:T("MSRS vbs and batch file removed")
elseif false then elseif self.UsePowerShell == true 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)
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 -- 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 else
-- Play command. -- Play command.
@@ -1616,11 +1790,11 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
ssml=string.format("<voice%s%s>%s</voice>", gender, language, Text) ssml=string.format("<voice%s%s>%s</voice>", gender, language, Text)
end end
end end
for _,freq in pairs(Frequencies) do for _,freq in pairs(Frequencies) do
self:F("Calling GRPC.tts with the following parameter:") self:T("Calling GRPC.tts with the following parameter:")
self:F({ssml=ssml, freq=freq, options=options}) self:T({ssml=ssml, freq=freq, options=options})
self:F(options.provider[provider]) self:T(options.provider[provider])
GRPC.tts(ssml, freq*1e6, options) GRPC.tts(ssml, freq*1e6, options)
end end
@@ -1942,7 +2116,7 @@ end
-- @param Core.Point#COORDINATE coordinate Coordinate to be used -- @param Core.Point#COORDINATE coordinate Coordinate to be used
-- @return #MSRSQUEUE.Transmission Radio transmission table. -- @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) 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.TransmitOnlyWithPlayers then
if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then
return self return self
@@ -1982,7 +2156,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr
transmission.volume = volume or msrs.volume transmission.volume = volume or msrs.volume
transmission.label = label or msrs.Label transmission.label = label or msrs.Label
transmission.coordinate = coordinate or msrs.coordinate transmission.coordinate = coordinate or msrs.coordinate
-- Add transmission to queue. -- Add transmission to queue.
self:AddTransmission(transmission) self:AddTransmission(transmission)

View File

@@ -5,7 +5,7 @@
-- ## Features: -- ## Features:
-- --
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions -- * 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)
-- --
-- === -- ===
-- --

View File

@@ -1166,6 +1166,127 @@ 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.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.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 -- Gazelle
ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771} 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_200RDS = {4,15,46,1770}
@@ -1177,16 +1298,16 @@ 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.GIAT_M261_APHE = {4,15,46,1764}
ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680}
ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679}
-- Chinook -- Chinook (changed)
ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2476} ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2489}
ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2477} ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2488}
ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2478} ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2490}
ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2482} ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2494}
ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2483} ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2495}
ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2484} ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2496} --
ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2479} ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2492}
ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2480} ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2491}
ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2481} ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2493}
-- Huey -- Huey
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right = {4,15,46,161} 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_Left = {4,15,46,160}
@@ -1195,20 +1316,20 @@ 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.M134_MiniGun_Left_Door = {4,15,46,174}
ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door = {4,15,46,176} ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door = {4,15,46,176}
-- Kiowa -- Kiowa
ENUMS.Storage.weapons.OH58.FIM92 = {4,4,7,446} ENUMS.Storage.weapons.OH58.FIM92 = {4,4,7,449}
ENUMS.Storage.weapons.OH58.MG_M3P100 = {4,15,46,2578} ENUMS.Storage.weapons.OH58.MG_M3P100 = {4,15,46,2611}
ENUMS.Storage.weapons.OH58.MG_M3P200 = {4,15,46,2577} ENUMS.Storage.weapons.OH58.MG_M3P200 = {4,15,46,2610}
ENUMS.Storage.weapons.OH58.MG_M3P300 = {4,15,46,2576} ENUMS.Storage.weapons.OH58.MG_M3P300 = {4,15,46,2609}
ENUMS.Storage.weapons.OH58.MG_M3P400 = {4,15,46,2575} ENUMS.Storage.weapons.OH58.MG_M3P400 = {4,15,46,2608}
ENUMS.Storage.weapons.OH58.MG_M3P500 = {4,15,46,2574} ENUMS.Storage.weapons.OH58.MG_M3P500 = {4,15,46,2607}
ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue = {4,5,9,484} ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue = {4,5,9,488}
ENUMS.Storage.weapons.OH58.Smk_Grenade_Green = {4,5,9,485} ENUMS.Storage.weapons.OH58.Smk_Grenade_Green = {4,5,9,489}
ENUMS.Storage.weapons.OH58.Smk_Grenade_Red = {4,5,9,483} ENUMS.Storage.weapons.OH58.Smk_Grenade_Red = {4,5,9,487}
ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet = {4,5,9,486} ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet = {4,5,9,490}
ENUMS.Storage.weapons.OH58.Smk_Grenade_White = {4,5,9,488} ENUMS.Storage.weapons.OH58.Smk_Grenade_White = {4,5,9,492}
ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow = {4,5,9,487} ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow = {4,5,9,491}
-- Apache -- Apache
ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2138} ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2114}
ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank = {1,3,43,1700} ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank = {1,3,43,1700}
--- ---
@@ -1237,3 +1358,4 @@ ENUMS.FARPObjectTypeNamesAndShape ={
[ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"}, [ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"},
[ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"}, [ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"},
} }

View File

@@ -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

View File

@@ -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*
--
-- ===
--
-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg)
--
-- 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"]
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -56,6 +56,8 @@ BIGSMOKEPRESET = {
-- @field #string Falklands South Atlantic map. -- @field #string Falklands South Atlantic map.
-- @field #string Sinai Sinai map. -- @field #string Sinai Sinai map.
-- @field #string Kola Kola map. -- @field #string Kola Kola map.
-- @field #string Afghanistan Afghanistan map
-- @field #string Iraq Iraq map
DCSMAP = { DCSMAP = {
Caucasus="Caucasus", Caucasus="Caucasus",
NTTR="Nevada", NTTR="Nevada",
@@ -68,6 +70,7 @@ DCSMAP = {
Sinai="SinaiMap", Sinai="SinaiMap",
Kola="Kola", Kola="Kola",
Afghanistan="Afghanistan", Afghanistan="Afghanistan",
Iraq="Iraq"
} }
@@ -511,7 +514,7 @@ function UTILS.PrintTableToLog(table, indent, noprint)
env.info(string.rep(" ", indent) .. tostring(k) .. " = {") env.info(string.rep(" ", indent) .. tostring(k) .. " = {")
end end
text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n" 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 if not noprint then
env.info(string.rep(" ", indent) .. "},") env.info(string.rep(" ", indent) .. "},")
end end
@@ -1786,6 +1789,8 @@ function UTILS.GetMagneticDeclination(map)
declination=15 declination=15
elseif map==DCSMAP.Afghanistan then elseif map==DCSMAP.Afghanistan then
declination=3 declination=3
elseif map==DCSMAP.Iraq then
declination=4.4
else else
declination=0 declination=0
end end
@@ -2315,9 +2320,9 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true return true
end end
if type_name == "OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then if type_name == "OH58D" then
BASE:T(unit_name .. " cargo door is open") BASE:T(unit_name .. " front door(s) are open")
return true return true -- no doors on this one ;)
end end
if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0.5) then if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0.5) then
@@ -2523,7 +2528,7 @@ end
--- Function to save an object to a file --- Function to save an object to a file
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @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 #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. -- @return #boolean outcome True if saving is possible, else false.
function UTILS.SaveToFile(Path,Filename,Data) function UTILS.SaveToFile(Path,Filename,Data)
-- Thanks to @FunkyFranky -- Thanks to @FunkyFranky
@@ -4199,3 +4204,198 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
return ReturnObjects, ADFName return ReturnObjects, ADFName
end 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

View File

@@ -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, -- 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() -- 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}(). -- 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 -- @field #AIRBASE AIRBASE
AIRBASE = { AIRBASE = {
@@ -248,6 +253,9 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Villacoublay -- * AIRBASE.Normandy.Villacoublay
-- * AIRBASE.Normandy.Vrigny -- * AIRBASE.Normandy.Vrigny
-- * AIRBASE.Normandy.West_Malling -- * AIRBASE.Normandy.West_Malling
-- * AIRBASE.Normandy.Eastchurch
-- * AIRBASE.Normandy.Headcorn
-- * AIRBASE.Normandy.Hawkinge
-- --
-- @field Normandy -- @field Normandy
AIRBASE.Normandy = { AIRBASE.Normandy = {
@@ -330,6 +338,9 @@ AIRBASE.Normandy = {
["Villacoublay"] = "Villacoublay", ["Villacoublay"] = "Villacoublay",
["Vrigny"] = "Vrigny", ["Vrigny"] = "Vrigny",
["West_Malling"] = "West Malling", ["West_Malling"] = "West Malling",
["Eastchurch"] = "Eastchurch",
["Headcorn"] = "Headcorn",
["Hawkinge"] = "Hawkinge",
} }
--- Airbases of the Persion Gulf Map: --- Airbases of the Persion Gulf Map:
@@ -450,6 +461,7 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Gaziantep -- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.Gazipasa -- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Gecitkale -- * AIRBASE.Syria.Gecitkale
-- * AIRBASE.Syria.H
-- * AIRBASE.Syria.H3 -- * AIRBASE.Syria.H3
-- * AIRBASE.Syria.H3_Northwest -- * AIRBASE.Syria.H3_Northwest
-- * AIRBASE.Syria.H3_Southwest -- * AIRBASE.Syria.H3_Southwest
@@ -497,6 +509,10 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Tha_lah -- * AIRBASE.Syria.Tha_lah
-- * AIRBASE.Syria.Tiyas -- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Ben_Gurion
-- * AIRBASE.Syria.Hatzor
-- * AIRBASE.Syria.Palmashim
-- * AIRBASE.Syria.Tel_Nof
-- --
--@field Syria --@field Syria
AIRBASE.Syria={ AIRBASE.Syria={
@@ -518,6 +534,7 @@ AIRBASE.Syria={
["Gaziantep"] = "Gaziantep", ["Gaziantep"] = "Gaziantep",
["Gazipasa"] = "Gazipasa", ["Gazipasa"] = "Gazipasa",
["Gecitkale"] = "Gecitkale", ["Gecitkale"] = "Gecitkale",
["H"] = "H",
["H3"] = "H3", ["H3"] = "H3",
["H3_Northwest"] = "H3 Northwest", ["H3_Northwest"] = "H3 Northwest",
["H3_Southwest"] = "H3 Southwest", ["H3_Southwest"] = "H3 Southwest",
@@ -565,6 +582,10 @@ AIRBASE.Syria={
["Tha_lah"] = "Tha'lah", ["Tha_lah"] = "Tha'lah",
["Tiyas"] = "Tiyas", ["Tiyas"] = "Tiyas",
["Wujah_Al_Hajar"] = "Wujah Al Hajar", ["Wujah_Al_Hajar"] = "Wujah Al Hajar",
["Ben_Gurion"] = "Ben Gurion",
["Hatzor"] = "Hatzor",
["Palmashim"] = "Palmashim",
["Tel_Nof"] = "Tel Nof",
} }
--- Airbases of the Mariana Islands map: --- Airbases of the Mariana Islands map:
@@ -752,12 +773,14 @@ AIRBASE.Sinai = {
-- --
-- * AIRBASE.Kola.Banak -- * AIRBASE.Kola.Banak
-- * AIRBASE.Kola.Bodo -- * AIRBASE.Kola.Bodo
-- * AIRBASE.Kola.Ivalo
-- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Jokkmokk
-- * AIRBASE.Kola.Kalixfors -- * AIRBASE.Kola.Kalixfors
-- * AIRBASE.Kola.Kallax -- * AIRBASE.Kola.Kallax
-- * AIRBASE.Kola.Kemi_Tornio -- * AIRBASE.Kola.Kemi_Tornio
-- * AIRBASE.Kola.Kirkenes -- * AIRBASE.Kola.Kirkenes
-- * AIRBASE.Kola.Kiruna -- * AIRBASE.Kola.Kiruna
-- * AIRBASE.Kola.Kuusamo
-- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Monchegorsk
-- * AIRBASE.Kola.Murmansk_International -- * AIRBASE.Kola.Murmansk_International
-- * AIRBASE.Kola.Olenya -- * AIRBASE.Kola.Olenya
@@ -766,62 +789,124 @@ AIRBASE.Sinai = {
-- * AIRBASE.Kola.Severomorsk_3 -- * AIRBASE.Kola.Severomorsk_3
-- * AIRBASE.Kola.Vidsel -- * AIRBASE.Kola.Vidsel
-- * AIRBASE.Kola.Vuojarvi -- * AIRBASE.Kola.Vuojarvi
-- -- * AIRBASE.Kola.Andoya
-- * AIRBASE.Kola.Alakourtti
-- * AIRBASE.Kola.Kittila
-- * AIRBASE.Kola.Bardufoss
--
-- @field Kola -- @field Kola
AIRBASE.Kola = { AIRBASE.Kola = {
["Banak"] = "Banak", ["Banak"] = "Banak",
["Bodo"] = "Bodo", ["Bodo"] = "Bodo",
["Ivalo"] = "Ivalo",
["Jokkmokk"] = "Jokkmokk", ["Jokkmokk"] = "Jokkmokk",
["Kalixfors"] = "Kalixfors", ["Kalixfors"] = "Kalixfors",
["Kallax"] = "Kallax",
["Kemi_Tornio"] = "Kemi Tornio", ["Kemi_Tornio"] = "Kemi Tornio",
["Kirkenes"] = "Kirkenes",
["Kiruna"] = "Kiruna", ["Kiruna"] = "Kiruna",
["Kuusamo"] = "Kuusamo",
["Monchegorsk"] = "Monchegorsk", ["Monchegorsk"] = "Monchegorsk",
["Murmansk_International"] = "Murmansk International", ["Murmansk_International"] = "Murmansk International",
["Olenya"] = "Olenya", ["Olenya"] = "Olenya",
["Rovaniemi"] = "Rovaniemi", ["Rovaniemi"] = "Rovaniemi",
["Severomorsk_1"] = "Severomorsk-1", ["Severomorsk_1"] = "Severomorsk-1",
["Severomorsk_3"] = "Severomorsk-3", ["Severomorsk_3"] = "Severomorsk-3",
["Vuojarvi"] = "Vuojarvi",
["Kirkenes"] = "Kirkenes",
["Kallax"] = "Kallax",
["Vidsel"] = "Vidsel", ["Vidsel"] = "Vidsel",
["Vuojarvi"] = "Vuojarvi",
["Andoya"] = "Andoya",
["Alakourtti"] = "Alakourtti",
["Kittila"] = "Kittila",
["Bardufoss"] = "Bardufoss",
} }
--- Airbases of the Afghanistan map --- Airbases of the Afghanistan map
-- --
-- * AIRBASE.Afghanistan.Bost -- * AIRBASE.Afghanistan.Bost
-- * AIRBASE.Afghanistan.Bagram
-- * AIRBASE.Afghanistan.Bamyan
-- * AIRBASE.Afghanistan.Camp_Bastion -- * AIRBASE.Afghanistan.Camp_Bastion
-- * AIRBASE.Afghanistan.Camp_Bastion_Heliport -- * AIRBASE.Afghanistan.Camp_Bastion_Heliport
-- * AIRBASE.Afghanistan.Chaghcharan -- * AIRBASE.Afghanistan.Chaghcharan
-- * AIRBASE.Afghanistan.Dwyer -- * AIRBASE.Afghanistan.Dwyer
-- * AIRBASE.Afghanistan.Farah -- * AIRBASE.Afghanistan.Farah
-- * AIRBASE.Afghanistan.Herat -- * AIRBASE.Afghanistan.Herat
-- * AIRBASE.Afghanistan.Gardez
-- * AIRBASE.Afghanistan.Ghazni_Heliport
-- * AIRBASE.Afghanistan.Jalalabad
-- * AIRBASE.Afghanistan.Kabul
-- * AIRBASE.Afghanistan.Kandahar -- * AIRBASE.Afghanistan.Kandahar
-- * AIRBASE.Afghanistan.Kandahar_Heliport -- * AIRBASE.Afghanistan.Kandahar_Heliport
-- * AIRBASE.Afghanistan.Khost
-- * AIRBASE.Afghanistan.Khost_Heliport
-- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi -- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi
-- * AIRBASE.Afghanistan.Nimroz -- * AIRBASE.Afghanistan.Nimroz
-- * AIRBASE.Afghanistan.Qala_i_Naw -- * AIRBASE.Afghanistan.Qala_i_Naw
-- * AIRBASE.Afghanistan.Shindand -- * AIRBASE.Afghanistan.Shindand
-- * AIRBASE.Afghanistan.Shindand_Heliport -- * AIRBASE.Afghanistan.Shindand_Heliport
-- * AIRBASE.Afghanistan.Tarinkot -- * AIRBASE.Afghanistan.Tarinkot
-- * AIRBASE.Afghanistan.Urgoon_Heliport
-- --
-- @field Afghanistan -- @field Afghanistan
AIRBASE.Afghanistan = { AIRBASE.Afghanistan = {
["Bagram"] = "Bagram",
["Bamyan"] = "Bamyan",
["Bost"] = "Bost", ["Bost"] = "Bost",
["Camp_Bastion"] = "Camp Bastion", ["Camp_Bastion"] = "Camp Bastion",
["Camp_Bastion_Heliport"] = "Camp Bastion Heliport", ["Camp_Bastion_Heliport"] = "Camp Bastion Heliport",
["Chaghcharan"] = "Chaghcharan", ["Chaghcharan"] = "Chaghcharan",
["Dwyer"] = "Dwyer", ["Dwyer"] = "Dwyer",
["Farah"] = "Farah", ["Farah"] = "Farah",
["Gardez"] = "Gardez",
["Ghazni_Heliport"] = "Ghazni Heliport",
["Herat"] = "Herat", ["Herat"] = "Herat",
["Jalalabad"] = "Jalalabad",
["Kabul"] = "Kabul",
["Kandahar"] = "Kandahar", ["Kandahar"] = "Kandahar",
["Kandahar_Heliport"] = "Kandahar Heliport", ["Kandahar_Heliport"] = "Kandahar Heliport",
["Khost"] = "Khost",
["Khost_Heliport"] = "Khost Heliport",
["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi", ["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi",
["Nimroz"] = "Nimroz", ["Nimroz"] = "Nimroz",
["Qala_i_Naw"] = "Qala i Naw", ["Qala_i_Naw"] = "Qala i Naw",
["Sharana"] = "Sharana",
["Shindand"] = "Shindand", ["Shindand"] = "Shindand",
["Shindand_Heliport"] = "Shindand Heliport", ["Shindand_Heliport"] = "Shindand Heliport",
["Tarinkot"] = "Tarinkot", ["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". --- 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 HelicopterOnly 40: Special spots for Helicopers.
-- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map. -- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map.
-- @field #number OpenMed 72: Open/Shelter air airplane only. -- @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 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 OpenMedOrBig 176: Combines OpenMed and OpenBig spots.
-- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig. -- @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 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 FighterAircraftSmall 344: Combines Shelter, SmallsizeFighter, OpenMed and OpenBig spots. So effectively all spots usable by small fixed wing aircraft.
AIRBASE.TerminalType = { AIRBASE.TerminalType = {
Runway=16, Runway=16,
HelicopterOnly=40, HelicopterOnly=40,
@@ -875,6 +961,7 @@ AIRBASE.TerminalType = {
OpenMedOrBig=176, OpenMedOrBig=176,
HelicopterUsable=216, HelicopterUsable=216,
FighterAircraft=244, FighterAircraft=244,
FighterAircraftSmall=344,
} }
--- Status of a parking spot. --- Status of a parking spot.
@@ -937,40 +1024,55 @@ function AIRBASE:Register(AirbaseName)
--end --end
-- Set category. -- Set category.
if self.category==Airbase.Category.AIRDROME then if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD then 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 self.isHelipad=true
elseif self.category==Airbase.Category.SHIP then self.isShip=false
self.isShip=true self.category=Airbase.Category.HELIPAD
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() _DATABASE:AddStatic(AirbaseName)
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!")
end end
else
self:E("ERROR: Unknown airbase category!")
end
-- Init Runways. -- Init Runways.
self:_InitRunways() self:_InitRunways()
-- Number of runways
local Nrunways=#self.runways
-- Set the active runways based on wind direction. -- Set the active runways based on wind direction.
if self.isAirdrome then if Nrunways>0 then
self:SetActiveRunway() self:SetActiveRunway()
end end
-- Init parking spots. -- Init parking spots.
self:_InitParkingSpots() 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. -- Get 2D position vector.
local vec2=self:GetVec2() local vec2=self:GetVec2()
-- Init coordinate. -- Init coordinate.
self:GetCoordinate() self:GetCoordinate()
-- Storage. -- Storage.
self.storage=_DATABASE:AddStorage(AirbaseName) self.storage=_DATABASE:AddStorage(AirbaseName)
@@ -993,6 +1095,46 @@ function AIRBASE:Register(AirbaseName)
return self return self
end 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 -- Reference methods
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1516,7 +1658,7 @@ function AIRBASE:_InitParkingSpots()
self.NparkingTotal=self.NparkingTotal+1 self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType) do 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 self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end end
end end
@@ -1524,6 +1666,9 @@ function AIRBASE:_InitParkingSpots()
self.parkingByID[park.TerminalID]=park self.parkingByID[park.TerminalID]=park
table.insert(self.parking, park) table.insert(self.parking, park)
end end
-- Runways are not included in total number of parking spots
self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway]
return self return self
end end
@@ -1947,9 +2092,13 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
match=true match=true
end end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then 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 match=true
end 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 end
return match return match
@@ -2007,11 +2156,6 @@ function AIRBASE:_InitRunways(IncludeInverse)
-- Runway table. -- Runway table.
local Runways={} local Runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self.runways={}
return {}
end
--- Function to create a runway data table. --- Function to create a runway data table.
local function _createRunway(name, course, width, length, center) local function _createRunway(name, course, width, length, center)
@@ -2097,7 +2241,7 @@ function AIRBASE:_InitRunways(IncludeInverse)
-- Debug info. -- Debug info.
self:T2(runways) self:T2(runways)
if runways then if runways and #runways>0 then
-- Loop over runways. -- Loop over runways.
for _,rwy in pairs(runways) do for _,rwy in pairs(runways) do
@@ -2130,6 +2274,12 @@ function AIRBASE:_InitRunways(IncludeInverse)
end end
end end
else
-- No runways
self.runways={}
return {}
end end

View File

@@ -902,7 +902,11 @@ function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay )
groupId = self:GetID(), groupId = self:GetID(),
}, },
} }
--if self:IsGround() then
--CommandEPLRS.params.groupId = self:GetID()
--end
if Delay and Delay > 0 then if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay ) SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay )
else else
@@ -937,7 +941,7 @@ function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay)
end 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 #CONTROLLABLE self
-- @param #number Frequency Radio frequency in MHz. -- @param #number Frequency Radio frequency in MHz.
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
@@ -956,7 +960,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
} }
if Delay and Delay > 0 then 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 else
self:SetCommand( CommandSetFrequency ) self:SetCommand( CommandSetFrequency )
end end
@@ -964,12 +968,12 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
return self return self
end 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 #CONTROLLABLE self
-- @param #number Frequency Radio frequency in MHz. -- @param #number Frequency Radio frequency in MHz.
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
-- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10. -- @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. -- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay)
@@ -983,7 +987,7 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit
}, },
} }
if Delay and Delay>0 then 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 else
self:SetCommand(CommandSetFrequencyForUnit) self:SetCommand(CommandSetFrequencyForUnit)
end end
@@ -1009,7 +1013,11 @@ function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx )
groupId = self:GetID(), groupId = self:GetID(),
}, },
} }
--if self:IsGround() then
--CommandEPLRS.params.groupId = self:GetID()
--end
return self:TaskWrappedAction( CommandEPLRS, idx or 1 ) return self:TaskWrappedAction( CommandEPLRS, idx or 1 )
end end
@@ -1769,8 +1777,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation,
return DCSTask return DCSTask
end end
-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES
--- (AIR) Engaging targets of defined types. --- (AIR) Engaging targets of defined types.
-- @param #CONTROLLABLE self -- @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. -- @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.
@@ -2997,7 +3003,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
if DCSControllable then if DCSControllable then
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil 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 DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST 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 local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
@@ -3031,26 +3037,27 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
return nil return nil
end 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. -- 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 **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. -- If **at least one** detection method is specified, only the methods set to *true* will be used.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param DCS#Object DCSObject The DCS object that is checked. -- @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 DetectVisual (Optional) If *false*, do not include visually detected targets.
-- @param #boolean DetectOptical (Optional) If *false*, do not include optically 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 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 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 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. -- @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 detected.
-- @return #boolean True if target is visible by line of sight. -- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
-- @return #number Mission time when target was detected. -- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
-- @return #boolean True if target type is known. -- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
-- @return #boolean True if distance to target is known. -- @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. -- @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. -- @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 ) function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
self:F2( self.ControllableName ) self:F2( self.ControllableName )
@@ -3059,7 +3066,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
if DCSControllable then if DCSControllable then
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil 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 DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST 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 local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
@@ -3067,10 +3074,10 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
local Controller = self:_GetController() 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 ) = 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 end
return nil return nil
@@ -3078,6 +3085,7 @@ end
--- Check if a certain UNIT is detected by the controllable. --- Check if a certain UNIT is detected by the controllable.
-- The optional parametes specify the detection methods that can be applied. -- 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 **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. -- If **at least one** detection method is specified, only the methods set to *true* will be used.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
@@ -3088,13 +3096,13 @@ end
-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST. -- @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 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. -- @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 detected.
-- @return #boolean True if target is visible by line of sight. -- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
-- @return #number Mission time when target was detected. -- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
-- @return #boolean True if target type is known. -- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
-- @return #boolean True if distance to target is known. -- @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. -- @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. -- @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 ) function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
self:F2( self.ControllableName ) self:F2( self.ControllableName )
@@ -4253,6 +4261,9 @@ function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, short
self:F2( { self.ControllableName } ) self:F2( { self.ControllableName } )
local _coord = self:GetCoordinate() local _coord = self:GetCoordinate()
if not _coord then
return self
end
local _radius = radius or 500 local _radius = radius or 500
local _speed = speed or 20 local _speed = speed or 20
local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 ) local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 )
@@ -4302,7 +4313,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds )
end end
--- Returns if the unit is a submarine. --- Returns if the unit is a submarine.
-- @param #POSITIONABLE self -- @param #CONTROLLABLE self
-- @return #boolean Submarines attributes result. -- @return #boolean Submarines attributes result.
function CONTROLLABLE:IsSubmarine() function CONTROLLABLE:IsSubmarine()
self:F2() self:F2()
@@ -5628,22 +5639,21 @@ end
--- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP. --- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP.
-- @param #CONTROLLABLE self -- @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 #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. -- @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 -- @return #CONTROLLABLE self
function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime)
--sefl:F("NewIRMarker") self:T2("NewIRMarker")
if self.ClassName == "GROUP" then if self:IsInstanceOf("GROUP") then
if self.IRMarkerGroup == true then return end
self.IRMarkerGroup = true self.IRMarkerGroup = true
self.IRMarkerUnit = false self.IRMarkerUnit = false
elseif self.ClassName == "UNIT" then elseif self:IsInstanceOf("UNIT") then
if self.IRMarkerUnit == true then return end
self.IRMarkerGroup = false self.IRMarkerGroup = false
self.IRMarkerUnit = true self.IRMarkerUnit = true
end end
self.spot = nil
self.timer = nil
self.stoptimer = nil
self.Runtime = Runtime or 60
if EnableImmediately and EnableImmediately == true then if EnableImmediately and EnableImmediately == true then
self:EnableIRMarker(Runtime) self:EnableIRMarker(Runtime)
end end
@@ -5656,19 +5666,23 @@ end
-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`. -- @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 -- @return #CONTROLLABLE self
function CONTROLLABLE:EnableIRMarker(Runtime) function CONTROLLABLE:EnableIRMarker(Runtime)
--sefl:F("EnableIRMarker") self:T2("EnableIRMarker")
if self.IRMarkerGroup == nil then if self.IRMarkerGroup == nil then
self:NewIRMarker(true,Runtime) self:NewIRMarker(true,Runtime)
return return
end end
if (self.IRMarkerGroup == true) then if self:IsInstanceOf("GROUP") then
self:EnableIRMarkerForGroup() self:EnableIRMarkerForGroup(Runtime)
return return
end 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 = TIMER:New(CONTROLLABLE._MarkerBlink, self)
self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized
self.IRMarkerUnit = true
return self return self
end end
@@ -5677,33 +5691,42 @@ end
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:DisableIRMarker() function CONTROLLABLE:DisableIRMarker()
--sefl:F("DisableIRMarker") self:T2("DisableIRMarker")
if (self.IRMarkerGroup == true) then if self:IsInstanceOf("GROUP") then
self:DisableIRMarkerForGroup() self:DisableIRMarkerForGroup()
return return
end end
if self.spot then if self.spot then
self.spot:destroy() self.spot = nil
self.spot = nil end
if self.timer and self.timer:IsRunning() then if self.timer and self.timer:IsRunning() then
self.timer:Stop() self.timer:Stop()
self.timer = nil self.timer = nil
end end
if self:IsInstanceOf("GROUP") then
self.IRMarkerGroup = nil
elseif self:IsInstanceOf("UNIT") then
self.IRMarkerUnit = nil
end end
return self return self
end end
--- [GROUND] Enable the IR markers for a whole group. --- [GROUND] Enable the IR markers for a whole group.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #number Runtime Runtime of the marker in seconds
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:EnableIRMarkerForGroup() function CONTROLLABLE:EnableIRMarkerForGroup(Runtime)
--sefl:F("EnableIRMarkerForGroup") self:T2("EnableIRMarkerForGroup")
if self.ClassName == "GROUP" then if self:IsInstanceOf("GROUP")
then
local units = self:GetUnits() or {} local units = self:GetUnits() or {}
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
_unit:EnableIRMarker() _unit:EnableIRMarker(Runtime)
end end
self.IRMarkerGroup = true
end end
return self return self
end end
@@ -5712,21 +5735,43 @@ end
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:DisableIRMarkerForGroup() function CONTROLLABLE:DisableIRMarkerForGroup()
--sefl:F("DisableIRMarkerForGroup") self:T2("DisableIRMarkerForGroup")
if self.ClassName == "GROUP" then if self:IsInstanceOf("GROUP") then
local units = self:GetUnits() or {} local units = self:GetUnits() or {}
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
_unit:DisableIRMarker() _unit:DisableIRMarker()
end end
self.IRMarkerGroup = nil
end end
return self return self
end 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. --- [Internal] This method is called by the scheduler after enabling the IR marker.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:_MarkerBlink() function CONTROLLABLE:_MarkerBlink()
--sefl:F("_MarkerBlink") self:T2("_MarkerBlink")
if self:IsAlive() ~= true then if self:IsAlive() ~= true then
self:DisableIRMarker() self:DisableIRMarker()
return return
@@ -5737,13 +5782,17 @@ function CONTROLLABLE:_MarkerBlink()
local _, _, unitBBHeight, _ = self:GetObjectSize() local _, _, unitBBHeight, _ = self:GetObjectSize()
local unitPos = self:GetPositionVec3() local unitPos = self:GetPositionVec3()
self.spot = Spot.createInfraRed( if self.timer:IsRunning() then
self.DCSUnit, self:T2("Create Spot")
{ x = 0, y = (unitBBHeight + 1), z = 0 }, local spot = Spot.createInfraRed(
{ x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z } self.DCSUnit,
) { x = 0, y = (unitBBHeight + 1), z = 0 },
{ x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z }
local offTimer = TIMER:New(function() if self.spot then self.spot:destroy() end end) )
offTimer:Start(0.5) self.spot = spot
local offTimer = nil
local offTimer = TIMER:New(CONTROLLABLE._StopSpot, spot)
offTimer:Start(0.5)
end
return self return self
end end

View File

@@ -458,7 +458,7 @@ function DYNAMICCARGO:_UpdatePosition()
self:T(self.lid.." AGL: "..agl or -1) self:T(self.lid.." AGL: "..agl or -1)
local isunloaded = true local isunloaded = true
local client local client
local playername local playername = self.Owner
if count > 0 and (agl > 0 or self.testing) then if count > 0 and (agl > 0 or self.testing) then
self:T(self.lid.." Possible alive helos: "..count or -1) self:T(self.lid.." Possible alive helos: "..count or -1)
if agl ~= 0 or self.testing then if agl ~= 0 or self.testing then
@@ -470,6 +470,11 @@ function DYNAMICCARGO:_UpdatePosition()
self.Owner = playername self.Owner = playername
_DATABASE:CreateEventDynamicCargoUnloaded(self) _DATABASE:CreateEventDynamicCargoUnloaded(self)
end 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
end end
self.LastPosition = pos self.LastPosition = pos

View File

@@ -360,7 +360,7 @@ end
-- @return DCS#Group The DCS Group. -- @return DCS#Group The DCS Group.
function GROUP:GetDCSObject() function GROUP:GetDCSObject()
if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then --if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then
-- Get DCS group. -- Get DCS group.
local DCSGroup = Group.getByName( self.GroupName ) local DCSGroup = Group.getByName( self.GroupName )
@@ -369,14 +369,14 @@ function GROUP:GetDCSObject()
self.LastCallDCSObject = timer.getTime() self.LastCallDCSObject = timer.getTime()
self.DCSObject = DCSGroup self.DCSObject = DCSGroup
return DCSGroup return DCSGroup
else -- else
self.DCSObject = nil -- self.DCSObject = nil
self.LastCallDCSObject = nil -- self.LastCallDCSObject = nil
end end
else --else
return self.DCSObject --return self.DCSObject
end --end
--self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName)))
return nil return nil
@@ -485,20 +485,24 @@ function GROUP:Destroy( GenerateEvent, delay )
self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent)
else else
local DCSGroup = self:GetDCSObject() --local DCSGroup = self:GetDCSObject()
local DCSGroup = Group.getByName( self.GroupName )
if DCSGroup then if DCSGroup then
for Index, UnitData in pairs( DCSGroup:getUnits() ) do for Index, UnitData in pairs( DCSGroup:getUnits() ) do
if GenerateEvent and GenerateEvent == true then if GenerateEvent and GenerateEvent == true then
if self:IsAir() then if self:IsAir() then
self:CreateEventCrash( timer.getTime(), UnitData ) self:CreateEventCrash( timer.getTime(), UnitData )
--self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),UnitData)
else else
self:CreateEventDead( timer.getTime(), UnitData ) self:CreateEventDead( timer.getTime(), UnitData )
--self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),UnitData)
end end
elseif GenerateEvent == false then elseif GenerateEvent == false then
-- Do nothing! -- Do nothing!
else else
self:CreateEventRemoveUnit( timer.getTime(), UnitData ) self:CreateEventRemoveUnit( timer.getTime(), UnitData )
--self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),UnitData)
end end
end end
USERFLAG:New( self:GetName() ):Set( 100 ) USERFLAG:New( self:GetName() ):Set( 100 )
@@ -753,7 +757,11 @@ end
-- @param #GROUP self -- @param #GROUP self
-- @return #boolean If true, group is associated with a client or player slot. -- @return #boolean If true, group is associated with a client or player slot.
function GROUP:IsPlayer() function GROUP:IsPlayer()
return self:GetUnit(1):IsPlayer() local unit = self:GetUnit(1)
if unit then
return unit:IsPlayer()
end
return false
end end
--- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit. --- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit.
@@ -1217,7 +1225,17 @@ end
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP. -- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetCoordinate() function GROUP:GetCoordinate()
local Units = self:GetUnits() or {} -- First try to get the 3D vector of the group. This uses
local vec3=self:GetVec3()
local coord
if vec3 then
coord=COORDINATE:NewFromVec3(vec3)
coord.Heading = self:GetHeading() or 0
return coord
end
-- No luck try units and add Heading data
local Units = self:GetUnits() or {}
for _,_unit in pairs(Units) do for _,_unit in pairs(Units) do
local FirstUnit = _unit -- Wrapper.Unit#UNIT local FirstUnit = _unit -- Wrapper.Unit#UNIT
@@ -1227,15 +1245,15 @@ function GROUP:GetCoordinate()
local FirstUnitCoordinate = FirstUnit:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate()
if FirstUnitCoordinate then if FirstUnitCoordinate then
local Heading = self:GetHeading() local Heading = self:GetHeading() or 0
FirstUnitCoordinate.Heading = Heading FirstUnitCoordinate.Heading = Heading
return FirstUnitCoordinate return FirstUnitCoordinate
end end
end end
end end
-- no luck, try the API way
-- no luck, try the API way
local DCSGroup = Group.getByName(self.GroupName) local DCSGroup = Group.getByName(self.GroupName)
if DCSGroup then if DCSGroup then
local DCSUnits = DCSGroup:getUnits() or {} local DCSUnits = DCSGroup:getUnits() or {}
@@ -1246,14 +1264,19 @@ function GROUP:GetCoordinate()
if point then if point then
--self:I(point) --self:I(point)
local coord = COORDINATE:NewFromVec3(point) local coord = COORDINATE:NewFromVec3(point)
coord.Heading = 0
local munit = UNIT:Find(_unit)
if munit then
coord.Heading = munit:GetHeading() or 0
end
return coord return coord
end end
end end
end end
end end
BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } )
end end
@@ -1985,15 +2008,11 @@ end
-- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units). -- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units).
-- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group. -- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group.
-- - @{#GROUP.InitZone}: Set the respawn @{Core.Zone} for the respawned group. -- - @{#GROUP.InitZone}: Set the respawn @{Core.Zone} for the respawned group.
-- - @{#GROUP.InitRandomizeZones}: Randomize the respawn @{Core.Zone} between one of the @{Core.Zone}s given for the respawned group.
-- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}. -- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}.
-- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band. -- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band.
-- - @{#GROUP.InitRandomizeTemplates}: Randomize the Template for the respawned group.
--
-- --
-- Notes: -- Notes:
-- --
-- - When InitZone or InitRandomizeZones is not used, the position of the respawned group will be its current position.
-- - The current alive group will always be destroyed and respawned using the template definition. -- - The current alive group will always be destroyed and respawned using the template definition.
-- --
-- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self
@@ -2015,10 +2034,24 @@ function GROUP:Respawn( Template, Reset )
end end
return h return h
end end
local function TransFormRoute(Template,OldPos,NewPos)
if Template.route and Template.route.points then
for _,_point in ipairs(Template.route.points) do
--self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y))
_point.x = _point.x - OldPos.x + NewPos.x
_point.y = _point.y - OldPos.y + NewPos.y
--self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y))
end
end
return Template
end
-- First check if group is alive. -- First check if group is alive.
if self:IsAlive() then if self:IsAlive() then
local OldPos = self:GetVec2()
-- Respawn zone. -- Respawn zone.
local Zone = self.InitRespawnZone -- Core.Zone#ZONE local Zone = self.InitRespawnZone -- Core.Zone#ZONE
@@ -2031,6 +2064,8 @@ function GROUP:Respawn( Template, Reset )
-- X, Y -- X, Y
Template.x = Vec3.x Template.x = Vec3.x
Template.y = Vec3.z Template.y = Vec3.z
local NewPos = { x = Vec3.x, y = Vec3.z }
--Template.x = nil --Template.x = nil
--Template.y = nil --Template.y = nil
@@ -2085,11 +2120,13 @@ function GROUP:Respawn( Template, Reset )
-- Set heading. -- Set heading.
Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi = -Template.units[UnitID].heading Template.units[UnitID].psi = -Template.units[UnitID].heading
-- Debug. -- Debug.
--self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end
Template = TransFormRoute(Template,OldPos,NewPos)
elseif Reset==false then -- Reset=false or nil elseif Reset==false then -- Reset=false or nil
@@ -2128,11 +2165,13 @@ function GROUP:Respawn( Template, Reset )
-- Heading -- Heading
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
-- Debug. -- Debug.
--self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
Template = TransFormRoute(Template,OldPos,NewPos)
else else
local units=self:GetUnits() local units=self:GetUnits()
@@ -2181,10 +2220,11 @@ function GROUP:Respawn( Template, Reset )
-- Destroy old group. Dont trigger any dead/crash events since this is a respawn. -- Destroy old group. Dont trigger any dead/crash events since this is a respawn.
self:Destroy(false) self:Destroy(false)
--self:T({Template=Template}) --UTILS.PrintTableToLog(Template)
-- Spawn new group. -- Spawn new group.
_DATABASE:Spawn(Template) self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template)
--_DATABASE:Spawn(Template)
-- Reset events. -- Reset events.
self:ResetEvents() self:ResetEvents()
@@ -2192,6 +2232,29 @@ function GROUP:Respawn( Template, Reset )
return self return self
end end
--- Respawn the @{Wrapper.Group} at a @{Core.Point#COORDINATE}.
-- The method will setup the new group template according the Init(Respawn) settings provided for the group.
-- These settings can be provided by calling the relevant Init...() methods of the Group prior.
--
-- - @{#GROUP.InitHeading}: Set the heading for the units in degrees within the respawned group.
-- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units).
-- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group.
-- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}.
-- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band.
--
-- Notes:
--
-- - When no coordinate is given, the position of the respawned group will be its current position.
-- - The current alive group will always be destroyed first.
-- - The new group will have all of its original units and health restored.
--
-- @param Wrapper.Group#GROUP self
-- @param Core.Point#COORDINATE Coordinate Where to respawn the group. Can be handed as a @{Core.Zone#ZONE_BASE} object.
-- @return Wrapper.Group#GROUP self
function GROUP:Teleport(Coordinate)
self:InitZone(Coordinate)
return self:Respawn(nil,false)
end
--- Respawn a group at an airbase. --- Respawn a group at an airbase.
-- Note that the group has to be on parking spots at the airbase already in order for this to work. -- Note that the group has to be on parking spots at the airbase already in order for this to work.
@@ -2333,8 +2396,11 @@ end
-- @return #table The mission route defined by points. -- @return #table The mission route defined by points.
function GROUP:GetTaskRoute() function GROUP:GetTaskRoute()
--self:F2( self.GroupName ) --self:F2( self.GroupName )
if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then
return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points )
else
return {}
end
end end
--- Return the route of a group by using the global _DATABASE object (an instance of @{Core.Database#DATABASE}). --- Return the route of a group by using the global _DATABASE object (an instance of @{Core.Database#DATABASE}).
@@ -2750,7 +2816,7 @@ do -- Event Handling
self:EventDispatcher():Reset( self ) self:EventDispatcher():Reset( self )
for UnitID, UnitData in pairs( self:GetUnits() ) do for UnitID, UnitData in pairs( self:GetUnits() or {}) do
UnitData:ResetEvents() UnitData:ResetEvents()
end end
@@ -2926,8 +2992,10 @@ end
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1" -- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1"
-- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns -- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns
-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set -- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set
-- callsigns from playername or group name. -- callsigns from playername or group name.
-- @param #func CustomFunction (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first arguments.
-- @param #arg ... (Optional) Comma separated arguments to add to the CustomFunction call after groupname and playername.
-- @return #string Callsign -- @return #string Callsign
-- @usage -- @usage
-- -- suppose there are three groups with one (client) unit each: -- -- suppose there are three groups with one (client) unit each:
@@ -2948,9 +3016,13 @@ end
-- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set -- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set
-- -- Apollo for Slot 3 -- -- Apollo for Slot 3
-- -- Bengal-4 for Slot 4 -- -- Bengal-4 for Slot 4
--
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) -- -- Using a custom function (for player units **only**):
--self:I("GetCustomCallSign") -- -- Imagine your playernames are looking like so: "[Squadname] | Cpt Apple" and you only want to have the last word as callsign, i.e. "Apple" here. Then this custom function will return this:
-- local callsign = mygroup:GetCustomCallSign(true,false,nil,function(groupname,playername) return string.match(playername,"([%a]+)$") end)
--
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...)
self:T("GetCustomCallSign")
local callsign = "Ghost 1" local callsign = "Ghost 1"
if self:IsAlive() then if self:IsAlive() then
@@ -2963,7 +3035,18 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9
local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 local callnumberminor = string.char(string.byte(callnumber,2)) -- 1
local personalized = false local personalized = false
--local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign
local playername = shortcallsign
if IsPlayer then playername = self:GetPlayerName() end
self:T2("GetCustomCallSign outcome = "..playername)
if CustomFunction and IsPlayer then
local arguments = arg or {}
local callsign = CustomFunction(groupname,playername,unpack(arguments))
return callsign
end
-- prioritize bespoke callsigns over parsing, prefer parsing over default callsigns -- prioritize bespoke callsigns over parsing, prefer parsing over default callsigns
if CallsignTranslations and CallsignTranslations[callsignroot] then if CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot] callsignroot = CallsignTranslations[callsignroot]
@@ -2975,9 +3058,9 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
shortcallsign = string.match(groupname,"#%s*([%a]+)") or "Ghost" -- Ghostrider shortcallsign = string.match(groupname,"#%s*([%a]+)") or "Ghost" -- Ghostrider
end end
personalized = true personalized = true
elseif IsPlayer and string.find(self:GetPlayerName(),"|") then elseif IsPlayer and string.find(playername,"|") then
-- personalized flight name in group naming -- personalized flight name in group naming
shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider shortcallsign = string.match(playername,"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider
personalized = true personalized = true
end end
@@ -3077,7 +3160,7 @@ function GROUP:IsSAM()
local units = self:GetUnits() local units = self:GetUnits()
for _,_unit in pairs(units or {}) do for _,_unit in pairs(units or {}) do
local unit = _unit -- Wrapper.Unit#UNIT local unit = _unit -- Wrapper.Unit#UNIT
if unit:HasSEAD() and unit:IsGround() and (not unit:HasAttribute("Mobile AAA")) then if unit:IsSAM() then
issam = true issam = true
break break
end end
@@ -3087,18 +3170,16 @@ end
--- [GROUND] Determine if a GROUP has a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. --- [GROUND] Determine if a GROUP has a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute.
-- @param #GROUP self -- @param #GROUP self
-- @return #boolean IsSAM True if AAA, else false -- @return #boolean IsAAA True if AAA, else false
function GROUP:IsAAA() function GROUP:IsAAA()
local issam = false local isAAA = false
local units = self:GetUnits() local units = self:GetUnits()
for _,_unit in pairs(units or {}) do for _,_unit in pairs(units or {}) do
local unit = _unit -- Wrapper.Unit#UNIT local unit = _unit -- Wrapper.Unit#UNIT
local desc = unit:GetDesc() or {} if unit:IsAAA() then
local attr = desc.attributes or {} isAAA = true
if unit:HasSEAD() then return false end break
if attr["AAA"] or attr["SAM related"] then
issam = true
end end
end end
return issam return isAAA
end end

View File

@@ -43,7 +43,7 @@ do
-- @field #NET -- @field #NET
NET = { NET = {
ClassName = "NET", ClassName = "NET",
Version = "0.1.3", Version = "0.1.4",
BlockTime = 600, BlockTime = 600,
BlockedPilots = {}, BlockedPilots = {},
BlockedUCIDs = {}, BlockedUCIDs = {},
@@ -67,6 +67,9 @@ function NET:New()
self.KnownPilots = {} self.KnownPilots = {}
self:SetBlockMessage() self:SetBlockMessage()
self:SetUnblockMessage() self:SetUnblockMessage()
self.BlockedSides = {}
self.BlockedSides[1] = false
self.BlockedSides[2] = false
-- Start State. -- Start State.
self:SetStartState("Stopped") self:SetStartState("Stopped")
@@ -160,11 +163,12 @@ end
-- @param #string PlayerSlot -- @param #string PlayerSlot
-- @return #boolean IsBlocked -- @return #boolean IsBlocked
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot})
local blocked = false local blocked = false
local TNow = timer.getTime() local TNow = timer.getTime()
-- UCID -- UCID
if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then
return true blocked = true
end end
-- ID/Name -- ID/Name
if PlayerID and not Name then if PlayerID and not Name then
@@ -172,16 +176,18 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
end end
-- Name -- Name
if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then
return true blocked = true
end end
-- Side -- Side
if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then self:T({time = self.BlockedSides[PlayerSide]})
return true if PlayerSide and type(self.BlockedSides[PlayerSide]) == "number" and TNow < self.BlockedSides[PlayerSide] then
blocked = true
end end
-- Slot -- Slot
if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then
return true blocked = true
end end
self:T("IsAnyBlocked: "..tostring(blocked))
return blocked return blocked
end end
@@ -200,19 +206,27 @@ function NET:_EventHandler(EventData)
local ucid = self:GetPlayerUCID(nil,name) or "none" local ucid = self:GetPlayerUCID(nil,name) or "none"
local PlayerID = self:GetPlayerIDByName(name) or "none" local PlayerID = self:GetPlayerIDByName(name) or "none"
local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit) 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() 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 -- Joining
if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id) self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id)
-- Check for blockages -- Check for blockages
local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot) local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
if blocked and PlayerID then -- and tonumber(PlayerID) ~= 1 then
if blocked and PlayerID and tonumber(PlayerID) ~= 1 then self:T("Player blocked")
-- block pilot -- 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 else
local client = CLIENT:FindByPlayerName(name) or data.IniUnit 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 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, slot = PlayerSlot,
timestamp = TNow, timestamp = TNow,
} }
--UTILS.PrintTableToLog(self.KnownPilots[name])
end end
return self return self
end 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 --- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players
-- @param #NET self -- @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. -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self -- @return #NET self
function NET:BlockSide(Side,Seconds) function NET:BlockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then if Side == 1 or Side == 2 then
self.BlockedSides[Side] = timer.getTime()+addon 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. -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self -- @return #NET self
function NET:UnblockSide(Side,Seconds) function NET:UnblockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then if Side == 1 or Side == 2 then
self.BlockedSides[Side] = nil self.BlockedSides[Side] = false
end end
return self return self
end end
@@ -485,8 +498,11 @@ end
-- @param Wrapper.Client#CLIENT Client The client -- @param Wrapper.Client#CLIENT Client The client
-- @return #number PlayerID or nil -- @return #number PlayerID or nil
function NET:GetPlayerIDFromClient(Client) function NET:GetPlayerIDFromClient(Client)
self:T("GetPlayerIDFromClient")
self:T({Client=Client})
if Client then if Client then
local name = Client:GetPlayerName() local name = Client:GetPlayerName()
self:T({name=name})
local id = self:GetPlayerIDByName(name) local id = self:GetPlayerIDByName(name)
return id return id
else else
@@ -682,16 +698,19 @@ end
-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue -- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @return #number SlotID -- @return #number SlotID
function NET:GetSlot(Client) function NET:GetSlot(Client)
self:T("NET.GetSlot")
local PlayerID = self:GetPlayerIDFromClient(Client) local PlayerID = self:GetPlayerIDFromClient(Client)
self:T("NET.GetSlot PlayerID = "..tostring(PlayerID))
if PlayerID then if PlayerID then
local side,slot = net.get_slot(tonumber(PlayerID)) local side,slot = net.get_slot(tonumber(PlayerID))
self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot))
return side,slot return side,slot
else else
return nil,nil return nil,nil
end end
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 #NET self
-- @param Wrapper.Client#CLIENT Client The client -- @param Wrapper.Client#CLIENT Client The client
-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue -- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
@@ -699,19 +718,22 @@ end
-- @return #boolean Success -- @return #boolean Success
function NET:ForceSlot(Client,SideID,SlotID) function NET:ForceSlot(Client,SideID,SlotID)
local PlayerID = self:GetPlayerIDFromClient(Client) local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID) ~= 1 then local SlotID = SlotID or Client:GetID()
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' ) if PlayerID then -- and tonumber(PlayerID) ~= 1 then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID )
else else
return false return false
end end
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 #NET self
-- @param Wrapper.Client#CLIENT Client The client -- @param Wrapper.Client#CLIENT Client The client
-- @return #boolean Succes -- @return #boolean Succes
function NET:ReturnToSpectators(Client) function NET:ReturnToSpectators(Client)
local outcome = self:ForceSlot(Client,0) local outcome = self:ForceSlot(Client,0)
-- workaround
local sched = TIMER:New(Client.Destroy,Client,1):Start(1)
return outcome return outcome
end end
@@ -781,7 +803,7 @@ function NET:onafterStatus(From,Event,To)
local function HouseHold(tavolo) local function HouseHold(tavolo)
local TNow = timer.getTime() local TNow = timer.getTime()
for _,entry in pairs (tavolo) do 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
end end

View File

@@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent )
if GenerateEvent and GenerateEvent == true then if GenerateEvent and GenerateEvent == true then
if self:IsAir() then if self:IsAir() then
--self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject)
self:CreateEventCrash( timer.getTime(), DCSObject ) self:CreateEventCrash( timer.getTime(), DCSObject )
else else
--self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject)
self:CreateEventDead( timer.getTime(), DCSObject ) self:CreateEventDead( timer.getTime(), DCSObject )
end end
elseif GenerateEvent == false then elseif GenerateEvent == false then
-- Do nothing! -- Do nothing!
else else
self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) self:CreateEventRemoveUnit( timer.getTime(), DCSObject )
--self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject)
end end
USERFLAG:New( UnitGroupName ):Set( 100 ) USERFLAG:New( UnitGroupName ):Set( 100 )
@@ -142,7 +145,11 @@ function POSITIONABLE:GetPosition()
self:F2( self.PositionableName ) self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject() local DCSPositionable = self:GetDCSObject()
if self:IsInstanceOf("GROUP") then
DCSPositionable = self:GetFirstUnitAlive():GetDCSObject()
end
if DCSPositionable then if DCSPositionable then
local PositionablePosition = DCSPositionable:getPosition() local PositionablePosition = DCSPositionable:getPosition()
self:T3( PositionablePosition ) self:T3( PositionablePosition )

View File

@@ -48,8 +48,8 @@ function SCENERY:Register( SceneryName, SceneryObject )
self.SceneryObject = SceneryObject self.SceneryObject = SceneryObject
if self.SceneryObject then if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions
self.Life0 = self.SceneryObject:getLife() self.Life0 = self.SceneryObject:getLife() or 0
else else
self.Life0 = 0 self.Life0 = 0
end end
@@ -59,7 +59,7 @@ function SCENERY:Register( SceneryName, SceneryObject )
return self return self
end 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 #SCENERY self
-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved. -- @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. -- @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] return self.Properties[PropertyName]
end 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. --- Returns the scenery Properties table.
-- @param #SCENERY self -- @param #SCENERY self
-- @return #table The Key:Value table of QuadZone properties of the zone from the scenery assignment . -- @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 self.Properties[PropertyName] = PropertyValue
return self return self
end end
--- Obtain object name. --- Obtain object name.
--@param #SCENERY self --@param #SCENERY self
--@return #string Name --@return #string Name
@@ -97,7 +106,7 @@ function SCENERY:GetDCSObject()
return self.SceneryObject return self.SceneryObject
end 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% -- **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 -- 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. -- criteria for a bombing task.
@@ -105,7 +114,7 @@ end
--@return #number life --@return #number life
function SCENERY:GetLife() function SCENERY:GetLife()
local life = 0 local life = 0
if self.SceneryObject then if self.SceneryObject and self.SceneryObject.getLife then
life = self.SceneryObject:getLife() life = self.SceneryObject:getLife()
if life > self.Life0 then if life > self.Life0 then
self.Life0 = math.floor(life * 1.2) self.Life0 = math.floor(life * 1.2)
@@ -121,7 +130,7 @@ function SCENERY:GetLife0()
return self.Life0 or 0 return self.Life0 or 0
end 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 #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100). --@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100).
--@return #number life --@return #number life
@@ -133,7 +142,7 @@ function SCENERY:IsAlive(Threshold)
end end
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 #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100). --@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100).
--@return #number life --@return #number life
@@ -145,12 +154,13 @@ function SCENERY:IsDead(Threshold)
end end
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 --@param #SCENERY self
--@return #number rlife --@return #number rlife
function SCENERY:GetRelativeLife() function SCENERY:GetRelativeLife()
local life = self:GetLife() local life = self:GetLife()
local life0 = self:GetLife0() local life0 = self:GetLife0()
if life == 0 or life0 == 0 then return 0 end
local rlife = math.floor((life/life0)*100) local rlife = math.floor((life/life0)*100)
return rlife return rlife
end end

View File

@@ -61,6 +61,8 @@ function STATIC:Register( StaticName )
if DCSStatic then if DCSStatic then
local Life0 = DCSStatic:getLife() or 1 local Life0 = DCSStatic:getLife() or 1
self.Life0 = Life0 self.Life0 = Life0
else
self:E(string.format("Static object %s does not exist!", tostring(self.StaticName)))
end end
return self return self

View File

@@ -26,6 +26,7 @@
-- @field #string lid Class id string for output to DCS log file. -- @field #string lid Class id string for output to DCS log file.
-- @field DCS#Warehouse warehouse The DCS warehouse object. -- @field DCS#Warehouse warehouse The DCS warehouse object.
-- @field DCS#Airbase airbase The DCS airbase object. -- @field DCS#Airbase airbase The DCS airbase object.
-- @field Core.Timer#TIMER SaverTimer The TIMER for autosave.
-- @extends Core.Base#BASE -- @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 --- *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 -- # Weapons Helper Enumerater
-- --
-- The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. -- 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 -- @field #STORAGE
STORAGE = { STORAGE = {
@@ -173,14 +203,14 @@ STORAGE.Type = {
--- STORAGE class version. --- STORAGE class version.
-- @field #string version -- @field #string version
STORAGE.version="0.0.3" STORAGE.version="0.1.5"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot... -- TODO: A lot...
-- TODO: Persistence -- DONE: Persistence
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor -- Constructor
@@ -197,11 +227,11 @@ function STORAGE:New(AirbaseName)
self.airbase=Airbase.getByName(AirbaseName) self.airbase=Airbase.getByName(AirbaseName)
if Airbase.getWarehouse then if Airbase.getWarehouse and self.airbase then
self.warehouse=self.airbase:getWarehouse() self.warehouse=self.airbase:getWarehouse()
end end
self.lid = string.format("STORAGE %s", AirbaseName) self.lid = string.format("STORAGE %s | ", AirbaseName)
return self return self
end end
@@ -221,12 +251,12 @@ function STORAGE:NewFromStaticCargo(StaticCargoName)
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end end
self.lid = string.format("STORAGE %s", StaticCargoName) self.lid = string.format("STORAGE %s | ", StaticCargoName)
return self return self
end end
--- Create a new STORAGE object from an DCS static cargo object. --- Create a new STORAGE object from a Wrapper.DynamicCargo#DYNAMICCARGO object.
-- @param #STORAGE self -- @param #STORAGE self
-- @param #string DynamicCargoName Unit name of the dynamic cargo. -- @param #string DynamicCargoName Unit name of the dynamic cargo.
-- @return #STORAGE self -- @return #STORAGE self
@@ -235,13 +265,13 @@ function STORAGE:NewFromDynamicCargo(DynamicCargoName)
-- Inherit everything from BASE class. -- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #STORAGE local self=BASE:Inherit(self, BASE:New()) -- #STORAGE
self.airbase=Unit.getByName(DynamicCargoName) self.airbase=Unit.getByName(DynamicCargoName) or StaticObject.getByName(DynamicCargoName)
if Airbase.getWarehouse then if Airbase.getWarehouse then
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end end
self.lid = string.format("STORAGE %s", DynamicCargoName) self.lid = string.format("STORAGE %s | ", DynamicCargoName)
return self return self
end end
@@ -266,6 +296,10 @@ end
-- @return #STORAGE self -- @return #STORAGE self
function STORAGE:SetVerbosity(VerbosityLevel) function STORAGE:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0 self.verbose=VerbosityLevel or 0
if self.verbose > 1 then
BASE:TraceOn()
BASE:TraceClass("STORAGE")
end
return self return self
end end
@@ -499,7 +533,7 @@ function STORAGE:IsUnlimited(Type)
end end
-- Debug info. -- 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 end
return unlimited return unlimited
@@ -595,6 +629,247 @@ function STORAGE:GetInventory(Item)
return inventory.aircraft, inventory.liquids, inventory.weapon return inventory.aircraft, inventory.liquids, inventory.weapon
end 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 -- Private Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -385,24 +385,26 @@ function WEAPON:GetTarget()
--Target name --Target name
local name=object:getName() local name=object:getName()
-- Debug info. if name then
self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category))
-- Debug info.
if category==Object.Category.UNIT then self:T(self.lid..string.format("Got Target Object %s, category=%d", name, category))
target=UNIT:FindByName(name) if category==Object.Category.UNIT then
elseif category==Object.Category.STATIC then target=UNIT:FindByName(name)
target=STATIC:FindByName(name, false) elseif category==Object.Category.STATIC then
elseif category==Object.Category.SCENERY then target=STATIC:FindByName(name, false)
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) 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 end
end end

View File

@@ -2,7 +2,7 @@ Utilities/Enums.lua
Utilities/Utils.lua Utilities/Utils.lua
Utilities/Enums.lua Utilities/Enums.lua
Utilities/Profiler.lua Utilities/Profiler.lua
Utilities/STTS.lua Utilities/Templates.lua
Utilities/FiFo.lua Utilities/FiFo.lua
Utilities/Socket.lua Utilities/Socket.lua