Compare commits

...

140 Commits

Author SHA1 Message Date
Thomas c335881b2d Merge pull request #2505 from shaji-Dev/master
[ADDED] 268 Missing Warehouse EQP ENUMS
2026-02-09 18:09:24 +01:00
smiki 993fc45e83 [ADDED] 268 Missing Warehouse EQP ENUMS 2026-02-09 16:48:32 +01:00
Thomas 130d358f4a Merge pull request #2499 from shaji-Dev/master
[FIXED] nil function
2026-02-06 12:37:28 +01:00
shaji-Dev 49476abb9f Merge branch 'FlightControl-Master:master' into master 2026-02-06 11:27:21 +01:00
smiki a02b99036b [FIXED] nil function 2026-02-06 11:26:57 +01:00
Applevangelist dab6f48fe9 xx 2026-02-05 12:20:48 +01:00
Thomas 23c9ea0961 Merge pull request #2497 from shaji-Dev/master
Optimizations
2026-02-05 12:18:41 +01:00
smiki d0b04ebbbd Optimizations 2026-02-05 10:52:44 +01:00
smiki bd64d084e9 Optimizations 2026-02-05 10:49:48 +01:00
Thomas c91b59f8cd Merge pull request #2495 from shaji-Dev/master
COMMANDER and CHIEF fixes and additions
2026-01-30 06:39:41 +01:00
smiki a699300f5f [ADDED] COMMANDER:CanMission(Mission) and CHIEF:CanMission(Mission) 2026-01-29 21:43:27 +01:00
smiki 6e12c5c1d0 [FIX] Added Alias to COMMANDER init from CHIEF 2026-01-29 18:34:37 +01:00
smiki 852fc1d2d5 Merge branch 'master' of https://github.com/shaji-Dev/MOOSE
# Conflicts:
#	Moose Development/Moose/Core/Event.lua
2026-01-29 18:33:35 +01:00
smiki c2c957b859 [FIX] Added Alias to COMMANDER init from CHIEF 2026-01-29 18:32:04 +01:00
Applevangelist f20c22ab49 #EVENT - Save some memory by avoiding to register SCENERY yet unregistered and avoiding creating COORDINATEs on Marks (still has a Vec3) when not switched on 2026-01-28 14:41:51 +01:00
smiki 1e200511cb [FIX] Memory leak caused by continuous F10 redrawing that for marker (add, remove) which creates new coordinate. 2026-01-28 12:06:11 +01:00
smiki 80f2a0ef39 [FIX] Memory leak caused by continuous F10 redrawing that for marker (add, remove) which creates new coordinate. 2026-01-28 12:05:32 +01:00
smiki ae156259c0 [FIX] Memory leak caused by players bombing neighborhoods and creating hundreds of Dead events for scenery that registers their objects. 2026-01-28 12:01:53 +01:00
smiki 39e3f8b646 [FIX] Memory leak caused by continuous F10 redrawing that for marker (add, remove) which creates new coordinate. 2026-01-28 11:56:09 +01:00
Applevangelist ac61c3a55c #MANTIS - Added more flags for systems able to shoot down incoming missiles
#SEAD/#SHORAD/#MANTIS - Fixed logic and governance problems with SHORAD engagement
2026-01-25 13:14:53 +01:00
Thomas 1054ffa7ac Merge pull request #2492 from leka1986/patch-8
Update CTLD.lua
2026-01-24 18:46:17 +01:00
leka1986 41503ae625 Update CTLD.lua
fixed menu getting deleted.
2026-01-24 18:00:25 +01:00
Applevangelist 1ee90e67b2 #UTILS - LoadStationaryListOfGroups() added option to (randomly) restore dead units on reload. 2026-01-24 16:21:37 +01:00
Applevangelist d0ce7f1aa6 xx 2026-01-24 16:16:50 +01:00
Thomas c387749a81 Merge pull request #2490 from leka1986/patch-7
Fixed bugs
2026-01-23 19:21:22 +01:00
Applevangelist b980c5d93a #EasyA2G/GCICAP - Added option to set Squadron Turnovertime 2026-01-23 18:20:24 +01:00
leka1986 49332fafec Fixed bugs
Fixed bugs for load troops.

Added Mi-8 to the prevention method for ignoring what is inside when trying to build crates.

Removed return self if the player is trying to build stuff that is outside the helicopter or C-130, while he still have stuff inside. Cargo will be built but it will ignore what is inside.

Removed the menu "Others" if there is no sub category involved for that type. If user have crates subcat but not troops subcat, it won't show "others".
2026-01-22 18:45:23 +01:00
Applevangelist 3ab3278db4 xx 2026-01-22 17:18:59 +01:00
Applevangelist 7a3ea2918d #SEAD/#MANTIS - Adding functions for non-suppression of groups capable of HARM defense
#CTLD/#CSAR - Added data for A/M-H6J
2026-01-22 12:35:16 +01:00
Thomas 9c0394f035 Update Utils.lua
#UTILS - Added  `UTILS.Vec3toVec2(Vec3)`
2026-01-20 11:38:34 +01:00
Applevangelist 9536d368b0 xx 2026-01-12 13:13:48 +01:00
Applevangelist 9426905959 #AIRBASE - Fix parking data table could be nil 2026-01-12 08:48:37 +01:00
Applevangelist 99b7b4b057 #EASYGCICAP - Fix error in setting floor/ceiling for corridors 2026-01-09 15:38:05 +01:00
Applevangelist 2fdbdb4058 #RECOVERYTANKER - Ensure correct "F10 Name" for A6E if used as tanker 2026-01-08 18:51:13 +01:00
Applevangelist 898405e15f Merge remote-tracking branch 'origin/master' 2026-01-08 13:11:34 +01:00
Applevangelist 7259038b1c xx 2026-01-08 13:10:48 +01:00
Applevangelist 3886351490 xx 2026-01-08 13:10:37 +01:00
Thomas 3aedf8e5d0 Merge pull request #2487 from leka1986/patch-14
Update CTLD.lua
2026-01-05 06:58:07 +01:00
leka1986 aed2f1d8a9 Enhance troop loading logic and messaging
noticed a bug, I could load troops outside the zone when choosing amount. I could also load without the door was even checked.

Combined the troop drop to one message instead of multiple messages for each dropped group.
2026-01-05 04:18:57 +01:00
leka1986 3504797147 Update CTLD.lua
Fixed so the Engineer can remove all the items when he is building from an airdrop.

Added a function that I use, CanGetCrates, it's a check before getting it if the conditions are met.

Added small fixes where calling FindCratesNearBy would remove the units when it's called by the function RemoveCratesNearBy when it was checking the surroundings and it should ignore what is inside. In the case of C-130 and the CH-47.

Added how many crates are dropped when getting statics.

Expanded OnAfterTroopsRTB to accept chunk, ie the number of units which is returned.
2026-01-05 03:43:28 +01:00
Applevangelist 3cfaa60ebb #VARIOUS fixes 2026-01-04 17:52:55 +01:00
Applevangelist 2d930ff2dd #SCORING - missed data for saving in format() 2026-01-03 17:26:17 +01:00
Frank 11a8792398 Update Warehouse.lua
- fixed bug that assets cannot be found in DB and therefore are not added to the requesting warehouse
2025-12-31 13:17:13 +01:00
Applevangelist f7556fac0e #UNIT - Added GetFuelMassMax() and GetCurrentFileKgs() 2025-12-30 10:40:27 +01:00
Applevangelist 394f3ffd88 #AIRBASE - Small fix for #ParkingSpots == 0 2025-12-30 09:26:07 +01:00
Applevangelist a3d7b4eba9 #AIRWING - Check if STORAGE object has NO warehouse! 2025-12-29 18:08:53 +01:00
Applevangelist b94f36dfdf #AIRBASE - Added AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark)
#CSAR - Use `AIRBASE:GetMinimumBoundingCircleFromParkingSpots()` to determine if we're actually standing on an airbase somewhere
2025-12-29 14:01:10 +01:00
Applevangelist 8eae7ac26b #UTILS - Added functions to get a (minimum) bounding circle from a table of VEC2s 2025-12-29 13:43:45 +01:00
Applevangelist 7bac1d3b92 #UTILS - Added CALLSIGN.Intruder enumeration for the A-6E 2025-12-29 12:36:30 +01:00
Applevangelist b31b11cda8 #CSAR - Enforce pilot names on AI for saving
#SPAWN - Added use of CALLSIGN.Intruder for random callsigns of the A-6E
2025-12-29 12:35:59 +01:00
Applevangelist d000ad76f8 #CSAR - Corrected Airbase dist finding solution 2025-12-29 10:48:23 +01:00
Applevangelist 29b244d843 #AIRWING - make airwing fill airbase storage with aircraft types when adding a Squadron and if necessary, ie not unlimited storage and storage lower than squadron assets number 2025-12-28 17:53:49 +01:00
Applevangelist 1eb6dbc7f2 xx 2025-12-28 16:51:41 +01:00
Applevangelist 945b9bdf31 #ZONE - Typo in docu 2025-12-28 14:05:24 +01:00
Applevangelist b75e383956 #SCORING - enable saving and loading of per-game name scores for persistence across restarts 2025-12-28 14:05:24 +01:00
Applevangelist 15de1d58b0 #PLAYERTASK
* Changed startup timing slightly to allow task reloading working better
* Changed coordinate output format for INTERCEPT tasks on non A2A managers to A2A
2025-12-28 14:05:24 +01:00
Applevangelist 79ce3871ea #INTEL - Added option to read properties CorridorCeiling and CorridorFloor from Zone properties to allow individual settings per zone 2025-12-28 14:05:24 +01:00
Thomas 84c68ccf14 Merge pull request #2484 from shaji-Dev/master
[ADDED] Hidden options to SPAWNSTATIC
2025-12-28 09:00:26 +01:00
Shafik 93d0a8b674 [ADDED] Hidden options to SPAWNSTATIC 2025-12-28 09:43:45 +02:00
Shafik 8a33ccd356 [ADDED] Hidden options to SPAWNSTATIC 2025-12-28 02:49:14 +02:00
Thomas 9473cc2b27 Merge pull request #2482 from leka1986/patch-12
Bug fixes and functions.
2025-12-24 08:20:03 +01:00
leka1986 448d668b44 Bug fixes and functions.
Fixed a bug where units could still get items all though not in load zone.

Added CanGetCrates, it's a function where user can hook custom conditions.

Added CanGetUnits, same as the above.

Added CanBuildCrates, same as the first one.
2025-12-24 00:00:30 +01:00
Applevangelist 8c0e021e82 #CTLD - Make FSM event CratesDropped useable for tasking 2025-12-23 17:16:57 +01:00
Applevangelist a72d3afd1c xx 2025-12-22 14:09:37 +01:00
Applevangelist 68aab10902 xx 2025-12-22 12:21:42 +01:00
Applevangelist b712c44567 xx 2025-12-21 19:17:02 +01:00
Applevangelist 12a118d08f xx 2025-12-21 18:48:33 +01:00
Applevangelist b1b9db7333 xx 2025-12-21 18:48:24 +01:00
Applevangelist 5f920a46fd #PLAYERTASK - Added INTEL Corridors; added persistence for certain tasks as option 2025-12-21 15:39:19 +01:00
Applevangelist 23e3aefaf1 Files 2025-12-21 15:38:47 +01:00
Applevangelist 74e38f9755 #MANTIS - Added INTEL Corridors 2025-12-21 15:38:39 +01:00
Applevangelist 69a781a710 #INTEL - Added Corridors 2025-12-21 15:38:30 +01:00
Applevangelist 2c82513e54 #EASYGCICAP - Added INTEL Corridors 2025-12-21 15:38:14 +01:00
Applevangelist 8eeff7efa2 #EASYAG Added 2025-12-21 15:37:46 +01:00
Applevangelist 7fcac6159c #AWACS - Added INTEL Corridors 2025-12-21 15:37:34 +01:00
Applevangelist a5eef3087b #CSAR - added option for Smoke Closest MASH, adjusted logic for airbase rescues 2025-12-21 12:49:43 +01:00
Applevangelist af891f3fc2 #2479 Added S-300VM/S-300V4/S-400 SAM data 2025-12-21 11:43:29 +01:00
Thomas 8e47a17c96 Merge pull request #2480 from shaji-Dev/master
[ADDED] Altitude AGL option for POSITIONABLE
2025-12-21 09:25:35 +01:00
Shafik c2c78dfe78 [ADDED] Altitude AGL option for POSITIONABLE 2025-12-21 09:15:44 +02:00
Thomas 26742432f5 Merge pull request #2477 from leka1986/patch-11
Update CTLD.lua
2025-12-20 14:06:06 +01:00
leka1986 6a13e593ec Update CTLD.lua
Added:
OnAfrerGetCrates, Only triggers when player uses "Get"

Added:
OnAfterRemoveCratedNearBy, Only triggers when player actively uses Remove crates near by or remove units near by.

Added: 
Partial load. currently, if a player land 3 crates (Farp) and want to partially load another farp, even though it can not fit, some want to load partially, this allows for it. 
Player will be able to see what actually fits in the menu, but if they want, they can open the menu again, select the item, then it will say "Get" "Crates limit reached" and "Partially load"

Added:
AddStaticsCargo can now accept a last parms, UnitType, this parm will make the cargo only visible in the menu to that passed unitType.

Removed Get and Load from CH-47 for the statics if enableChinookGCLoading is enabled. This will make it less confusing, as it didn't allow players do load anything except Ground crew for statics.
2025-12-19 18:44:13 +01:00
Thomas 8d8b886ad6 Merge pull request #2474 from shaji-Dev/master
Removed MARKEROPS_BASE:_MatchTag log spam
2025-12-18 10:19:13 +01:00
Shafik 2e6cc61450 Removed MARKEROPS_BASE:_MatchTag log spam 2025-12-18 10:31:45 +02:00
Shafik 6859dc9142 Merge branch 'master' of https://github.com/shaji-Dev/MOOSE
# Conflicts:
#	Moose Development/Moose/Ops/CTLD.lua
2025-12-18 10:03:54 +02:00
Shafik b871f728df PULL 2025-12-18 10:03:45 +02:00
Thomas 87b87fa2ca Update Database.lua 2025-12-17 19:34:06 +01:00
Applevangelist a39f39646d #MARKEROPS - Put case sensitivity switching into own functions 2025-12-17 11:31:55 +01:00
Thomas 157f9a82b1 Merge pull request #2470 from leka1986/patch-10
Update CTLD.lua
2025-12-16 08:39:02 +01:00
leka1986 795f58dc9b Update CTLD.lua
Fixing error when the savefile loads statics in the onafterLoad, it blows up.

attempt to call method 'GetStaticResourceMap' (a nil value)
2025-12-15 22:42:29 +01:00
Thomas 976f92e5b1 Merge pull request #2468 from leka1986/patch-9
Update CTLD.lua
2025-12-15 17:41:06 +01:00
leka1986 25daa67554 Update CTLD.lua
Fixed error when using Dynamic cargo 
"concatenate local 'gname'"

and the AddStaticCargo did not respect the template staticType.
2025-12-15 17:29:54 +01:00
Applevangelist 33ff723bd8 Further refinement for acoustic detection and smoke 2025-12-15 12:13:04 +01:00
Applevangelist 34f3e24d3a #MANTIS - Fixing over-smoke and ensure helos are not attacked by long-range systems 2025-12-15 09:40:42 +01:00
Applevangelist b6a90d4633 #GROUP Added GROUP:SetFormation(Formation) 2025-12-14 16:05:18 +01:00
Applevangelist 0e960b77b9 #MANTIS - Mantis can use acoustic detection, e.g. for Helos sneaking in on our position: my_mantis:SetAccousticDetectionOn(2000,{Unit.Category.HELICOPTER}) 2025-12-14 14:37:00 +01:00
Applevangelist 093526f4ff #ZONE - Added optional coalition filter on GetScannedUnitSet() and GetScannedGroupSet() 2025-12-14 14:35:50 +01:00
Applevangelist 8160aad1bf #INTELLIGENCE - We can optionally detect units accousticly 2025-12-14 14:34:54 +01:00
Applevangelist a88750908e #WAREHOUSE - Nil fix 2025-12-11 15:43:33 +01:00
Thomas aca3080c9a Merge pull request #2467 from leka1986/patch-8
Update CTLD.lua
2025-12-11 15:10:01 +01:00
leka1986 948158e85a Update CTLD.lua
Fixed bugs, 

Added RemoveStockUnits.

Tested and tested, works as intended.
2025-12-11 11:27:05 +01:00
Thomas 98539eee87 Merge branch 'develop' into master 2025-12-10 09:58:13 +01:00
Thomas 223c1650d9 Update PseudoATC.lua 2025-12-10 09:48:16 +01:00
Thomas aa2133e72a Update build-includes.yml 2025-12-10 09:43:20 +01:00
Applevangelist 498d58599b xx 2025-12-10 09:38:49 +01:00
Thomas a0c0fe36ba Merge pull request #2465 from FlightControl-Master/master
Merge to master
2025-12-09 20:09:09 +01:00
Thomas 76b977ff98 Merge pull request #2464 from leka1986/patch-7
Added Get Units and New stuff for the C-130J
2025-12-09 20:07:07 +01:00
leka1986 878eaf4301 Added Get Units and New stuff for the C-130J
Added

* UseC130LoadAndUnload toggle, false by default.
* C130basetype = "cds_crate" -- only for the C-130
* Extended AddCratesCargo & AddCratesCargoNoMove with another parms where we can force Typename for the static for the C-130, even though a parms passed before it with another shape. the last parms will still be forced only if the unit is C-130J. else, the last parms will not be used.

Example usage:
Foothold_ctld:AddCratesCargo("FARP",{"CTLD_TROOP_FOB"},CTLD_CARGO.Enum.FOB,3,1500,10, "FARP",nil,nil,nil,"Cargos","ammo_cargo",nil, "cds_crate")
Blocked the build of crates if still inside C-130J and CH-47F using 15 Meters for the C-130 and 5 meters for the CH-47.

ListCargo will now display what's inside besed on _GetUnitCapabilities / 2


** Manage Units, New menu only for the C-130J.

* New menu under CTLD. Get Units, Remove units nearby.
* Addunits function.
function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes)
Example usage.

Foothold_ctld:AddUnits("Humvee",{"CTLD_CARGO_HMMWV"},CTLD_CARGO.Enum.VEHICLE,10, "ANTI TANK")

Those will be placed on a safe distance using same method as crates based on length and (idx-1)*2.5 + length.

* Added FSM, OnAfterUnitsSpawn. No internal, only Hook.

* Added 3 functions,
AddStockUnits
SetStockUnits
GetStockUnits

Added documents.
2025-12-09 19:45:56 +01:00
Applevangelist 240d3460d7 xx 2025-12-07 15:13:50 +01:00
Applevangelist ae0d405350 #PSEUDOATC - Added functionality to read the Radio.lua from the map data and display radio information per Airport 2025-12-07 15:12:37 +01:00
Applevangelist 45fc0eb9da Merge remote-tracking branch 'origin/master' into develop 2025-12-07 13:39:56 +01:00
Applevangelist 3327c3b24d #GROUP Small fix for getting templates data from dynamic spawns (there are no template data!) 2025-12-07 13:39:23 +01:00
Applevangelist c6521d9c38 Merge remote-tracking branch 'origin/master' into develop 2025-12-07 13:02:45 +01:00
Applevangelist 049529ecf0 #CTLD Allow hover extracting Troop objects 2025-12-07 13:02:17 +01:00
Applevangelist c2b8eea010 #RAT small fix for Tonfinal being nil sometimes 2025-12-07 13:01:53 +01:00
Thomas da8856c907 Merge pull request #2463 from FlightControl-Master/master
Merge from master
2025-12-06 11:50:12 +01:00
Thomas 04f7bb7cc7 Merge pull request #2458 from leka1986/patch-3
Convert cargo type to string for logging
2025-12-06 11:43:19 +01:00
Thomas 75c01fbe86 Merge pull request #2459 from leka1986/patch-4
Add check for IniGroup in HandleEventShot
2025-12-06 11:43:09 +01:00
Thomas 03b79351db Merge pull request #2460 from leka1986/patch-5
Update Scenery.lua
2025-12-06 11:42:51 +01:00
Thomas 33429766a0 Merge pull request #2461 from leka1986/patch-6
Update Set.lua
2025-12-06 11:42:39 +01:00
leka1986 83464090d6 Update Set.lua
removed duplicate Filter = {},
2025-12-06 10:20:03 +01:00
leka1986 d77b2655b1 Update Scenery.lua
attempt to call method getPoint (a nil value)
2025-12-06 10:16:15 +01:00
leka1986 31a537748e Add check for IniGroup in HandleEventShot
Added a check for IniGroup in HandleEventShot to prevent processing if it's not present.
Error in 2 instances, attempt to index field IniGroup ( a nil value )
2025-12-06 10:13:37 +01:00
leka1986 e873b097cf Convert cargo type to string for logging
Concatenate local 'type' Nil value
2025-12-06 10:10:18 +01:00
Frank 07a3095392 Merge branch 'master' into develop 2025-12-02 10:36:55 +01:00
Frank e1b5c7f9c2 Merge pull request #2457 from shaji-Dev/master
[ADDED] C-130J loadouts enums and other missing weapons
2025-12-02 10:36:01 +01:00
Shafik f005cdbc71 [ADDED] C-130J loadouts enums and other missing weapons. 2025-12-02 11:27:57 +02:00
Applevangelist 7fa360f32c Merge remote-tracking branch 'origin/master' into develop 2025-11-28 12:49:54 +01:00
Applevangelist 30c6937910 #CTLD Fix find crates also looking into static cargos 2025-11-28 12:48:26 +01:00
Applevangelist 28724f396c Merge remote-tracking branch 'origin/master' into develop 2025-11-28 10:20:13 +01:00
Applevangelist fb3ca6d9d5 #SCORING nil check added 2025-11-28 10:19:46 +01:00
Applevangelist 6e75e58952 #RESCUEHELO small fix 2025-11-28 09:16:08 +01:00
Thomas 0abb0db2a3 Merge pull request #2456 from FlightControl-Master/master
Merge from master
2025-11-27 18:07:18 +01:00
Thomas edb42bbbe9 Merge pull request #2455 from shaji-Dev/master
[ADDED] C-130J Utils
2025-11-27 18:06:41 +01:00
Shafik 3bebe7f69c [ADDED] C-130J Utils 2025-11-27 18:25:19 +02:00
Applevangelist 7605b582d1 Merge branch 'master' into develop 2025-11-27 09:22:11 +01:00
Applevangelist 6ed031762b #BUGFIXING 2025-11-27 09:21:17 +01:00
Thomas 945efc0516 Merge pull request #2454 from FlightControl-Master/master
Update Artillery.lua
2025-11-26 10:11:11 +01:00
Thomas 4e342a59be Update Artillery.lua
#ARTY fix a small error for coordinate of smoke shell creation
2025-11-26 10:10:10 +01:00
Thomas 8e517bb9cf Merge pull request #2453 from FlightControl-Master/master
Update Net.lua
2025-11-24 09:35:23 +01:00
Thomas dacf5125bf Update Net.lua
#NET Fix for GetPlayerIDByName()
2025-11-24 09:24:37 +01:00
43 changed files with 5156 additions and 828 deletions
+1 -1
View File
@@ -105,7 +105,7 @@ jobs:
if: ${{ vars.SKIP_LUACHECK != true }} if: ${{ vars.SKIP_LUACHECK != true }}
continue-on-error: true continue-on-error: true
run: | run: |
luacheck --std=lua51c --config=.luacheckrc -gurasqq "Moose Development/Moose" luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
- name: Run LuaSrcDiet - name: Run LuaSrcDiet
run: | run: |
+1 -1
View File
@@ -1820,7 +1820,7 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
if Event.IniDCSUnit then if Event.IniDCSUnit then
-- Player entering a CA slot -- Player entering a CA slot
if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then if Event.IniObjectCategory == 1 and Event.IniUnit and Event.IniUnit:IsGround() then
local IsPlayer = Event.IniDCSUnit:getPlayerName() local IsPlayer = Event.IniDCSUnit:getPlayerName()
if IsPlayer then if IsPlayer then
+19 -37
View File
@@ -184,6 +184,7 @@ EVENT = {
ClassName = "EVENT", ClassName = "EVENT",
ClassID = 0, ClassID = 0,
MissionEnd = false, MissionEnd = false,
CreateMarkCoordinateOnEvent = false,
} }
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000 world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
@@ -339,14 +340,18 @@ EVENTS = {
-- @field #string WeaponName Name of the weapon. -- @field #string WeaponName Name of the weapon.
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon. -- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
-- --
-- @field Cargo.Cargo#CARGO Cargo The cargo object.
-- @field #string CargoName The name of the cargo object.
--
-- @field Core.Zone#ZONE Zone The zone object. -- @field Core.Zone#ZONE Zone The zone object.
-- @field #string ZoneName The name of the zone. -- @field #string ZoneName The name of the zone.
-- --
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object. -- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
-- @field #string IniDynamicCargoName The dynamic cargo unit name. -- @field #string IniDynamicCargoName The dynamic cargo unit name.
--
-- @field #number MarkCoalition Coalition of a marker (if any)
-- @field DCS#Vec3 MarkVec3 Position of a marker
-- @field #string MarkText Text content of a marker, if any
-- @field #number MarkID ID of the marker, for deletion
-- @field #number MarkGroupID Group ID of the group that created the marker
-- @field Core.Point#COORDINATE Coordinate object of the marker, only filled if EVENT.CreateMarkCoordinateOnEvent is set to true (off by default)
@@ -1063,36 +1068,6 @@ end
do -- Event Creation do -- Event Creation
--- Creation of a New Cargo Event.
-- @param #EVENT self
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
function EVENT:CreateEventNewCargo( Cargo )
self:F( { Cargo } )
local Event = {
id = EVENTS.NewCargo,
time = timer.getTime(),
cargo = Cargo,
}
world.onEvent( Event )
end
--- Creation of a Cargo Deletion Event.
-- @param #EVENT self
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
function EVENT:CreateEventDeleteCargo( Cargo )
self:F( { Cargo } )
local Event = {
id = EVENTS.DeleteCargo,
time = timer.getTime(),
cargo = Cargo,
}
world.onEvent( Event )
end
--- Creation of a New Zone Event. --- Creation of a New Zone Event.
-- @param #EVENT self -- @param #EVENT self
-- @param Core.Zone#ZONE_BASE Zone The Zone created. -- @param Core.Zone#ZONE_BASE Zone The Zone created.
@@ -1361,7 +1336,8 @@ function EVENT:onEvent( Event )
Event.IniDynamicCargoName = Event.IniUnitName Event.IniDynamicCargoName = Event.IniUnitName
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
else else
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) --Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
end end
Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniCategory = Event.IniDCSUnit:getDesc().category
@@ -1374,7 +1350,9 @@ function EVENT:onEvent( Event )
Event.IniDCSUnit = Event.initiator Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000) Event.IniDCSUnitName = ( Event.IniDCSUnit and 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 ) local ID = (Event.IniDCSUnit and Event.IniDCSUnit.getID) and Event.IniDCSUnit:getID() or Event.IniDCSUnitName
Event.IniUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
--Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
Event.IniTypeName = (Event.initiator and Event.initiator.isExist Event.IniTypeName = (Event.initiator and Event.initiator.isExist
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY" and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
@@ -1478,7 +1456,9 @@ function EVENT:onEvent( Event )
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil
if Event.TgtDCSUnitName~=nil then if Event.TgtDCSUnitName~=nil then
Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) local ID = (Event.TgtDCSUnit and Event.TgtDCSUnit.getID) and Event.TgtDCSUnit:getID() or Event.TgtDCSUnitName
--Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
Event.TgtUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end end
@@ -1519,7 +1499,9 @@ function EVENT:onEvent( Event )
if Event.idx then if Event.idx then
Event.MarkID=Event.idx Event.MarkID=Event.idx
Event.MarkVec3=Event.pos Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) if self.CreateMarkCoordinateOnEvent == true then
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
end
Event.MarkText=Event.text Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition Event.MarkCoalition=Event.coalition
Event.IniCoalition=Event.coalition Event.IniCoalition=Event.coalition
+26 -13
View File
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS", ClassName = "MARKEROPS",
Tag = "mytag", Tag = "mytag",
Keywords = {}, Keywords = {},
version = "0.1.4", version = "0.1.5",
debug = false, debug = false,
Casesensitive = true, Casesensitive = true,
} }
@@ -59,9 +59,8 @@ MARKEROPS_BASE = {
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param #string Tagname Name to identify us from the event text. -- @param #string Tagname Name to identify us from the event text.
-- @param #table Keywords Table of keywords recognized from the event text. -- @param #table Keywords Table of keywords recognized from the event text.
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
-- @return #MARKEROPS_BASE self -- @return #MARKEROPS_BASE self
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) function MARKEROPS_BASE:New(Tagname,Keywords)
-- Inherit FSM -- Inherit FSM
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
@@ -72,11 +71,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
self.debug = false self.debug = false
self.Casesensitive = true self.Casesensitive = true
if Casesensitive and Casesensitive == false then
self.Casesensitive = false
end
----------------------- -----------------------
--- FSM Transitions --- --- FSM Transitions ---
----------------------- -----------------------
@@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
end end
--- (internal) Handle events. --- (Internal) Handle events.
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param Core.Event#EVENTDATA Event -- @param Core.Event#EVENTDATA Event
function MARKEROPS_BASE:OnEventMark(Event) function MARKEROPS_BASE:OnEventMark(Event)
@@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event)
end end
end end
--- (internal) Match tag. --- (Internal) Match tag.
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker. -- @param #string Eventtext Text added to the marker.
-- @return #boolean -- @return #boolean
function MARKEROPS_BASE:_MatchTag(Eventtext) function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches = false local matches = false
if not self.Casesensitive then --self:I(self.lid .. "Casesensitive "..tostring(self.Casesensitive))
if self.Casesensitive == false then
--self:I(self.lid .. "Marker non-casesensitive "..Eventtext)
local type = string.lower(self.Tag) -- #string local type = string.lower(self.Tag) -- #string
if string.find(string.lower(Eventtext),type) then if string.find(string.lower(Eventtext),type,1,true) then
matches = true --event text contains tag matches = true --event text contains tag
end end
else else
@@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext)
return matches return matches
end end
--- (internal) Match keywords table. --- (Internal) Match keywords table.
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker. -- @param #string Eventtext Text added to the marker.
-- @return #table -- @return #table
@@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:UnHandleEvent(EVENTS.MarkRemoved) self:UnHandleEvent(EVENTS.MarkRemoved)
end end
--- Switch off case sensitive matching
-- @param #MARKEROPS_BASE self
-- @return self
function MARKEROPS_BASE:SwitchCaseSensitiveOff()
self.Casesensitive = false
return self
end
--- Switch on case sensitive matching
-- @param #MARKEROPS_BASE self
-- @return self
function MARKEROPS_BASE:SwitchCaseSensitiveOn()
self.Casesensitive = true
return self
end
-------------------------------------------------------------------------- --------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition End. -- MARKEROPS_BASE Class Definition End.
-------------------------------------------------------------------------- --------------------------------------------------------------------------
+14 -1
View File
@@ -3281,7 +3281,6 @@ do -- COORDINATE
return delta/60 return delta/60
end end
--- Return a BR string from a COORDINATE to the COORDINATE. --- Return a BR string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @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.
@@ -3295,6 +3294,20 @@ do -- COORDINATE
local Distance = self:Get2DDistance( FromCoordinate ) local Distance = self:Get2DDistance( FromCoordinate )
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
end end
--- Return a Bearing string from a COORDINATE to the (self) COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
-- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA
-- @param #number Precision Rounding precision, currently full km as default (=0)
-- @return #string The BR text.
function COORDINATE:ToStringBearing( FromCoordinate, Settings, MagVar, Precision )
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
--local Distance = self:Get2DDistance( FromCoordinate )
return self:GetBearingText(AngleRadians,Precision,Settings,MagVar)
end
--- Return a BRA string from a COORDINATE to the COORDINATE. --- Return a BRA string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-1
View File
@@ -89,7 +89,6 @@ do -- SET_BASE
Index = {}, Index = {},
Database = nil, Database = nil,
CallScheduler = nil, CallScheduler = nil,
Filter = {},
FilterCoalitionNumbers = { FilterCoalitionNumbers = {
[coalition.side.RED+1] = "red", [coalition.side.RED+1] = "red",
[coalition.side.BLUE+1] = "blue", [coalition.side.BLUE+1] = "blue",
+8
View File
@@ -1667,6 +1667,9 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
-- If RandomizeUnits, then Randomize the formation at the start point. -- If RandomizeUnits, then Randomize the formation at the start point.
if self.SpawnRandomizeUnits then if self.SpawnRandomizeUnits then
if self.SpawnRandomizePosition then
PointVec3 = COORDINATE:New( SpawnTemplate.x, SpawnTemplate.route.points[1].alt, SpawnTemplate.y )
end
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
if (SpawnZone) then if (SpawnZone) then
@@ -3518,6 +3521,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
max = 18 max = 18
ctable = CALLSIGN.F15E ctable = CALLSIGN.F15E
end end
if SpawnTemplate.units[1].type == "A6E" then
min = 4
max = 18
ctable = CALLSIGN.Intruder
end
local callsignnr = math.random(min,max) local callsignnr = math.random(min,max)
local callsignname = "Enfield" local callsignname = "Enfield"
for name, value in pairs(ctable) do for name, value in pairs(ctable) do
@@ -334,6 +334,31 @@ function SPAWNSTATIC:InitCargo(IsCargo)
return self return self
end end
--- Hide the static on the map view (visible to game master slots!).
-- @param #SPAWN self
-- @param #boolean OnOff Defaults to true
-- @return #SPAWN The SPAWN object
function SPAWNSTATIC:InitHiddenOnMap(OnOff)
self.SpawnHiddenOnMap = OnOff == false and false or true
return self
end
--- Hide the static on MFDs (visible to game master slots!).
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWNSTATIC:InitHiddenOnMFD()
self.SpawnHiddenOnMFD = true
return self
end
--- Hide the static on planner (visible to game master slots!).
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWNSTATIC:InitHiddenOnPlanner()
self.SpawnHiddenOnPlanner = true
return self
end
--- Initialize as dead. --- Initialize as dead.
-- @param #SPAWNSTATIC self -- @param #SPAWNSTATIC self
-- @param #boolean IsDead If true, this static is dead. -- @param #boolean IsDead If true, this static is dead.
@@ -533,6 +558,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.mass=self.InitStaticCargoMass Template.mass=self.InitStaticCargoMass
end end
-- hiding options
if self.SpawnHiddenOnPlanner then
Template.hiddenOnPlanner=true
end
if self.SpawnHiddenOnMFD then
Template.hiddenOnMFD=true
end
if self.SpawnHiddenOnMap then
Template.hidden=self.SpawnHiddenOnMap
end
if self.InitLinkUnit then if self.InitLinkUnit then
Template.linkUnit=self.InitLinkUnit:GetID() Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true Template.linkOffset=true
+30 -14
View File
@@ -783,7 +783,7 @@ end
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param #string From -- @param #string From
-- @param #string Event -- @param #string Event
-- @param #string to -- @param #string To
-- @return #ZONE_BASE self -- @return #ZONE_BASE self
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To) function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
if self:GetState() ~= "TriggerStopped" then if self:GetState() ~= "TriggerStopped" then
@@ -1158,7 +1158,7 @@ end
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) -- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {} self.ScanData = {}
self.ScanData.Coalitions = {} self.ScanData.Coalitions = {}
self.ScanData.Scenery = {} self.ScanData.Scenery = {}
@@ -1168,7 +1168,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local ZoneCoord = self:GetCoordinate():SetAlt() local ZoneCoord = self:GetCoordinate():SetAlt()
local ZoneRadius = self:GetRadius() local ZoneRadius = self:GetRadius()
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) --self:I({x = ZoneCoord.x, y=ZoneCoord.y, z=ZoneCoord.z, ZoneRadius = ZoneRadius})
local SphereSearch = { local SphereSearch = {
id = world.VolumeType.SPHERE, id = world.VolumeType.SPHERE,
@@ -1180,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local function EvaluateZone( ZoneObject ) local function EvaluateZone( ZoneObject )
--if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5
if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then
-- Get object category. -- Get object category.
@@ -1265,38 +1266,48 @@ end
--- Get a set of scanned units. --- Get a set of scanned units.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number Coalition (optional) Filter for this coalition only.
-- @return Core.Set#SET_UNIT Set of units and statics inside the zone. -- @return Core.Set#SET_UNIT Set of units and statics inside the zone.
function ZONE_RADIUS:GetScannedSetUnit() function ZONE_RADIUS:GetScannedSetUnit(Coalition)
local SetUnit = SET_UNIT:New()
self.SetUnit = self.SetUnit or SET_UNIT:New()
self.SetUnit:Clear(false)
self.SetUnit.Set={}
if self.ScanData then if self.ScanData then
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
local UnitObject = UnitObject -- DCS#Unit local UnitObject = UnitObject -- DCS#Unit
if UnitObject:isExist() then if UnitObject:isExist() then
local FoundUnit = UNIT:FindByName( UnitObject:getName() ) local FoundUnit = UNIT:FindByName( UnitObject:getName() )
if FoundUnit then local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
SetUnit:AddUnit( FoundUnit ) local includeoncoalition = true
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
if Coalition == nil then includeoncoalition = true end
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
if FoundUnit and includeoncoalition then
self.SetUnit:AddUnit( FoundUnit )
else else
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false ) local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
if FoundStatic then if FoundStatic then
SetUnit:AddUnit( FoundStatic ) self.SetUnit:AddUnit( FoundStatic )
end end
end end
end end
end end
end end
return SetUnit return self.SetUnit
end end
--- Get a set of scanned groups. --- Get a set of scanned groups.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number Coalition (optional) Filter for this coalition only.
-- @return Core.Set#SET_GROUP Set of groups. -- @return Core.Set#SET_GROUP Set of groups.
function ZONE_RADIUS:GetScannedSetGroup() function ZONE_RADIUS:GetScannedSetGroup(Coalition)
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
self.ScanSetGroup:Clear(false)
self.ScanSetGroup.Set={} self.ScanSetGroup.Set={}
if self.ScanData then if self.ScanData then
@@ -1305,7 +1316,12 @@ function ZONE_RADIUS:GetScannedSetGroup()
if UnitObject:isExist() then if UnitObject:isExist() then
local FoundUnit=UNIT:FindByName(UnitObject:getName()) local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
local includeoncoalition = true
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
if Coalition == nil then includeoncoalition = true end
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
if FoundUnit and includeoncoalition then
local group=FoundUnit:GetGroup() local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group) self.ScanSetGroup:AddGroup(group)
end end
@@ -3380,7 +3396,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) --self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
end end
end end
@@ -27,7 +27,7 @@
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
-- --
-- ### Contributions: FlightControl -- ### Contributions: FlightControl, Applevangelist
-- --
-- ==== -- ====
-- @module Functional.Artillery -- @module Functional.Artillery
@@ -780,7 +780,7 @@ ARTY.db={
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="1.3.3" ARTY.version="1.3.4"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -2268,7 +2268,7 @@ function ARTY._FuncTrack(weapon, self, target)
if _dist<target.radius then if _dist<target.radius then
-- Get random coordinate within a certain radius. -- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius) local _cr=_coord:GetRandomCoordinateInRadius(target.radius)
-- Fire smoke at this coordinate. -- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor) _cr:Smoke(self.smokeColor)
+12 -13
View File
@@ -693,7 +693,7 @@ end
function AUTOLASE:GetLosFromUnit(Unit) function AUTOLASE:GetLosFromUnit(Unit)
local lasedistance = self.LaseDistance local lasedistance = self.LaseDistance
local unitheight = Unit:GetHeight() local unitheight = Unit:GetHeight()
local coord = Unit:GetCoordinate() local coord = Unit:GetCoord()
local landheight = coord:GetLandHeight() local landheight = coord:GetLandHeight()
local asl = unitheight - landheight local asl = unitheight - landheight
if asl > 100 then if asl > 100 then
@@ -845,7 +845,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
locationstring = entry.coordinate:ToStringLLDDM(settings) locationstring = entry.coordinate:ToStringLLDDM(settings)
elseif settings:IsA2G_BR() then elseif settings:IsA2G_BR() then
-- attention this is the distance from the ASKING unit to target, not from RECCE to target! -- attention this is the distance from the ASKING unit to target, not from RECCE to target!
local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate() local startcoordinate = Unit:GetCoord() or Group:GetCoord()
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision) locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
end end
end end
@@ -963,8 +963,8 @@ function AUTOLASE:CanLase(Recce,Unit)
end end
end end
-- calculate LOS -- calculate LOS
local reccecoord = Recce:GetCoordinate() local reccecoord = Recce:GetCoord()
local unitcoord = Unit:GetCoordinate() local unitcoord = Unit:GetCoord()
local islos = reccecoord:IsLOS(unitcoord,2.5) local islos = reccecoord:IsLOS(unitcoord,2.5)
-- calculate distance -- calculate distance
local distance = math.floor(reccecoord:Get3DDistance(unitcoord)) local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
@@ -1020,8 +1020,8 @@ function AUTOLASE:_Prescient()
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
for _,_static in pairs(Statics) do -- DCS static object here for _,_static in pairs(Statics) do -- DCS static object here
local static = STATIC:Find(_static) local static = STATIC:Find(_static)
if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
local IsLOS = position:IsLOS(static:GetCoordinate()) local IsLOS = position:IsLOS(static:GetCoord())
if IsLOS then if IsLOS then
unit:KnowUnit(static,true,true) unit:KnowUnit(static,true,true)
end end
@@ -1081,7 +1081,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
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:GetCoord()
local distance = math.floor(reccecoord:Get3DDistance(coord)) local distance = math.floor(reccecoord:Get3DDistance(coord))
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel) local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
report:Add(text) report:Add(text)
@@ -1120,7 +1120,6 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local unit = _unit -- Wrapper.Unit#UNIT local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then if unit and unit:IsAlive() then
local threat = unit:GetThreatLevel() local threat = unit:GetThreatLevel()
local coord = unit:GetCoordinate()
if threat >= self.minthreatlevel then if threat >= self.minthreatlevel then
local unitname = unit:GetName() local unitname = unit:GetName()
-- prefer radar units -- prefer radar units
@@ -1176,16 +1175,16 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local code = self:GetLaserCode(reccename) local code = self:GetLaserCode(reccename)
local spot = SPOT:New(recce) local spot = SPOT:New(recce)
spot:LaseOn(unit,code,self.LaseDuration) spot:LaseOn(unit,code,self.LaseDuration)
local locationstring = unit:GetCoordinate():ToStringLLDDM() local locationstring = unit:GetCoord():ToStringLLDDM()
if _SETTINGS:IsA2G_MGRS() then if _SETTINGS:IsA2G_MGRS() then
local precision = _SETTINGS:GetMGRS_Accuracy() local precision = _SETTINGS:GetMGRS_Accuracy()
local settings = {} local settings = {}
settings.MGRS_Accuracy = precision settings.MGRS_Accuracy = precision
locationstring = unit:GetCoordinate():ToStringMGRS(settings) locationstring = unit:GetCoord():ToStringMGRS(settings)
elseif _SETTINGS:IsA2G_LL_DMS() then elseif _SETTINGS:IsA2G_LL_DMS() then
locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS) locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
elseif _SETTINGS:IsA2G_BR() then elseif _SETTINGS:IsA2G_BR() then
locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS) locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
end end
local laserspot = { -- #AUTOLASE.LaserSpot local laserspot = { -- #AUTOLASE.LaserSpot
@@ -1198,7 +1197,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
unitname = unitname, unitname = unitname,
reccename = reccename, reccename = reccename,
unittype = unit:GetTypeName(), unittype = unit:GetTypeName(),
coordinate = unit:GetCoordinate(), coordinate = unit:GetCoord(),
} }
if self.smoketargets then if self.smoketargets then
local coord = unit:GetCoordinate() local coord = unit:GetCoordinate()
+519 -85
View File
@@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: August 2025 -- Last Update: January 2026
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
@@ -61,10 +61,13 @@
-- @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 #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 SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White.
-- @field #number checkcounter Counter for SAM Table refreshes. -- @field #number checkcounter Counter for SAM Table refreshes.
-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. -- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay.
-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true -- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true.
-- @field #boolean DetectAccoustic Set if we can also detect units accousticly.
-- @field #number DetectAccousticRadius We can hear in this range.
-- @field #table DetectAccousticCategories We can hear these categories.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -113,7 +116,7 @@
-- * TOR M2 -- * TOR M2
-- * C-RAM -- * C-RAM
-- * Silkworm (though strictly speaking this is a surface to ship missile) -- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, SA-21, S-300VM, S-300V4, S-400
-- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!)
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2 -- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
-- * Other Mods: Nike -- * Other Mods: Nike
@@ -280,7 +283,7 @@
MANTIS = { MANTIS = {
ClassName = "MANTIS", ClassName = "MANTIS",
name = "mymantis", name = "mymantis",
version = "0.9.34", version = "0.9.44",
SAM_Templates_Prefix = "", SAM_Templates_Prefix = "",
SAM_Group = nil, SAM_Group = nil,
EWR_Templates_Prefix = "", EWR_Templates_Prefix = "",
@@ -331,6 +334,11 @@ MANTIS = {
checkcounter = 1, checkcounter = 1,
DLinkCacheTime = 120, DLinkCacheTime = 120,
logsamstatus = false, logsamstatus = false,
DetectAccoustic = false,
DetectAccousticRadius = 2000,
DetectAccousticCategories = {Unit.Category.HELICOPTER},
ARMWeaponSeen = {},
InboundARMs = {},
} }
--- Advanced state enumerator --- Advanced state enumerator
@@ -366,24 +374,25 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
-- @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 -- @field #string Point Point defense capable
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
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", ARMCapacity=1 }, -- AIM 120B
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" }, ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" }, ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
["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", ARMCapacity=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=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland", ARMCapacity=1 },
["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" }, ["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" },
["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="Point", Radar="Strela", Point="true" }, ["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="Point", Radar="Tor 9A331", Point="true" }, ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true", ARMCapacity=2 },
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["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" },
@@ -391,19 +400,23 @@ MANTIS.SamData = {
["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" },
-- 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", ARMCapacity=4},
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
["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", ARMCapacity=16},
["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", ARMCapacity=18},
["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"},
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
["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" },
["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" },
["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, ["Nike"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" },
["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" },
-- CH Added to DCS core 2.9.19.x -- CH Added to DCS core 2.9.19.x
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 },
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 },
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery
} }
--- SAM data HDS --- SAM data HDS
@@ -419,13 +432,13 @@ MANTIS.SamDataHDS = {
-- group name MUST contain HDS to ID launcher type correctly! -- group name MUST contain HDS to ID launcher type correctly!
["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" },
["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" },
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12},
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 },
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher ["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher
["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher ["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher
@@ -469,21 +482,21 @@ MANTIS.SamDataCH = {
-- 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 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" },
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" }, ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 },
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2", ARMCapacity=4 },
["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" },
["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=3 },
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" }, ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true", ARMCapacity=4 },
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" }, ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true", ARMCapacity=4 },
["NASAMS3-AMRAAMER CHM"] = { 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 CHM"] = { 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 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, ["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" },
["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, ["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100", ARMCapacity=20 },
["S350-9M96D CHM"] = { 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", ARMCapacity=20 },
["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" }, ["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" },
["HQ-22 CHM"] = { 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.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, ["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" },
@@ -495,8 +508,8 @@ MANTIS.SamDataCH = {
["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, ["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" },
["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, ["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" },
["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, ["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, 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-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M", ARMCapacity=20 },
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA", ARMCapacity=20 },
["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" }, ["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" }, ["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" }, ["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" },
@@ -524,7 +537,7 @@ do
--@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..." --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..."
--@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..." --@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..."
--@param #string hq Group name of your HQ (optional) --@param #string hq Group name of your HQ (optional)
--@param #string coalition Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #string Coalition Coalition side of your setup, e.g. "blue", "red" or "neutral"
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
--@param #string awacs Group name of your Awacs (optional) --@param #string awacs Group name of your Awacs (optional)
--@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional)
@@ -548,7 +561,7 @@ do
-- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mybluemantis:Start() -- mybluemantis:Start()
-- --
function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs, EmOnOff, Padding, Zones) function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs, EmOnOff, Padding, Zones)
-- Inherit everything from BASE class. -- Inherit everything from BASE class.
@@ -561,12 +574,14 @@ do
-- DONE: Treat Awacs separately, since they might be >80km off site -- DONE: Treat Awacs separately, since they might be >80km off site
-- DONE: Allow tables of prefixes for the setup -- DONE: Allow tables of prefixes for the setup
-- DONE: Auto-Mode with range setups for various known SAM types. -- DONE: Auto-Mode with range setups for various known SAM types.
-- DONE: Added reaction on HIT and UNIT LOST events.
self.name = name or "mymantis" self.name = name or "mymantis"
self.SAM_Templates_Prefix = samprefix or "Red SAM" self.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
self.HQ_Template_CC = hq or nil self.HQ_Template_CC = hq or nil
self.Coalition = coalition or "red" self.Coalition = Coalition or "red"
self.coalition = Coalition == "blue" and coalition.side.BLUE or coalition.side.RED
self.SAM_Table = {} self.SAM_Table = {}
self.SAM_Table_Long = {} self.SAM_Table_Long = {}
self.SAM_Table_Medium = {} self.SAM_Table_Medium = {}
@@ -613,6 +628,8 @@ do
self.autoshorad = true self.autoshorad = true
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
self.FilterZones = Zones self.FilterZones = Zones
self.LastThreatEval = {}
self.InboundARMs = {}
self.SkateZones = nil self.SkateZones = nil
self.SkateNumber = 3 self.SkateNumber = 3
@@ -716,6 +733,8 @@ do
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group. self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group. self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression. self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression.
self:AddTransition("*", "SAMUnitHit", "*") -- A SAM unit was hit
self:AddTransition("*", "SAMUnitLost", "*") -- A SAM Unit was lost
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
------------------------ ------------------------
@@ -826,6 +845,24 @@ do
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
-- @param #string Name Name of the suppressed group -- @param #string Name Name of the suppressed group
--- On After "SAMUnitHit" event. A SAM Unit was hit.
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP of the hit UNIT object
-- @param #string Name Name of the suppressed group
--- On After "SAMUnitLoast" event. A SAM Unit was lost.
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP of the lost UNIT object
-- @param #string Name Name of the suppressed group
return self return self
end end
@@ -833,6 +870,138 @@ do
-- MANTIS helper functions -- MANTIS helper functions
----------------------------------------------------------------------- -----------------------------------------------------------------------
--- Set to accept accoustic detection. Set this *before* MANTIS starts!
-- @param #MANTIS self
-- @param #number Radius Radius in which we can "hear" units. Defaults to 2000 meters.
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.HELICOPTER}`
-- @return #MANTIS self
function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories)
self.DetectAccoustic = true
self.DetectAccousticRadius = Radius or 2000
self.DetectAccousticCategories = UnitCategories or {Unit.Category.HELICOPTER}
return self
end
--- Switch off accoustic detection.
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:SetAccousticDetectionOff()
self.DetectAccoustic = false
return self
end
--- [Internal] Function to manage hits on SAM units
-- @param #MANTIS self
-- @param Core.Event#EVENTDATA EventData The EVENT data
-- @return #MANTIS self
function MANTIS:_EventHandler(EventData)
self:T(self.lid .. "_EventHandler")
local function IsOneOfOurs(name)
for _,_name in pairs(self.ewr_templates) do
if string.find(name,_name,1,true) then
return true
end
end
return false
end
local function SwitchSAMOn(Name,Group)
local suppressed = self.SuppressedGroups[Name] or false
if not suppressed and self.SamStateTracker[Name] == "GREEN" then
self.SamStateTracker[Name] = "RED"
if self.UseEmOnOff then
-- DONE: add emissions on/off
Group:EnableEmission(true)
elseif (not self.UseEmOnOff) then
Group:OptionAlarmStateRed()
end
self:__RedState(1,Group)
if self.SmokeDecoy == true then
self:_SmokeUnits(Group)
end
end
end
local coordinate -- Core.Point#COORDINATE
local Name -- #string
local Group -- Wrapper.Group#GROUP
local lasthit = 0
local firsthit = false
local alerton = false
-- Check if we can get a location
local data = EventData -- Core.Event#EVENTDATA
if data.id == EVENTS.Hit then
-- Unit hit, one of ours?
if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName) then
self:T("Unit hit in group: "..data.TgtGroupName)
if data.TgtGroup then
lasthit = data.TgtGroup:GetProperty("MANTIS_LASTHIT")
firsthit = (lasthit==nil) and true or false
if firsthit == true then alerton = true end
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
coordinate = data.TgtGroup:GetCoordinate()
Name = data.TgtGroupName
Group = data.TgtGroup
if alerton == true then
self:__SAMUnitHit(1,Group,Name)
SwitchSAMOn(Name,Group)
end
if coordinate and self.debug then
local text = coordinate:ToStringMGRS()
self:I("Location: "..text)
end
end
end
end
if data.id == EVENTS.UnitLost then
if data.IniGroupName and IsOneOfOurs(data.IniGroupName) then
self:T("Unit lost in group: "..data.IniGroupName)
if data.IniGroup then
lasthit = data.IniGroup:GetProperty("MANTIS_LASTHIT")
firsthit = (lasthit==nil) and true or false
if firsthit == true then alerton = true end
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
coordinate = data.IniGroup:GetCoordinate()
Name = data.IniGroupName
Group = data.IniGroup
alerton = true
SwitchSAMOn(Name,Group)
self:__SAMUnitLost(1,Group,Name)
if coordinate and self.debug then
local text = coordinate:ToStringMGRS()
self:I("Location: "..text)
end
end
end
end
if firsthit == true or alerton == true then
Group:SetProperty("MANTIS_LASTHIT",timer.getTime())
end
if coordinate ~= nil and Name ~= nil and Group ~=nil and alerton == true then
if self.ShoradLink then
self:T("Shorad activated for: "..Name)
local Shorad = self.Shorad -- Functional.Shorad#SHORAD
local radius = self.checkradius
local ontime = self.ShoradTime
Shorad:WakeUpShorad(Name, radius, ontime, nil, true)
self:__ShoradActivated(1,Name, radius, ontime)
end
if self.autorelocate and Group then
Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
end
end
return self
end
--- [Internal] Function to get the self.SAM_Table --- [Internal] Function to get the self.SAM_Table
-- @param #MANTIS self -- @param #MANTIS self
-- @return #table table -- @return #table table
@@ -903,13 +1072,111 @@ do
return self return self
end end
--- Add a single reject zone to MANTIS.
-- @param #MANTIS self
-- @param Core.Zone#ZONE Zone The zone to be added
-- @return #MANTIS self
function MANTIS:AddRejectZone(Zone)
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
table.insert(self.RejectZones,Zone)
self.usezones = true
end
return self
end
--- Add a single accept zone to MANTIS.
-- @param #MANTIS self
-- @param Core.Zone#ZONE Zone The zone to be added
-- @return #MANTIS self
function MANTIS:AddAcceptZone(Zone)
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
table.insert(self.AcceptZones,Zone)
self.usezones = true
end
return self
end
--- Add a single conflict zone to MANTIS.
-- @param #MANTIS self
-- @param Core.Zone#ZONE Zone The zone to be added
-- @return #MANTIS self
function MANTIS:AddConflictZone(Zone)
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
table.insert(self.ConflictZones,Zone)
self.usezones = true
end
return self
end
--- Function to set corridor zones.
-- @param #MANTIS self
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
-- @return #MANTIS self
function MANTIS:SetCorridorZones(CorridorZones)
self:T(self.lid .. "SetCorridorZones")
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
self.corridorzones = CorridorZones
self.usecorridors = true
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
self.corridorzones:AddZone(CorridorZones)
self.usecorridors = true
end
if self.intelset then
for _,_intel in pairs(self.intelset) do
_intel:SetCorridorZones(self.corridorzones)
end
end
return self
end
--- Function to add one corridor zone.
-- @param #MANTIS self
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
-- @return #MANTIS self
function MANTIS:AddCorridorZone(CorridorZone)
self:T(self.lid .. "AddCorridorZone")
self:SetCorridorZones(CorridorZone)
return self
end
--- Function to set corridor zone floor and ceiling in FEET.
-- @param #MANTIS self
-- @param #number Floor Floor altitude ASL in feet.
-- @param #number Ceiling Ceiling altitude ASL in feet.
-- @return #MANTIS self
function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
self.corridorfloor = UTILS.FeetToMeters(Floor)
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
if self.intelset then
for _,_intel in pairs(self.intelset) do
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
return self
end
--- Function to set corridor zone floor and ceiling in METERS.
-- @param #MANTIS self
-- @param #number Floor Floor altitude ASL in meters.
-- @param #number Ceiling Ceiling altitude ASL in meters.
-- @return #MANTIS self
function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
self.corridorfloor = Floor
self.corridorceiling = Ceiling
if self.intelset then
for _,_intel in pairs(self.intelset) do
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
return self
end
--- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used) --- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used)
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number radius Radius of the EWR detection zone -- @param #number radius Radius of the EWR detection zone
function MANTIS:SetEWRRange(radius) function MANTIS:SetEWRRange(radius)
self:T(self.lid .. "SetEWRRange") self:T(self.lid .. "SetEWRRange")
--local radius = radius or 80000
-- self.acceptrange = radius
return self return self
end end
@@ -1325,11 +1592,12 @@ do
return inzone return inzone
end end
--- [Internal] Function to prefilter height based --- [Internal] Function to prefilter height based and check for Helo activity.
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number height -- @param #number height
-- @param Core.Point#COORDINATE SamCoordinate
-- @return #table set -- @return #table set
function MANTIS:_PreFilterHeight(height) function MANTIS:_PreFilterHeight(height,SamCoordinate)
self:T(self.lid.."_PreFilterHeight") self:T(self.lid.."_PreFilterHeight")
local set = {} local set = {}
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
@@ -1338,8 +1606,22 @@ do
local contact = _contact -- Ops.Intel#INTEL.Contact local contact = _contact -- Ops.Intel#INTEL.Contact
local grp = contact.group -- Wrapper.Group#GROUP local grp = contact.group -- Wrapper.Group#GROUP
if grp:IsAlive() then if grp:IsAlive() then
if grp:GetHeight(true) < height then local coord = grp:GetCoordinate()
local coord = grp:GetCoordinate() local dist = 0
local include = true
if grp:IsGround() then include = false end
if grp:GetCoalition() == self.coalition then include = false end
if coord and SamCoordinate and grp:IsHelicopter() then
dist = coord:Get2DDistance(SamCoordinate) or 0
if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos
end
if self.debug then
local text = "Looking at Group: "..grp:GetName() or "N/A"
text = text .. " Include = "..tostring(include)
MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog()
end
local grpalt = grp:GetHeight(true)
if grpalt < height and grpalt > 10 and include == true then
table.insert(set,coord) table.insert(set,coord)
end end
end end
@@ -1363,7 +1645,7 @@ do
local set = dectset local set = dectset
if dlink then if dlink then
-- DEBUG -- DEBUG
set = self:_PreFilterHeight(height) set = self:_PreFilterHeight(height,samcoordinate)
end end
--self.friendlyset -- Core.Set#SET_GROUP --self.friendlyset -- Core.Set#SET_GROUP
if self.checkforfriendlies == true and self.friendlyset == nil then if self.checkforfriendlies == true and self.friendlyset == nil then
@@ -1444,14 +1726,30 @@ do
self.intelset = {} self.intelset = {}
local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne") local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne")
--IntelOne:SetClusterAnalysis(true,true) IntelOne.DetectAccoustic = self.DetectAccoustic
--IntelOne:SetClusterRadius(5000) IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000
IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
--IntelOne:SetClusterAnalysis(true,true,true)
if self.usecorridors == true then
IntelOne:SetCorridorZones(self.corridorzones)
if self.corridorfloor or self.corridorceiling then
IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
IntelOne:Start() IntelOne:Start()
local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo") local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo")
--IntelTwo:SetClusterAnalysis(true,true) IntelTwo.DetectAccoustic = self.DetectAccoustic
--IntelTwo:SetClusterRadius(5000) IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000
IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
--IntelTwo:SetClusterAnalysis(true,true,true)
if self.usecorridors == true then
IntelTwo:SetCorridorZones(self.corridorzones)
if self.corridorfloor or self.corridorceiling then
IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
IntelTwo:Start() IntelTwo:Start()
local CacheTime = self.DLinkCacheTime or 120 local CacheTime = self.DLinkCacheTime or 120
@@ -1511,6 +1809,7 @@ do
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
local units = group:GetUnits() local units = group:GetUnits()
local SAMData = self.SamData local SAMData = self.SamData
local ARMCapacity
if mod then if mod then
SAMData = self.SamDataHDS SAMData = self.SamDataHDS
elseif sma then elseif sma then
@@ -1518,22 +1817,23 @@ do
elseif chm then elseif chm then
SAMData = self.SamDataCH SAMData = self.SamDataCH
end end
--self:I("Looking to auto-match for "..grpname) self:T("Looking to auto-match for "..grpname)
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT local unit = _unit -- Wrapper.Unit#UNIT
local type = string.lower(unit:GetTypeName()) local typename = string.lower(unit:GetTypeName())
--self:I(string.format("Matching typename: %s",type)) self:T(string.format("Matching typename: %s",typename))
for idx,entry in pairs(SAMData) do for idx,entry in pairs(SAMData) do
local _entry = entry -- #MANTIS.SamData local _entry = entry -- #MANTIS.SamData
local _radar = string.lower(_entry.Radar) local _radar = string.lower(_entry.Radar)
--self:I(string.format("Trying typename: %s",_radar)) self:T(string.format("Trying typename: %s",_radar))
if string.find(type,_radar,1,true) then if string.find(typename,_radar,1,true) then
type = _entry.Type type = _entry.Type
radiusscale = self.radiusscale[type] radiusscale = self.radiusscale[type]
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
height = _entry.Height * 1000 -- max firing height height = _entry.Height * 1000 -- max firing height
blind = _entry.Blindspot * 100 -- blind spot range blind = _entry.Blindspot * 100 -- blind spot range
--self:I(string.format("Match: %s - %s",_radar,type)) ARMCapacity = _entry.ARMCapacity
self:T(string.format("Match: %s - %s",_radar,type))
found = true found = true
break break
end end
@@ -1554,7 +1854,7 @@ do
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
return range, height, type, blind return range, height, type, blind, ARMCapacity
end end
--- [Internal] Function to get SAM firing data --- [Internal] Function to get SAM firing data
@@ -1575,6 +1875,7 @@ do
local HDSmod = false local HDSmod = false
local SMAMod = false local SMAMod = false
local CHMod = false local CHMod = false
local ARMCapacity = 0
if string.find(grpname,"HDS",1,true) then if string.find(grpname,"HDS",1,true) then
HDSmod = true HDSmod = true
elseif string.find(grpname,"SMA",1,true) then elseif string.find(grpname,"SMA",1,true) then
@@ -1592,6 +1893,7 @@ 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
ARMCapacity = _entry.ARMCapacity or 0
self:T("Matching Groupname = " .. grpname .. " Range= " .. range) self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
found = true found = true
break break
@@ -1611,14 +1913,14 @@ do
end end
--- Tertiary filter if not found --- Tertiary filter if not found
if (not found) 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, blind, ARMCapacity = 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 if found and string.find(grpname,"SHORAD",1,true) then
type = MANTIS.SamType.POINT -- force short on match type = MANTIS.SamType.POINT -- force short on match
end end
return range, height, type, blind return range, height, type, blind, ARMCapacity
end end
--- [Internal] Function to set the SAM start state --- [Internal] Function to set the SAM start state
@@ -1653,8 +1955,9 @@ do
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
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,ARMCapacity = self:_GetSAMRange(grpname)
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity})
--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, type}) table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
@@ -1718,12 +2021,15 @@ do
if group:IsGround() and group:IsAlive() then if group:IsGround() and group:IsAlive() then
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, ARMCapacity = self:_GetSAMRange(grpname)
-- TODO the below might stop working at some point after some hours, needs testing -- TODO the below might stop working at some point after some hours, needs testing
--local radaralive = group:IsSAM() --local radaralive = group:IsSAM()
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
local radaralive = true local radaralive = true
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( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here
table.insert( SEAD_Grps, grpname ) if type ~= MANTIS.SamType.POINT then
table.insert( SEAD_Grps, grpname )
end
if type == MANTIS.SamType.LONG and radaralive then if type == MANTIS.SamType.LONG and radaralive then
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
self:T({grpname,grprange, grpheight}) self:T({grpname,grprange, grpheight})
@@ -1780,10 +2086,122 @@ do
self.ShoradLink = false self.ShoradLink = false
return self return self
end end
--- [Internal] Function to smoke a group in decoy.
-- @param #MANTIS self
-- @param Wrapper.Group#GROUP Group
-- @return #MANTIS self
function MANTIS:_SmokeUnits(Group)
self:T("Smoking")
local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME") or 0
local TNow = timer.getTime()
if TNow - LastSmoketime > 290 then -- Smoking lasts 5 minutes
Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow)
local units = Group: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
return self
end
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- MANTIS main functions -- MANTIS main functions
----------------------------------------------------------------------- -----------------------------------------------------------------------
--- [Internal] Check if a system can and should defend for HARMs itself
-- @param #MANTIS self
-- @param Wrapper.Group#GROUP targetGroup
-- @param #string targetName
-- @param Wrapper.Group#GROUP attackerGroup
-- @param #string weaponName
-- @param Wrapper.Weapon#WEAPON weaponWrapper
-- @param #number tti
-- @param #number delay
-- @return #boolean Outcome
function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay)
self:T(self.lid.."SeadAllowSuppression")
--- Thanks to @Goon Jan 2026
----------------------------------------------------------------
-- LOG INCOMING REQUEST
----------------------------------------------------------------
self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
tostring(weaponName),tostring(tti),tostring(delay)))
----------------------------------------------------------------
-- LOOK UP ARM CAPACITY FOR THIS SAM
----------------------------------------------------------------
local armcap = targetGroup:GetProperty("ARMCapacity")
if not armcap then
for _, sam in pairs(self.SAM_Table or {}) do
if sam[1] == targetName then
armcap = sam[7] -- ARMCapacity
break
end
end
end
self:T(string.format("MANTIS:SeadAllowSuppression SAM DATA | target=%s | ARMCapacity=%s",tostring(targetName),armcap and tostring(armcap) or "nil"))
----------------------------------------------------------------
-- TRACK SEAD THREATS (PER TARGET)
----------------------------------------------------------------
local THREAT_WINDOW = 0.1 -- seconds
self.LastThreatEval = self.LastThreatEval or {}
self.InboundARMs = self.InboundARMs or {}
local now = timer.getTime()
local last = self.LastThreatEval[targetName] or 0
if (now - last) >= THREAT_WINDOW then
self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1
self.LastThreatEval[targetName] = now
self:T(string.format("MANTIS:SeadAllowSuppression NEW threat accepted | Δt=%.3f",now - last))
else
self:T(string.format("MANTIS:SeadAllowSuppression duplicate evaluation ignored | Δt=%.3f",now - last))
end
local inbound = self.InboundARMs[targetName] or 0
self:T(string.format("MANTIS:SeadAllowSuppression THREAT COUNT | target=%s | inboundThreats=%d",tostring(targetName),inbound))
----------------------------------------------------------------
-- DECISION GATE
----------------------------------------------------------------
-- No missiles left over → legacy behavior
if targetGroup and targetGroup:IsAlive() then
local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition()
-- TODO Check C-RAM probably needs an exception as it is a gun, need to check its effectiveness
if AmmoM and AmmoM == 0 then
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no MISSILES) | target=%s",tostring(targetName)))
return true
end
end
-- No ARM capacity defined → legacy behavior
if (not armcap) or armcap == 0 then
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no ARMCAP) | target=%s",tostring(targetName)))
return true
end
-- Suppress only once enough threats accumulated
if inbound >= armcap then
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (inbound %d >= cap %d) | target=%s",inbound,armcap,tostring(targetName)))
return true
end
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (inbound %d < cap %d) | target=%s",inbound,armcap,tostring(targetName)))
return false
end
--- [Internal] Check detection function --- [Internal] Check detection function
-- @param #MANTIS self -- @param #MANTIS self
@@ -1807,7 +2225,7 @@ do
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 local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false
if not shortsam then if not shortsam then
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
end end
@@ -1818,6 +2236,7 @@ do
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
activeshorad = true activeshorad = true
end end
if samgroup:GetProperty("SHORAD_ACTIVE") == true and activeshorad == false 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
@@ -1833,19 +2252,13 @@ do
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.SamStateTracker[name] = "RED" self.SamStateTracker[name] = "RED"
self:__RedState(1,samgroup)
end end
-- TODO doesn't work -- DONE Restrict on Distance
if shortsam == true and self.SmokeDecoy == true then if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then
self:T("Smoking") self:T("Smoking")
local units = samgroup:GetUnits() or {} self:_SmokeUnits(samgroup)
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 end
-- link in to SHORAD if available -- link in to SHORAD if available
-- DONE: Test integration fully -- DONE: Test integration fully
@@ -1872,8 +2285,8 @@ do
samgroup:OptionAlarmStateGreen() samgroup:OptionAlarmStateGreen()
end end
if self.SamStateTracker[name] ~= "GREEN" then if self.SamStateTracker[name] ~= "GREEN" then
self:__GreenState(1,samgroup)
self.SamStateTracker[name] = "GREEN" self.SamStateTracker[name] = "GREEN"
self:__GreenState(1,samgroup)
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)
@@ -1883,12 +2296,13 @@ do
end --end alive end --end alive
end --end check end --end check
end --for loop end --for loop
--[[
if self.debug or self.verbose or self.logsamstatus then if self.debug or self.verbose or self.logsamstatus then
for _,_status in pairs(self.SamStateTracker) do for _,_status in pairs(self.SamStateTracker) do
if _status == "GREEN" then if _status == "GREEN" then
instatusgreen=instatusgreen+1 instatusgreen=instatusgreen+1
elseif _status == "RED" then elseif _status == "RED" then
instatusred=instatusred+1 instatusred=instatusred+1
end end
end end
if self.Shorad then if self.Shorad then
@@ -1897,6 +2311,8 @@ do
end end
end end
end end
self:T(self.lid..string.format("SAM State Count: GREEN %d | RED %d | SHORAD %d",instatusred, instatusgreen, activeshorads))
--]]
return instatusred, instatusgreen, activeshorads return instatusred, instatusgreen, activeshorads
end end
@@ -1928,13 +2344,29 @@ do
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
local instatusreds, instatusgreens, activeshoradss = 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 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) local 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
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
end end
local function GetReport() local function GetReport()
if self.debug or self.verbose or self.logsamstatus 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
local statusreport = REPORT:New("\nMANTIS Status "..self.name) local statusreport = REPORT:New("\nMANTIS Status "..self.name)
statusreport:Add("+-----------------------------+") statusreport:Add("+-----------------------------+")
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
@@ -2030,21 +2462,22 @@ do
else else
self.Detection = self:StartIntelDetection() self.Detection = self:StartIntelDetection()
end end
--[[
if self.advAwacs and not self.automode then
self.AWACS_Detection = self:StartAwacsDetection()
end
--]]
if self.autoshorad then if self.autoshorad then
self.Shorad = SHORAD:New(self.name.."-SHORAD","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.SmokeDecoy,self.SmokeDecoyColor)
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
self.Shorad.debug = self.debug self.Shorad.debug = self.debug
self.Shorad:AddCallBack(self)
end end
if self.shootandscoot and self.SkateZones and self.Shorad then if self.shootandscoot and self.SkateZones and self.Shorad then
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
end end
self:HandleEvent(EVENTS.Hit,self._EventHandler)
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
self:__Status(-math.random(1,10)) self:__Status(-math.random(1,10))
return self return self
end end
@@ -2242,6 +2675,7 @@ do
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name) function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
self:T({From, Event, To, Name}) self:T({From, Event, To, Name})
self.SuppressedGroups[Name] = false self.SuppressedGroups[Name] = false
self.InboundARMs[Name] = 0
return self return self
end end
@@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
--- PSEUDOATC version. --- PSEUDOATC version.
-- @field #number version -- @field #number version
PSEUDOATC.version="0.10.5" PSEUDOATC.version="0.10.6"
----------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------
@@ -579,6 +579,9 @@ function PSEUDOATC:MenuAirports(GID,UID)
-- Create menu reporting commands -- Create menu reporting commands
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name) missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name) missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
if self.radios then
missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name)
end
-- Debug message. -- Debug message.
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID)) self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
@@ -705,7 +708,30 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
end end
--- Report absolute bearing and range form player unit to airport. --- Report airport radio information.
-- @param #PSEUDOATC self
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
-- @param Core.Point#COORDINATE position Coordinate of the airport.
-- @param #string location Name of the airport.
function PSEUDOATC:ReportRadios(GID, UID, position, location)
self:F({GID=GID, UID=UID, position=position, location=location})
if self.radios then
local Text=""
local radio = self.radios:GetClosestRadio(position,9) --Navigation.Radios#RADIOS.Radio
if radio then
Text=self.radios:_GetMarkerText(radio)
else
Text=self.group[GID].player[UID].playername..", no radio information found!"
end
-- Send message
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, Text, self.mdur, true)
end
return self
end
--- Report absolute bearing and range from player unit to airport.
-- @param #PSEUDOATC self -- @param #PSEUDOATC self
-- @param #number GID Group id of player unit. -- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player. -- @param #number UID Unit id of player.
@@ -979,3 +1005,22 @@ function PSEUDOATC:_myname(unitname)
return string.format("%s (%s)", csign, pname) return string.format("%s (%s)", csign, pname)
end end
--- Returns a string which consits of this callsign and the player name.
-- @param #PSEUDOATC self
-- @param #string path Path to map data, e.g. `[[<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua]]` (replace with correct path).
-- Needs `lfs` and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>\Scripts`
-- @return #PSEUDOATC self
-- @usage
--
-- mypseudoatc:SetUsingRadioInformationFromMap([[C:\Program Files\Eagle Dynamics\DCS World.Openbeta\Mods\terrains\Caucasus\Radio.lua]])
--
function PSEUDOATC:SetUsingRadioInformationFromMap(path)
if RADIOS and lfs and io then
self.radios = RADIOS:NewFromFile(path)
else
self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>/Scripts`")
end
return self
end
+16 -14
View File
@@ -5584,7 +5584,7 @@ end
-- @param #string dest Name of the destination airport. -- @param #string dest Name of the destination airport.
function RAT:_ATCAddFlight(name, dest) function RAT:_ATCAddFlight(name, dest)
-- Debug info -- Debug info
BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest)) BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest))
-- Create new flight -- Create new flight
local flight={} --#RAT.AtcFlight local flight={} --#RAT.AtcFlight
@@ -5603,7 +5603,7 @@ end
function RAT._ATCDelFlight(t,entry) function RAT._ATCDelFlight(t,entry)
for k,_ in pairs(t) do for k,_ in pairs(t) do
if k==entry then if k==entry then
BASE:I(RAT.id..string.format("Removing flight %s from queue", entry)) BASE:T(RAT.id..string.format("Removing flight %s from queue", entry))
t[entry]=nil t[entry]=nil
end end
end end
@@ -5614,7 +5614,7 @@ end
-- @param #string name Group name of the flight. -- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered. -- @param #number time Time the fight first registered.
function RAT._ATCRegisterFlight(name, time) function RAT._ATCRegisterFlight(name, time)
BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name)) BASE:T(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name))
RAT.ATC.flight[name].Tarrive=time RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0 RAT.ATC.flight[name].holding=0
end end
@@ -5649,15 +5649,16 @@ function RAT._ATCStatus()
-- Aircraft is holding. -- Aircraft is holding.
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
BASE:I(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then elseif hold==RAT.ATC.onfinal then
-- Aircarft is on final approach for landing. -- Aircarft is on final approach for landing.
local Tfinal=Tnow-flight.Tonfinal local Tonfinal = flight.Tonfinal or timer.getTime()-1
local Tfinal=Tnow-Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
BASE:I(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then elseif hold==RAT.ATC.unregistered then
@@ -5711,13 +5712,13 @@ function RAT._ATCCheck()
-- Debug message. -- Debug message.
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",
airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60) airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60)
BASE:I(RAT.id..text) BASE:T(RAT.id..text)
else else
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",
airportname, flightname, flight.holding/60, flight.holding%60) airportname, flightname, flight.holding/60, flight.holding%60)
BASE:I(RAT.id..text) BASE:T(RAT.id..text)
-- Clear flight for landing. -- Clear flight for landing.
RAT._ATCClearForLanding(airportname, flightname) RAT._ATCClearForLanding(airportname, flightname)
@@ -5772,7 +5773,7 @@ function RAT._ATCClearForLanding(airportname, flightname)
-- Debug message. -- Debug message.
BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname)) BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname))
if string.find(flightname,"#") then if string.find(flightname,"#") then
flightname = string.match(flightname,"^(.+)#") flightname = string.match(flightname,"^(.+)#")
@@ -5799,8 +5800,9 @@ function RAT._ATCFlightLanded(name)
-- Times for holding and final approach. -- Times for holding and final approach.
local Tnow=timer.getTime() local Tnow=timer.getTime()
local Tfinal=Tnow-flight.Tonfinal local Tonfinal = flight.Tonfinal or timer.getTime()-1
local Thold=flight.Tonfinal-flight.Tarrive local Tfinal=Tnow-Tonfinal
local Thold=Tonfinal-flight.Tarrive
local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport
@@ -5823,9 +5825,9 @@ function RAT._ATCFlightLanded(name)
local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600
-- Debug info -- Debug info
BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60)) BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60))
BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal)) BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour)) BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
if string.find(name,"#") then if string.find(name,"#") then
name = string.match(name,"^(.+)#") name = string.match(name,"^(.+)#")
+20 -6
View File
@@ -109,7 +109,6 @@
-- @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 #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. -- @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
@@ -1271,6 +1270,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC") self.controlmsrs:SetLabel("RANGEC")
self.controlmsrs:SetVolume(Volume or 1.0) self.controlmsrs:SetVolume(Volume or 1.0)
if self.rangezone then
self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate())
end
self.controlsrsQ = MSRSQUEUE:New("CONTROL") self.controlsrsQ = MSRSQUEUE:New("CONTROL")
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM) self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM)
@@ -1278,6 +1280,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.instructmsrs:SetLabel("RANGEI") self.instructmsrs:SetLabel("RANGEI")
self.instructmsrs:SetVolume(Volume or 1.0) self.instructmsrs:SetVolume(Volume or 1.0)
if self.rangezone then
self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate())
end
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then if PathToGoogleKey then
@@ -1316,8 +1321,13 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
self.rangecontrol = true self.rangecontrol = true
if relayunitname then if relayunitname then
local unit = UNIT:FindByName(relayunitname) local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate() if unit then
self.rangecontrolrelayname = relayunitname local Coordinate = unit:GetCoordinate()
self.rangecontrolrelayname = relayunitname
self.controlmsrs:SetCoordinate(Coordinate)
else
MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
end
end end
return self return self
end end
@@ -1345,9 +1355,13 @@ function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gen
self.instructor = true self.instructor = true
if relayunitname then if relayunitname then
local unit = UNIT:FindByName(relayunitname) local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate() if unit then
self.instructmsrs:SetCoordinate(Coordinate) local Coordinate = unit:GetCoordinate()
self.instructorrelayname = relayunitname self.instructmsrs:SetCoordinate(Coordinate)
self.instructorrelayname = relayunitname
else
MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
end
end end
return self return self
end end
+165 -45
View File
@@ -230,8 +230,10 @@ SCORING = {
ClassID = 0, ClassID = 0,
Players = {}, Players = {},
AutoSave = true, AutoSave = true,
version = "1.18.4", version = "1.18.5",
ScoringScenery = nil, -- Core.Set#SET_SCENERY ScoringScenery = nil, -- Core.Set#SET_SCENERY
SceneryHitsInZone = false,
LoadSave = false,
} }
local _SCORINGCoalition = { local _SCORINGCoalition = {
@@ -250,15 +252,16 @@ local _SCORINGCategory = {
--- Creates a new SCORING object to administer the scoring achieved by players. --- Creates a new SCORING object to administer the scoring achieved by players.
-- @param #SCORING self -- @param #SCORING self
-- @param #string GameName The name of the game. This name is also logged in the CSV score file. -- @param #string GameName The name of the game. This name is also logged in the CSV score file.
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder. -- @param #string SavePath (Optional) Path where to save the CSV files, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder. See next two options.
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. -- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. This stores a detailed table which will never be loaded again by SCORING (for e.g. Discord purposes).
-- @param #boolean LoadSave (Optional) If passed as `true` save summary scores per player, and load at restart of the mission.
-- @return #SCORING self -- @return #SCORING self
-- @usage -- @usage
-- --
-- -- Define a new scoring object for the mission Gori Valley. -- -- Define a new scoring object for the mission Gori Valley.
-- ScoringObject = SCORING:New( "Gori Valley" ) -- ScoringObject = SCORING:New( "Gori Valley" )
-- --
function SCORING:New( GameName, SavePath, AutoSave ) function SCORING:New( GameName, SavePath, AutoSave, LoadSave )
-- Inherits from BASE -- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
@@ -266,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
if GameName then if GameName then
self.GameName = GameName self.GameName = GameName
else else
error( "A game name must be given to register the scoring results" ) error( "A game name must be given to register the scoring results!" )
end end
-- Additional Object scores -- Additional Object scores
@@ -301,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave )
self.penaltyoncoalitionchange = true self.penaltyoncoalitionchange = true
self:SetDisplayMessagePrefix() self:SetDisplayMessagePrefix()
self.SceneryHitsInZone = false
if LoadSave then
self.LoadSave = LoadSave
end
-- Event handlers -- Event handlers
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
@@ -328,11 +337,61 @@ function SCORING:New( GameName, SavePath, AutoSave )
end end
self:I("SCORING "..tostring(GameName).." started! v"..self.version) self:I("SCORING "..tostring(GameName).." started! v"..self.version)
if LoadSave == true then
self:_LoadPlayerSummaryScore()
end
return self return self
end end
--- [Internal] Helper to load scores from disk at scoring start
-- @param #SCORING self
-- @return #SCORING self
function SCORING:_LoadPlayerSummaryScore()
if lfs and io and self.LoadSave == true then
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
local filename = self.GameName or "PlayerScoresSummary"
filename = filename..".csv"
if UTILS.CheckFileExists(path,filename) then
local ok, data = UTILS.LoadFromFile(path,filename)
-- Playername;;Score;;Penalty
table.remove(data,1)
for _,_data in pairs(data) do
local line = UTILS.Split(_data,";;")
local playername = tostring(line[1])
local score = tonumber(line[2])
local penalty = tonumber(line[3])
self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty))
local PlayerData = self.Players[playername]
if not PlayerData then
PlayerData = {}
PlayerData.Hit = {}
PlayerData.Destroy = {}
PlayerData.Goals = {}
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
PlayerData.Mission = {}
PlayerData.HitPlayers = {}
PlayerData.Score = score
PlayerData.Penalty = penalty
PlayerData.PenaltyCoalition = 0
PlayerData.PenaltyWarning = 0
self.Players[playername] = PlayerData
else
PlayerData.Score = score
PlayerData.Penalty =penalty
self.Players[playername] = PlayerData
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
end
end
end
end
return self
end
--- Set a prefix string that will be displayed at each scoring message sent. --- Set a prefix string that will be displayed at each scoring message sent.
-- @param #SCORING self -- @param #SCORING self
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string. -- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
@@ -579,6 +638,22 @@ function SCORING:AddZoneScore( ScoreZone, Score )
return self return self
end end
--- Allow Scenery hits in Zones to count (no specific(!) scenery targets). NOTE - Allowing this can spam your scoring display!
-- @param #SCORING self
-- @return #SCORING self
function SCORING:EnableSceneryHitsinZones()
self.SceneryHitsInZone = true
return self
end
--- Disallow Scenery hits in Zones to count (no specific(!) scenery targets).
-- @param #SCORING self
-- @return #SCORING self
function SCORING:DisableSceneryHitsinZones()
self.SceneryHitsInZone = false
return self
end
--- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone. --- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone.
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
-- This allows for a dynamic destruction zone evolution within your mission. -- This allows for a dynamic destruction zone evolution within your mission.
@@ -877,6 +952,20 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason. -- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then if PlayerName then
local PlayerData = self.Players[PlayerName] local PlayerData = self.Players[PlayerName]
if not PlayerData then
PlayerData = {}
PlayerData.Goals = {}
PlayerData.Hit = {}
PlayerData.Destroy = {}
PlayerData.Goals = {}
PlayerData.Mission = {}
PlayerData.HitPlayers = {}
PlayerData.Score = 0
PlayerData.Penalty = 0
PlayerData.PenaltyCoalition = 0
PlayerData.PenaltyWarning = 0
self.Players[PlayerName] = PlayerData
end
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 } PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
@@ -1153,7 +1242,7 @@ function SCORING:_EventOnHit( Event )
TargetType = "Scenery" TargetType = "Scenery"
TargetSceneryObject = TargetUNIT TargetSceneryObject = TargetUNIT
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
UTILS.PrintTableToLog(TargetSceneryObject) --UTILS.PrintTableToLog(TargetSceneryObject)
end end
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
@@ -1374,17 +1463,18 @@ function SCORING:_EventOnHit( Event )
-- Player contains the score data from self.Players[WeaponPlayerName] -- Player contains the score data from self.Players[WeaponPlayerName]
local PlayerName = Event.IniPlayerName or "Ghost" local PlayerName = Event.IniPlayerName or "Ghost"
local Player = self.Players[PlayerName] local Player = self.Players[PlayerName]
if Player then
Player.Score = Player.Score + Score Player.Score = Player.Score + Score
Player.Score = Player.Score + self.ScoreIncrementOnHit Player.Score = Player.Score + self.ScoreIncrementOnHit
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." .. MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType ) self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
end end
end
end end
end end
@@ -1567,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event )
end end
else else
-- Check if there are Zones where the destruction happened. if self.SceneryHitsInZone == true then
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do -- Check if there are Zones where the destruction happened.
self:F( { ScoringZone = ScoreZoneData } ) for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE self:F( { ScoringZone = ScoreZoneData } )
local Score = ScoreZoneData.Score local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then local Score = ScoreZoneData.Score
Player.Score = Player.Score + Score if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
TargetDestroy.Score = TargetDestroy.Score + Score Player.Score = Player.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. TargetDestroy.Score = TargetDestroy.Score + Score
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
MESSAGE.Type.Information ) "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) MESSAGE.Type.Information )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
Destroyed = true self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
Destroyed = true
end
end end
end end
end end
@@ -1926,9 +2018,12 @@ end
--- Report all players score --- Report all players score
-- @param #SCORING self -- @param #SCORING self
-- @param Wrapper.Group#GROUP PlayerGroup The player group. -- @param Wrapper.Group#GROUP PlayerGroup The player group.
function SCORING:ReportScoreAllSummary( PlayerGroup ) -- @param #boolean JustScore If this is true, return just a table with playernames and overall scores.
-- @return #table ReportTable Table returned if JustScore is true.
function SCORING:ReportScoreAllSummary( PlayerGroup, JustScore )
local PlayerMessage = "" local PlayerMessage = ""
local ReportTable = {}
self:T( { "Summary Score Report of All Players", Players = self.Players } ) self:T( { "Summary Score Report of All Players", Players = self.Players } )
@@ -1960,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage = if JustScore~=true then
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerMessage =
PlayerName, string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerScore - PlayerPenalty, PlayerName,
PlayerScore, PlayerScore - PlayerPenalty,
PlayerPenalty PlayerScore,
) PlayerPenalty
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup ) )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
else
ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
end
end end
end end
return ReportTable
end end
--- Opens a score CSV file to log the scores.
-- @param #SCORING self
-- @param #number sSeconds
-- @return #string ClockString
function SCORING:SecondsToClock( sSeconds ) function SCORING:SecondsToClock( sSeconds )
local nSeconds = sSeconds local nSeconds = sSeconds
if nSeconds == 0 then if nSeconds == 0 then
@@ -2101,6 +2204,23 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
self.CSVFile:write( "\n" ) self.CSVFile:write( "\n" )
end end
if lfs and io and self.LoadSave == true then
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
local filename = self.GameName or "PlayerScoresSummary"
filename = filename..".csv"
local data = self:ReportScoreAllSummary("",true)
local text = "-- Playername;;Score;;Penalty\n"
for _playername,_data in pairs(data or {}) do
-- ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
local Playername = _playername or "Ghost"
local Score = _data.Score or 0
local Penalty = _data.Penalty or 0
text = text..string.format("%s;;%d;;%d\n",Playername,Score,Penalty)
end
UTILS.SaveToFile(path,filename,text)
end
end end
--- Close CSV file --- Close CSV file
+16 -5
View File
@@ -19,7 +19,7 @@
-- --
-- ### Authors: **applevangelist**, **FlightControl** -- ### Authors: **applevangelist**, **FlightControl**
-- --
-- Last Update: Dec 2024 -- Last Update: January 2026
-- --
-- === -- ===
-- --
@@ -157,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.9") self:I("*** SEAD - Started Version 0.4.11")
return self return self
end end
@@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
elseif height <= 12500 then elseif height <= 12500 then
Ropt = Ropt * 0.98 Ropt = Ropt * 0.98
end end
local WeaponWrapper = WEAPON:New(SEADWeapon)
-- look at a couple of zones across the trajectory -- look at a couple of zones across the trajectory
for n=1,3 do for n=1,3 do
local dist = Ropt - ((n-1)*20000) local dist = Ropt - ((n-1)*20000)
@@ -342,7 +342,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
_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:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20) self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20, WeaponWrapper)
end end
--end --end
end end
@@ -442,7 +442,18 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
local SuppressionStartTime = timer.getTime() + delay local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
local _targetgroupname = _targetgroup:GetName() local _targetgroupname = _targetgroup:GetName()
if not self.SuppressedGroups[_targetgroupname] then local shoradactive = _targetgroup:GetProperty("SHORAD_ACTIVE")
if not self.SuppressedGroups[_targetgroupname] and shoradactive ~= true then
-- TODO: ask callback if suppression is allowed BEFORE scheduling timers
local allow = true
if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then
allow = self.CallBack:SeadAllowSuppression(_targetgroup,_targetgroupname,SEADGroup,SEADWeaponName,Weapon,_tti,delay)
end
if not allow then
self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname))
-- Important: do NOT schedule SuppressionStart/Stop and do NOT flip SuppressedGroups true.
return self
end
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime) timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
+132 -59
View File
@@ -21,7 +21,7 @@
-- @image Functional.Shorad.jpg -- @image Functional.Shorad.jpg
-- --
-- Date: Nov 2021 -- Date: Nov 2021
-- Last Update: Jan 2025 -- Last Update: Jan 2026
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE --- **SHORAD** class, extends Core.Base#BASE
@@ -48,7 +48,9 @@
-- @field #number minscootdist Min distance of the next zone -- @field #number minscootdist Min distance of the next zone
-- @field #number maxscootdist Max distance of the next zone -- @field #number maxscootdist Max distance of the next zone
-- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center -- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone" -- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
-- @field #boolean SmokeDecoy = false,
-- @field #number SmokeDecoyColor = SMOKECOLOR.White
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -114,7 +116,9 @@ SHORAD = {
SkateZones = nil, SkateZones = nil,
minscootdist = 100, minscootdist = 100,
maxscootdist = 3000, maxscootdist = 3000,
scootrandomcoord = false, scootrandomcoord = false,
SmokeDecoy = false,
SmokeDecoyColor = SMOKECOLOR.White
} }
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -161,8 +165,10 @@ do
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch) -- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
-- @param #boolean SmokeDecoy Throw smoke decoy when getting activated. Defaults to false.
-- @param #number SmokeDecoyColor SMOLECOLOR to use. Defaults to SMOLECOLOR.White
-- @return #SHORAD self -- @return #SHORAD self
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff, SmokeDecoy, SmokeDecoyColor)
local self = BASE:Inherit( self, FSM:New() ) local self = BASE:Inherit( self, FSM:New() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
@@ -171,6 +177,7 @@ do
self.name = Name or "MyShorad" self.name = Name or "MyShorad"
self.Prefixes = ShoradPrefix or "SAM SHORAD" self.Prefixes = ShoradPrefix or "SAM SHORAD"
self.Radius = Radius or 20000 self.Radius = Radius or 20000
if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end
self.Coalition = Coalition or "blue" self.Coalition = Coalition or "blue"
self.Samset = Samset or GroupSet self.Samset = Samset or GroupSet
self.ActiveTimer = ActiveTimer or 600 self.ActiveTimer = ActiveTimer or 600
@@ -181,8 +188,15 @@ do
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end
self:I("*** SHORAD - Started Version 0.3.4")
if SmokeDecoy then
self.SmokeDecoy = SmokeDecoy
self.SmokeDecoyColor = SmokeDecoyColor or SMOKECOLOR.White
end
self:I("*** SHORAD - Started Version 0.3.6")
-- Set the string id for output to DCS.log file. -- Set the string id for output to DCS.log file.
self.lid=string.format("SHORAD %s | ", self.name) self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState() self:_InitState()
@@ -451,6 +465,43 @@ do
return returnname return returnname
end end
--- Set an object to call back when going evasive.
-- @param #SHORAD self
-- @param #table Object The object to call.
-- @return #SHORAD self
function SHORAD:AddCallBack(Object)
self:T({Class=Object.ClassName})
self.CallBack = Object
self.UseCallBack = true
return self
end
--- Smoke a SHORAD Group
-- @param #SHORAD self
-- @param Wrapper.Group#GROUP Group The Shorad Group to Smoke
-- @return self
function SHORAD:_SmokeUnits(Group)
if self.SmokeDecoy == true then
if Group and Group:IsAlive() then
local units = Group:GetUnits()
for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local coordinate = unit:GetCoordinate()
if coordinate then
coordinate:SwitchSmokeOffsetOn()
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,1,20)
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,180,20)
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,270,20)
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,90,20)
end
end
end
end
end
return self
end
--- Calculate if the missile shot is detected --- Calculate if the missile shot is detected
-- @param #SHORAD self -- @param #SHORAD self
-- @return #boolean Returns true for a detection, else false -- @return #boolean Returns true for a detection, else false
@@ -484,7 +535,56 @@ do
-- mymantis:Start() -- mymantis:Start()
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt) 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 TDiff = 4
-- local function to switch off shorad again
local function SleepShorad(group)
if group and group:IsAlive() then
local groupname = group:GetName()
self.ActiveGroups[groupname] = nil
if self.UseEmOnOff then
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
group:SetProperty("SHORAD_ACTIVE",false)
local text = string.format("Sleeping SHORAD %s", 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)
else
--group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
end
end
end
local function WakeUp(_group,groupname)
-- shot at a group we protect
local text = string.format("Waking up SHORAD %s", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
_group:SetProperty("SHORAD_ACTIVE",true)
self:_SmokeUnits(_group)
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
--Shoot and Scoot
if self.shootandscoot then
self:__ShootAndScoot(TDiff,_group)
TDiff=TDiff+1
end
end
end
local targetcat = TargetCat or Object.Category.UNIT local targetcat = TargetCat or Object.Category.UNIT
local targetgroup = TargetGroup local targetgroup = TargetGroup
local targetvec2 = nil local targetvec2 = nil
@@ -501,69 +601,41 @@ do
local groupset = self.Groupset --Core.Set#SET_GROUP local groupset = self.Groupset --Core.Set#SET_GROUP
local shoradset = groupset:GetAliveSet() --#table local shoradset = groupset:GetAliveSet() --#table
-- local function to switch off shorad again
local function SleepShorad(group)
if group and group:IsAlive() then
local groupname = group:GetName()
self.ActiveGroups[groupname] = nil
if self.UseEmOnOff then
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
local text = string.format("Sleeping SHORAD %s", group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
--Shoot and Scoot
if self.shootandscoot then
self:__ShootAndScoot(1,group)
end
end
end
-- go through set and find the one(s) to activate -- go through set and find the one(s) to activate
local TDiff = 4
for _,_group in pairs (shoradset) do for _,_group in pairs (shoradset) do
local groupname = _group:GetName() local groupname = _group:GetName()
if groupname == TargetGroup and ShotAt==true then if groupname == TargetGroup and ShotAt==true then
-- Shot at a SHORAD group -- Shot at a SHORAD group
if self.UseEmOnOff then local allow = false
_group:EnableEmission(false) if self.CallBack and self.UseCallBack == true then
allow = self.CallBack:SeadAllowSuppression(_group,groupname)
end end
_group:OptionAlarmStateGreen() if allow == true then
self.ActiveGroups[groupname] = nil if self.UseEmOnOff then
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) _group:EnableEmission(false)
self:T(text) end
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) _group:OptionAlarmStateGreen()
self.ActiveGroups[groupname] = nil
--Shoot and Scoot local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
if self.shootandscoot then self:T(text)
self:__ShootAndScoot(1,_group) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
end self:_SmokeUnits(_group)
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
-- shot at a group we protect
local text = string.format("Waking up SHORAD %s", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
--Shoot and Scoot --Shoot and Scoot
if self.shootandscoot then if self.shootandscoot then
self:__ShootAndScoot(TDiff,_group) self:__ShootAndScoot(1,_group)
TDiff=TDiff+1 else
_group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
end end
else
WakeUp(_group,groupname)
end end
end elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
end WakeUp(_group,groupname)
end -- end if
end -- end in pairs
return self return self
end end
@@ -679,11 +751,12 @@ do
-- @param Core.Event#EVENTDATA EventData The event details table data set -- @param Core.Event#EVENTDATA EventData The event details table data set
-- @return #SHORAD self -- @return #SHORAD self
function SHORAD:HandleEventShot( EventData ) function SHORAD:HandleEventShot( EventData )
self:T( { EventData } ) --self:T( { EventData.id } )
self:T(self.lid .. " HandleEventShot") self:T(self.lid .. " HandleEventShot")
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
local ShootingWeaponName = EventData.WeaponName -- return weapon type local ShootingWeaponName = EventData.WeaponName -- return weapon type
-- get firing coalition -- get firing coalition
if not EventData.IniGroup then return self end
local weaponcoalition = EventData.IniGroup:GetCoalition() local weaponcoalition = EventData.IniGroup:GetCoalition()
-- get detection probability -- get detection probability
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
@@ -703,7 +776,7 @@ do
-- Is there target data? -- Is there target data?
if not targetdata or self.debug then if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true) then if string.find(ShootingWeaponName,"AGM_88",1,true) then
self:I("**** Tracking AGM-88 with no target data.") self:T("**** Tracking AGM-88 with no target data.")
local pos0 = EventData.IniUnit:GetCoordinate() local pos0 = EventData.IniUnit:GetCoordinate()
local fheight = EventData.IniUnit:GetHeight() local fheight = EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup) self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
@@ -4112,10 +4112,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
-- Get name of template group. -- Get name of template group.
local templategroupname=group:GetName() local templategroupname=group:GetName()
local unit = group:GetUnit(1)
local Descriptors=group:GetUnit(1):GetDesc() local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
local Category=group:GetCategory() local Category=group:GetCategory()
local TypeName=group:GetTypeName() local TypeName=group:GetTypeName() or "none"
local SpeedMax=group:GetSpeedMax() local SpeedMax=group:GetSpeedMax()
local RangeMin=group:GetRange() local RangeMin=group:GetRange()
local smax,sx,sy,sz=_GetObjectSize(Descriptors) local smax,sx,sy,sz=_GetObjectSize(Descriptors)
@@ -6132,6 +6132,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
unit.onboard_num=asset.modex[i] unit.onboard_num=asset.modex[i]
end end
if asset.callsign then if asset.callsign then
--UTILS.PrintTableToLog(asset.callsign)
unit.callsign=asset.callsign[i] unit.callsign=asset.callsign[i]
end end
@@ -8596,8 +8597,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
local item=self.stock[i] --#WAREHOUSE.Assetitem local item=self.stock[i] --#WAREHOUSE.Assetitem
if item.uid==stockitem.uid then if item.uid==stockitem.uid then
table.remove(self.stock,i) table.remove(self.stock,i)
-- remove also from warehouse DB -- remove also from warehouse DB (not good! causes an error if the asset needs to be added to a requesting wherehouse)
_WAREHOUSEDB.Assets[stockitem.uid]=nil --_WAREHOUSEDB.Assets[stockitem.uid]=nil
break break
end end
end end
+1
View File
@@ -118,6 +118,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' )
@@ -49,7 +49,7 @@
-- --
-- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function. -- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function.
-- --
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\radio.lua") -- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua")
-- radios:MarkerShow() -- radios:MarkerShow()
-- --
-- This will load the radios from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file -- This will load the radios from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
+36
View File
@@ -326,6 +326,42 @@ function AIRWING:AddSquadron(Squadron)
if Squadron:IsStopped() then if Squadron:IsStopped() then
Squadron:Start() Squadron:Start()
end end
-- if storage is limited, add the amount of aircraft needed
local airbasename = self:GetAirbaseName()
if airbasename then
local group = Squadron.templategroup
if group then
local Nunits = 1
local units
if group then units = group:GetUnits() end
if units then Nunits = #units end
local typename = Squadron.aircrafttype or "none"
local NAssets = Squadron.Ngroups * Nunits
local storage = STORAGE:New(airbasename)
self:T(self.lid.."Adding "..typename.." #"..NAssets)
if storage and storage.warehouse and storage:IsLimitedAircraft() and typename ~= "none" then
local NInStore = storage:GetItemAmount(typename) or 0
if NAssets > NInStore then
storage:AddItem(typename,NAssets)
end
end
local unit = group:GetUnit(1)
-- if storage is limited, add the amount of liquids needed
if unit and storage and storage.warehouse and storage:IsLimitedLiquids() and typename ~= "none" then
local fuel = unit:GetFuelMassMax()
local neededfuel = (fuel*NAssets) -- kgs of fuel
local NInStore = storage:GetLiquidAmount(STORAGE.Liquid.JETFUEL) or 0
self:T(string.format(self.lid.."Fuel Needed: %dt | Fuel in store: %dt",neededfuel/1000,NInStore/1000))
if neededfuel > NInStore then
storage:AddLiquid(STORAGE.Liquid.JETFUEL,neededfuel)
end
end
end
end
return self return self
end end
+58 -1
View File
@@ -509,7 +509,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.73", -- #string version = "0.2.74", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@@ -2060,6 +2060,55 @@ function AWACS:SetRejectionZone(Zone,Draw)
return self return self
end end
--- Function to set corridor zones.
-- @param #AWACS self
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
-- @return #AWACS self
function AWACS:SetCorridorZones(CorridorZones)
self:T(self.lid .. "SetCorridorZones")
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
self.corridorzones = CorridorZones
self.usecorridors = true
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
self.corridorzones:AddZone(CorridorZones)
self.usecorridors = true
end
return self
end
--- Function to add one corridor zone.
-- @param #AWACS self
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
-- @return #AWACS self
function AWACS:AddCorridorZone(CorridorZone)
self:T(self.lid .. "AddCorridorZone")
self:SetCorridorZones(CorridorZone)
return self
end
--- Function to set corridor zone floor and ceiling in FEET.
-- @param #AWACS self
-- @param #number Floor Floor altitude ASL in feet.
-- @param #number Ceiling Ceiling altitude ASL in feet.
-- @return #AWACS self
function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
self.corridorfloor = UTILS.FeetToMeters(Floor)
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
return self
end
--- Function to set corridor zone floor and ceiling in METERS.
-- @param #AWACS self
-- @param #number Floor Floor altitude ASL in meters.
-- @param #number Ceiling Ceiling altitude ASL in meters.
-- @return #AWACS self
function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
self.corridorfloor = Floor
self.corridorceiling = Ceiling
return self
end
--- [User] Draw a line around the FEZ on the F10 map. --- [User] Draw a line around the FEZ on the F10 map.
-- @param #AWACS self -- @param #AWACS self
-- @return #AWACS self -- @return #AWACS self
@@ -4336,6 +4385,14 @@ function AWACS:_StartIntel(awacs)
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
end end
-- Corridors
if self.usecorridors == true then
intel:SetCorridorZones(self.corridorzones)
if self.corridorceiling or self.corridorfloor then
intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
-- Callbacks -- Callbacks
local function NewCluster(Cluster) local function NewCluster(Cluster)
self:__NewCluster(5,Cluster) self:__NewCluster(5,Cluster)
+85 -32
View File
@@ -31,7 +31,7 @@
-- @image OPS_CSAR.jpg -- @image OPS_CSAR.jpg
--- ---
-- Last Update Oct 2025 -- Last Update Jan 2026
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -81,7 +81,7 @@
-- --
-- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms.
-- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! -- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only!
-- mycsar.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. -- mycsar.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
-- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near.
-- mycsar.autosmokedistance = 1000 -- distance for autosmoke -- mycsar.autosmokedistance = 1000 -- distance for autosmoke
-- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
@@ -119,6 +119,7 @@
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it). -- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it).
-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins). -- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins).
-- mycsar.EnableMenuSmokeMASH = true -- Allow a menu item to request smoke at the closest MASH/AFB for rescue.
-- --
-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups -- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups
-- --
@@ -272,6 +273,8 @@ CSAR = {
UserSetGroup = nil, UserSetGroup = nil,
AllowIRStrobe = false, AllowIRStrobe = false,
IRStrobeRuntime = 300, IRStrobeRuntime = 300,
FARPRescueDistance = 500,
EnableMenuSmokeMASH = true,
} }
--- Downed pilots info. --- Downed pilots info.
@@ -312,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH58D"] = 2 CSAR.AircraftType["OH58D"] = 2
CSAR.AircraftType["CH-47Fbl1"] = 31 CSAR.AircraftType["CH-47Fbl1"] = 31
CSAR.AircraftType["AH-6J"] = 2
CSAR.AircraftType["MH-6J"] = 2
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.34" CSAR.version="1.0.36"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -431,12 +436,13 @@ function CSAR:New(Coalition, Template, Alias)
self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK!
self.beaconRefresher = 29 -- seconds self.beaconRefresher = 29 -- seconds
self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase
self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. self.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
self.max_units = 6 --max number of pilots that can be carried self.max_units = 6 --max number of pilots that can be carried
self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below
self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names!
self.template = Template or "generic" -- template for downed pilot self.template = Template or "generic" -- template for downed pilot
self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mashprefix = {"MASH"} -- prefixes used to find MASHes
self.EnableMenuSmokeMASH = true
self.autosmoke = false -- automatically smoke location when heli is near self.autosmoke = false -- automatically smoke location when heli is near
self.autosmokedistance = 2000 -- distance for autosmoke self.autosmokedistance = 2000 -- distance for autosmoke
@@ -673,7 +679,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
DownedPilot.alive = true DownedPilot.alive = true
DownedPilot.wetfeet = Wetfeet or false DownedPilot.wetfeet = Wetfeet or false
DownedPilot.BeaconName = BeaconName DownedPilot.BeaconName = BeaconName
-- Add Pilot -- Add Pilot
local PilotTable = self.downedPilots local PilotTable = self.downedPilots
local counter = self.downedpilotcounter local counter = self.downedpilotcounter
@@ -851,6 +857,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
else else
BeaconName = "Ghost-1-1"..math.random(1,10000) BeaconName = "Ghost-1-1"..math.random(1,10000)
end end
if _playerName == nil or _playerName == "" then
_playerName = "AI MIA"
end
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
@@ -1232,7 +1242,7 @@ function CSAR:_EventHandler(EventData)
local _place = _event.Place -- Wrapper.Airbase#AIRBASE local _place = _event.Place -- Wrapper.Airbase#AIRBASE
if _place == nil then if _place == nil then
self:T(self.lid .. " Landing Place Nil") self:T(self.lid .. " Landing Place nil")
return self -- error! return self -- error!
end end
@@ -1267,7 +1277,7 @@ function CSAR:_EventHandler(EventData)
self:T("Country = ".._country.." Coalition = ".._coalition) self:T("Country = ".._country.." Coalition = ".._coalition)
if _coalition == self.coalition then if _coalition == self.coalition then
local _freq = self:_GenerateADFFrequency() local _freq = self:_GenerateADFFrequency()
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) self:T({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location. self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location.
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
@@ -1761,7 +1771,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I
end end
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000))
if self.verbose>0 then
local debugtext = string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase))
self:T("*******************************")
self:T(debugtext)
self:T("*******************************")
end
if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then
self:T(self.lid.."[Drop off debug] Distance ok, door check") self:T(self.lid.."[Drop off debug] Distance ok, door check")
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
@@ -2138,15 +2155,52 @@ function CSAR:_Reqsmoke( _unitName )
return self return self
end end
---(Internal) Request smoke at closest MASH/AFB.
--@param #CSAR self
--@param #string _unitName Name of the helicopter
function CSAR:_ReqsmokeMash( _unitName )
self:T(self.lid .. " _ReqsmokeMash")
local _heli = self:_GetSARHeli(_unitName)
if _heli == nil then
return
end
local smokedist = 8000
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
local distance, name, coordinate = self:_GetClosestMASH(_heli)
if coordinate and distance then
local disttext
if _SETTINGS:IsImperial() then
disttext = string.format("%.1fnm",UTILS.MetersToNM(distance))
else
disttext = string.format("%.1fkm",distance/1000)
end
local _msg = string.format("%s - Popping smoke at the closest rescue point: %s", self:_GetCustomCallSign(_unitName), disttext)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
local color = self.smokecolor
coordinate:Smoke(color)
else
local _distance = string.format("%.1fkm",smokedist/1000)
if _SETTINGS:IsImperial() then
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
_distance = string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli, string.format("No rescue point within %s",_distance), self.messageTime, false, false, true)
end
return self
end
--- (Internal) Determine distance to closest MASH. --- (Internal) Determine distance to closest MASH.
-- @param #CSAR self -- @param #CSAR self
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
-- @return #number Distance in meters -- @return #number Distance in meters
-- @return #string MASH Name as string -- @return #string MASH Name as string
-- @return Core.Point#COORDINATE Coordinate The MASH/AFB Coordinate (for smoke)
function CSAR:_GetClosestMASH(_heli) function CSAR:_GetClosestMASH(_heli)
self:T(self.lid .. " _GetClosestMASH") self:T(self.lid .. " _GetClosestMASH")
local _mashset = self.mash -- Core.Set#SET_GROUP local _mashset = self.mash -- Core.Set#SET_GROUP
local MashSets = {} local MashSets = {}
--local _mashes = _mashset.Set-- #table --local _mashes = _mashset.Set-- #table
table.insert(MashSets,_mashset.Set) table.insert(MashSets,_mashset.Set)
table.insert(MashSets,self.zonemashes.Set) table.insert(MashSets,self.zonemashes.Set)
@@ -2155,12 +2209,21 @@ function CSAR:_GetClosestMASH(_heli)
local _distance = 0 local _distance = 0
local _helicoord = _heli:GetCoordinate() local _helicoord = _heli:GetCoordinate()
local MashName = nil local MashName = nil
local Coordinate = nil -- Core.Point#COORDINATE
if self.allowFARPRescue then if self.allowFARPRescue then
local position = _heli:GetCoordinate() local afb,distance = _helicoord:GetClosestAirbase(nil,self.coalition)
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
_shortestDistance = distance _shortestDistance = distance
MashName = (afb ~= nil) and afb:GetName() or "Unknown" MashName = (afb ~= nil) and afb:GetName() or "Unknown"
Coordinate = (afb ~= nil) and afb:GetCoordinate()
if afb then
local afbzone = afb:GetMinimumBoundingCircleFromParkingSpots()
if afbzone then
if afbzone:IsCoordinateInZone(_helicoord) and distance > self.FARPRescueDistance*1.1 then
_shortestDistance = 100
end
end
end
end end
for _,_mashes in pairs(MashSets) do for _,_mashes in pairs(MashSets) do
@@ -2175,12 +2238,13 @@ function CSAR:_GetClosestMASH(_heli)
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance _shortestDistance = _distance
MashName = _mashUnit:GetName() or "Unknown" MashName = _mashUnit:GetName() or "Unknown"
Coordinate = _mashcoord
end end
end end
end end
if _shortestDistance ~= -1 then if _shortestDistance ~= -1 then
return _shortestDistance, MashName return _shortestDistance, MashName, Coordinate
else else
return -1 return -1
end end
@@ -2249,6 +2313,9 @@ function CSAR:_AddMedevacMenuItem()
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName) local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
if self.AllowIRStrobe then if self.AllowIRStrobe then
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
end
if self.EnableMenuSmokeMASH then
local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath, self._ReqsmokeMash,self,_unitName)
else else
_rootMenu4:Refresh() _rootMenu4:Refresh()
end end
@@ -2269,6 +2336,7 @@ function CSAR:_GetDistance(_point1, _point2)
if _point1 and _point2 then if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2) local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2)
MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1)
if distance1 and type(distance1) == "number" then if distance1 and type(distance1) == "number" then
return distance1 return distance1
elseif distance2 and type(distance2) == "number" then elseif distance2 and type(distance2) == "number" then
@@ -2368,7 +2436,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
--local name = _radioUnit:GetName() --local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound local Sound = "l10n/DEFAULT/"..self.radioSound
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z))
trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight
end end
end end
@@ -2496,22 +2564,6 @@ function CSAR:onafterStart(From, Event, To)
self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart() self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart()
--[[
if staticmashes:Count() > 0 then
for _,_mash in pairs(staticmashes.Set) do
self.mash:AddObject(_mash)
end
end
if zonemashes:Count() > 0 then
self:T("Adding zones to self.mash SET")
for _,_mash in pairs(zonemashes.Set) do
self.mash:AddObject(_mash)
end
self:T("Objects in SET: "..self.mash:Count())
end
--]]
if not self.coordinate then if not self.coordinate then
local csarhq = self.mash:GetRandom() local csarhq = self.mash:GetRandom()
if csarhq then if csarhq then
@@ -2854,6 +2906,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
if DownedPilot and DownedPilot.alive then if DownedPilot and DownedPilot.alive then
-- get downed pilot data for saving -- get downed pilot data for saving
local playerName = DownedPilot.player local playerName = DownedPilot.player
if playerName == nil or playerName == "" then playerName = "AI MIA" end
local group = DownedPilot.group local group = DownedPilot.group
local coalition = group:GetCoalition() local coalition = group:GetCoalition()
local country = group:GetCountry() local country = group:GetCountry()
@@ -2864,7 +2917,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
local unitName = DownedPilot.originalUnit local unitName = DownedPilot.originalUnit
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: " .. txt) self:T(self.lid.."Saving to CSAR File: " .. txt)
data = data .. txt data = data .. txt
end end
@@ -2979,8 +3032,8 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
-- Info message. -- Info message.
local text=string.format("Loading CSAR state from file %s", filename) local text=string.format("Loading CSAR state from file %s", filename)
MESSAGE:New(text,10):ToAllIf(self.Debug) MESSAGE:New(text,10):ToAllIf(self.verbose>0)
self:I(self.lid..text) self:T(self.lid..text)
local file=assert(io.open(filename, "rb")) local file=assert(io.open(filename, "rb"))
File diff suppressed because it is too large Load Diff
+11 -1
View File
@@ -396,7 +396,8 @@ function CHIEF:New(Coalition, AgentSet, Alias)
self.TransportCategories = {Group.Category.HELICOPTER} self.TransportCategories = {Group.Category.HELICOPTER}
-- Create a new COMMANDER. -- Create a new COMMANDER.
self.commander=COMMANDER:New(Coalition) self.commander=COMMANDER:New(Coalition, Alias)
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
@@ -3357,6 +3358,15 @@ function CHIEF._CheckAssetProperties(Asset, Properties)
return false return false
end end
--- Checks whether or not any of the legions con run a mission.
-- @param #CHIEF self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #boolean If `true`, one of the cohorts can run the mission.
function CHIEF:CanMission(Mission)
return self.commander and self.commander:CanMission(Mission)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+64
View File
@@ -1748,6 +1748,70 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
return cohorts return cohorts
end end
--- Checks whether or not any of the legions con run a mission.
-- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #boolean If `true`, one of the cohorts can run the mission.
function COMMANDER:CanMission(Mission)
local commander = self
-- Target position.
local TargetVec2 = Mission:GetTargetVec2()
local MaxWeight = nil
if Mission.NcarriersMin then
local legions = commander.legions
local cohorts = nil
if Mission.transportLegions or Mission.transportCohorts then
legions = Mission.transportLegions
cohorts = Mission.transportCohorts
end
-- Get transport cohorts.
local Cohorts = LEGION._GetCohorts(legions, cohorts)
-- Filter cohorts that can actually perform transport missions.
local transportcohorts = {}
for _, _cohort in pairs(Cohorts) do
local cohort = _cohort --Ops.Cohort#COHORT
-- Check if cohort can perform transport to target.
local can = LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Mission.carrierCategories, Mission.carrierAttributes, Mission.carrierProperties, nil, TargetVec2)
-- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos.
if can and (MaxWeight == nil or cohort.cargobayLimit > MaxWeight) then
MaxWeight = cohort.cargobayLimit
end
end
end
local legions = commander.legions
local cohorts = nil
if Mission.specialLegions or Mission.specialCohorts then
legions = Mission.specialLegions
cohorts = Mission.specialCohorts
end
-- Get cohorts.
local Cohorts = LEGION._GetCohorts(legions, cohorts, Mission.operation, commander.opsqueue)
for _, _cohort in pairs(Cohorts) do
local cohort = _cohort --Ops.Cohort#COHORT
-- Check if cohort can do the mission.
local can = LEGION._CohortCan(cohort, Mission.type, nil, Mission.attributes, Mission.properties, { Mission.engageWeaponType }, TargetVec2, Mission.engageRange, Mission.refuelSystem, nil, MaxWeight)
if can then
return true
end
end
return false
end
--- Recruit assets for a given mission. --- Recruit assets for a given mission.
-- @param #COMMANDER self -- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
+950
View File
@@ -0,0 +1,950 @@
--- **Ops** - Create your A2G Defenses.
--
-- **Main Features:**
--
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
-- * Easy set-up
--
-- ===
--
-------------------------------------------------------------------------
-- Easy A2G Engagement Class, based on OPS classes
-------------------------------------------------------------------------
--
-- ## Documentation:
--
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyAG.html
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyAG).
--
-------------------------------------------------------------------------
-- Date: Dec 2025
-- Last Update: Jan 2026
-------------------------------------------------------------------------
--
-- ===
--
-- **Main Features:**
--
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
-- * Easy set-up
-- * Add additional AirWings on other airbases
-- * Each wing can have more than one Squadron - tasking to Squadrons is done on a random basis per AirWing
-- * Create borders and zones of engagement
-- * Detection can be ground based and/or via AWACS
--
-- ===
--
-- ### AUTHOR: **applevangelist**
--
-- @module Ops.EasyAG
-- @image AI_Air_To_Ground_Dispatching.JPG
--- EASYA2G Class
-- @type EASYA2G
-- @field #string ClassName
-- @field #number overhead
-- @field #number engagerange
-- @field #number capgrouping
-- @field #string airbasename
-- @field Wrapper.Airbase#AIRBASE airbase
-- @field #number coalition
-- @field #string alias
-- @field #table wings
-- @field Ops.Intel#INTEL Intel
-- @field #number resurrection
-- @field #number capspeed
-- @field #number capalt
-- @field #number capdir
-- @field #number capleg
-- @field #number maxinterceptsize
-- @field #number missionrange
-- @field #number noalert5
-- @field #table ManagedAW
-- @field #table ManagedSQ
-- @field #table ManagedCP
-- @field #table ManagedTK
-- @field #table ManagedEWR
-- @field #table ManagedREC
-- @field #number MaxAliveMissions
-- @field #boolean debug
-- @field #number repeatsonfailure
-- @field Core.Set#SET_ZONE GoZoneSet
-- @field Core.Set#SET_ZONE NoGoZoneSet
-- @field Core.Set#SET_ZONE ConflictZoneSet
-- @field #boolean Monitor
-- @field #boolean TankerInvisible
-- @field #number CapFormation
-- @field #table ReadyFlightGroups
-- @field #boolean DespawnAfterLanding
-- @field #boolean DespawnAfterHolding
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
-- @field #string defaulttakeofftype Take off type
-- @field #number FuelLowThreshold
-- @field #number FuelCriticalThreshold
-- @field #boolean showpatrolpointmarks
-- @field #table EngageTargetTypes
-- @extends Ops.EasyGCICAP#EASYGCICAP
--- *“High-Threat Close-Air-Support is a Myth.”* -- Mike “Starbaby” Pietrucha.
--
-- ===
--
-- # The EasyAG Concept
--
-- The idea of this class is partially to make the OPS classes easier operational for an A2G defense network, and to replace the legacy AI_A2G_Dispatcher system - not to it's
-- full extent, but make a basic system work very quickly.
--
-- # Setup
--
-- ## Basic understanding
--
-- The basics are, there is **one** and only **one** AirWing per airbase. Each AirWing has **at least** one Squadron, who will do A2G tasks. Squadrons will be randomly chosen for the task at hand.
-- Each AirWing has **at least** one Conflict Zone that it manages. COnflict Zones will be covered by the AirWing automatically as long as airframes are available. Detected enemy ground forces will be assigned to **one**
-- AirWing based on proximity (that is, if you have more than one).
--
-- ## Assignment of tasks for enemies
--
-- An exisiting plane or a newly spawned plane will take care of the intruders. Standard overhead is 0.1, i.e. a group of 10 intrudes will
-- be managed by one planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies.
--
-- ## Basic set-up code
--
-- ### Prerequisites
--
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
-- Next put a late activated template group for your A2G Squadron on the map. Last, put a zone on the map for the Defense operations, let's name it "Blue Zone 1". Size of the zone plays no role.
-- Put a scout system on the map and name it aptly, like "Blue SCOUT".
--
-- ### Zones
--
-- For our example, you create a RED and a BLUE border, as a closed polygonal zone representing the borderlines. You can also have conflict zone, where - for our example - BLUE will attack
-- RED groups, despite being on or close to RED territory. Think of a no-fly zone or an limited area of engagement. Conflict zones take precedence over borders, i.e. they can overlap all borders.
--
-- ### Code it
--
-- -- Set up a basic system for the blue side, we'll reside on Kutaisi, and use GROUP objects with "Blue SCOUT" in the name as Detecting Systems.
-- local mywing = EASYA2G:New("A2G",AIRBASE.Caucasus.Kutaisi,"blue","SCOUT")
--
-- -- Add a holding/ingress point belonging to our airbase, we'll be at 5k ft doing 250 kn, initial direction 225 degrees (West), leg 5NM
-- -- This will effectively be the ingress coordinate into the cnflict zone
-- local Coordinate = ZONE:New("A2G Loitering"):GetCoordinate()
-- mywing:AddHoldingPointA2G(AIRBASE.Caucasus.Kutaisi,Coordinate,5000,250,225,5)
--
-- -- Add a recon point over the conflict zone, we'll use a reaper for recon
-- local Coordinate2 = ZONE:New("A2G Recon"):GetCoordinate()
-- mywing:AddPatrolPointRecon(AIRBASE.Caucasus.Kutaisi,Coordinate2,15000,225,225,5)
--
-- -- Add three Squadrons with templates "Hero 1" and "Hero 2", 20 airframes, skill as set
-- mywing:AddSquadron("A2G Flight", "Hero 1", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.GOOD, Modex, Livery)
-- mywing:AddSquadron("A2G Helo", "Hero 2", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.HIGH, Modex, Livery)
-- mywing:AddReconSquadron("Recon Drone", "SpyInTheSky SCOUT", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.EXCELLENT, Modex, Livery)
--
-- -- Ensure our reaper doesn't get immediately killed
-- mywing:SetTankerAndScoutsInvisible(true)
--
-- -- Add a couple of zones
-- -- We'll defend our own border
-- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) ))
-- -- We'll attack intruders also here - conflictzones can overlap borders(!) - limited zone of engagement
-- mywing:AddConflictZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )))
-- -- We'll leave the reds alone on their turf
-- mywing:AddRejectZone(ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ))
--
-- -- Optional - Draw the borders on the map so we see what's going on
-- -- Set up borders on map
-- local BlueBorder = ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )
-- BlueBorder:DrawZone(-1,{0,0,1},1,FillColor,FillAlpha,1,true)
-- local ConflictZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))
-- ConflictZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true)
-- local BlueNoGoZone = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) )
-- BlueNoGoZone:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,true)
--
-- ### Add a second airwing with squads and own patrol point (optional)
--
-- -- Set this up at Sukhumi
-- mywing:AddAirwing(AIRBASE.Caucasus.Sukhumi_Babushara,"Blue A2G Sukhumi")
-- -- A2G Point "Blue Zone 2"
-- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Sukhumi_Babushara,ZONE:FindByName("Blue Zone 2"):GetCoordinate(),30000,400,90,20)
--
-- -- This one has two squadrons to choose from
-- mywing:AddSquadron("Blue Sq3 F16","A2G Sukhumi II",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,402,"JASDF 6th TFS 43-8526 Skull Riders")
-- mywing:AddSquadron("Blue Sq2 F15","A2G Sukhumi I",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,202,"390th Fighter SQN")
--
-- ### Add a tanker (optional)
--
-- -- **Note** If you need different tanker types, i.e. Boom and Drogue, set them up at different AirWings!
-- -- Add a tanker point
-- mywing:AddPatrolPointTanker(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone Tanker"):GetCoordinate(),20000,280,270,50)
-- -- Add a tanker squad - Radio 251 AM, TACAN 51Y
-- mywing:AddTankerSquadron("Blue Tanker","Tanker Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.EXCELLENT,602,nil,251,radio.modulation.AM,51)
--
-- ### Add an AWACS (optional)
--
-- -- Add an AWACS point
-- mywing:AddPatrolPointAwacs(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone AWACS"):GetCoordinate(),25000,300,270,50)
-- -- Add an AWACS squad - Radio 251 AM, TACAN 51Y
-- mywing:AddAWACSSquadron("Blue AWACS","AWACS Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.AVERAGE,702,nil,271,radio.modulation.AM)
--
-- # Fine-Tuning
--
-- ## Change Defaults
--
-- * @{#EASYA2G.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
-- * @{#EASYA2G.SetDefaultA2GSpeed}: Set how many knots the A2G flights should do (will be altitude corrected), default 225 kn.
-- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 10,000 ft.
-- * @{#EASYA2G.SetDefaultA2GDirection}: Set the initial direction from the A2G point the planes will fly in degrees, default is 90°.
-- * @{#EASYA2G.SetDefaultA2GLeg}: Set the length of the A2G leg, default is 5 NM.
-- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 1.
-- * @{#EASYA2G.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
-- * @{#EASYA2G.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
-- * @{#EASYA2G.SetDefaultEngageRange}: Set max engage range for A2G flights if they detect intruders, defaults to 50.
-- * @{#EASYA2G.SetMaxAliveMissions}: Set max parallel missions can be done (A2G+GCI+Alert5+Tanker+AWACS), defaults to 8.
-- * @{#EASYA2G.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
-- * @{#EASYA2G.SetTankerAndScoutsInvisible}: Set Tanker and Scouts to be invisible to enemy AI eyes. Is set to `true` by default.
--
-- ## Debug and Monitor
--
-- mywing.debug = true -- log information
-- mywing.Monitor = true -- show some statistics on screen
--
--
-- @field #EASYA2G
EASYA2G = {
ClassName = "EASYA2G",
overhead = 0.2,
capgrouping = 1,
airbasename = nil,
airbase = nil,
coalition = "blue",
alias = nil,
wings = {},
Intel = nil,
resurrection = 900,
capspeed = 300,
capalt = 25000,
capdir = 45,
capleg = 5,
maxinterceptsize = 2,
missionrange = 100,
noalert5 = 2,
ManagedAW = {},
ManagedSQ = {},
ManagedCP = {},
ManagedTK = {},
ManagedEWR = {},
ManagedREC = {},
MaxAliveMissions = 8,
debug = false,
engagerange = 50,
repeatsonfailure = 3,
GoZoneSet = nil,
NoGoZoneSet = nil,
ConflictZoneSet = nil,
Monitor = false,
TankerInvisible = true,
CapFormation = nil,
ReadyFlightGroups = {},
DespawnAfterLanding = false,
DespawnAfterHolding = true,
ListOfAuftrag = {},
defaulttakeofftype = "hot",
FuelLowThreshold = 25,
FuelCriticalThreshold = 10,
showpatrolpointmarks = false,
EngageTargetTypes = {"Ground"},
}
--- Internal Squadron data type
-- @type EASYA2G.Squad
-- @field #string TemplateName
-- @field #string SquadName
-- @field #string AirbaseName
-- @field #number AirFrames
-- @field #string Skill
-- @field #string Modex
-- @field #string Livery
-- @field #boolean Tanker
-- @field #boolean AWACS
-- @field #boolean RECON
-- @field #number Frequency
-- @field #number Modulation
-- @field #number TACAN
--- Internal Wing data type
-- @type EASYA2G.Wing
-- @field #string AirbaseName
-- @field #string Alias
-- @field #string CapZoneName
--- Internal CapPoint data type
-- @type EASYA2G.CapPoint
-- @field #string AirbaseName
-- @field Core.Point#COORDINATE Coordinate
-- @field #number Altitude
-- @field #number Speed
-- @field #number Heading
-- @field #number LegLength
-- @field Core.Zone#ZONE_BASE Zone
--- EASYA2G class version.
-- @field #string version
EASYA2G.version="0.1.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: TBD
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new GCIA2G Manager
-- @param #EASYA2G self
-- @param #string Alias A Name for this A2G Setup
-- @param #string AirbaseName Name of the Home Airbase
-- @param #string Coalition Coalition, e.g. "blue" or "red"
-- @param #string ScoutName (Partial) group name of the detection system of the coalition, e.g. "Red SCOUT", can be handed in as table of names, e.g.{"SCOUT","DRONE","SAM"}
-- @return #EASYA2G self
function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, EASYGCICAP:New(Alias, AirbaseName, Coalition, ScoutName)) -- #EASYA2G
-- defaults
self.alias = Alias or AirbaseName.." A2G Wing"
-- Set some string id for output to DCS.log file.
self.lid=string.format("EASYA2G %s | ", self.alias)
self.coalitionname = string.lower(Coalition) or "blue"
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
self.wings = {}
if type(ScoutName) == "string" then ScoutName = {ScoutName} end
self.EWRName = ScoutName --or self.coalitionname.." EWR"
--self.CapZoneName = CapZoneName
self.airbasename = AirbaseName
self.airbase = AIRBASE:FindByName(self.airbasename)
self.GoZoneSet = SET_ZONE:New()
self.NoGoZoneSet = SET_ZONE:New()
self.ConflictZoneSet = SET_ZONE:New()
self.resurrection = 900
self.capspeed = 225
self.capalt = 5000
self.capdir = 90
self.capleg = 5
self.capgrouping = 2
self.missionrange = 100
self.noalert5 = 2
self.MaxAliveMissions = 8
self.engagerange = 50
self.repeatsonfailure = 3
self.Monitor = false
self.TankerInvisible = true
self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group
self.DespawnAfterLanding = false
self.DespawnAfterHolding = true
self.ListOfAuftrag = {}
self.defaulttakeofftype = "hot"
self.FuelLowThreshold = 25
self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false
self.EngageTargetTypes = {"Ground"}
self:SetDefaultTurnoverTime()
-- Add FSM transitions.
-- From State --> Event --> To State
self:SetStartState("Stopped")
self:AddTransition("Stopped", "Start", "Running")
self:AddTransition("Running", "Stop", "Stopped")
self:AddTransition("*", "Status", "*")
--- On Before "Start" event.
-- @function [parent=#EASYA2G] OnBeforeStart
-- @param #EASYA2G self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "Start" event.
-- @function [parent=#EASYA2G] OnAfterStart
-- @param #EASYA2G self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On Before "Status" event.
-- @function [parent=#EASYA2G] OnBeforeStatus
-- @param #EASYA2G self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "Status" event.
-- @function [parent=#EASYA2G] OnAfterStatus
-- @param #EASYA2G self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
self:AddAirwing(self.airbasename,self.alias,self.CapZoneName)
self:I(self.lid.."Created new instance (v"..self.version..")")
self:__Start(math.random(6,12))
return self
end
-------------------------------------------------------------------------
-- Functions
-------------------------------------------------------------------------
--- Set Tanker and Scouts to be invisible to enemy AI eyes
-- @param #EASYA2G self
-- @param #boolean Switch Set to true or false, by default this is set to true already
-- @return #EASYA2G self
function EASYA2G:SetTankerAndScoutsInvisible(Switch)
self:T(self.lid.."SetTankerAndScoutsInvisible")
self.TankerInvisible = Switch
return self
end
--- Set default A2G Speed in knots
-- @param #EASYA2G self
-- @param #number Speed Speed defaults to 300
-- @return #EASYA2G self
function EASYA2G:SetDefaultA2GSpeed(Speed)
self:T(self.lid.."SetDefaultSpeed")
self.capspeed = Speed or 300
return self
end
--- Set A2G Flight formation.
-- @param #EASYA2G self
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
-- @return #EASYA2G self
function EASYA2G:SetA2GFormation(Formation)
self:T(self.lid.."SetA2GFormation")
self.CapFormation = Formation
return self
end
--- Set default A2G Altitude in feet
-- @param #EASYA2G self
-- @param #number Altitude Altitude defaults to 25000
-- @return #EASYA2G self
function EASYA2G:SetDefaultA2GAlt(Altitude)
self:T(self.lid.."SetDefaultAltitude")
self.capalt = Altitude or 25000
return self
end
--- Set default A2G lieg initial direction in degrees
-- @param #EASYA2G self
-- @param #number Direction Direction defaults to 90 (East)
-- @return #EASYA2G self
function EASYA2G:SetDefaultA2GDirection(Direction)
self:T(self.lid.."SetDefaultDirection")
self.capdir = Direction or 90
return self
end
--- Set default leg length in NM
-- @param #EASYA2G self
-- @param #number Leg Leg defaults to 5
-- @return #EASYA2G self
function EASYA2G:SetDefaultA2GLeg(Leg)
self:T(self.lid.."SetDefaultLeg")
self.capleg = Leg or 5
return self
end
--- Set default grouping, i.e. how many airplanes per A2G point
-- @param #EASYA2G self
-- @param #number Grouping Grouping defaults to 2
-- @return #EASYA2G self
function EASYA2G:SetDefaultA2GGrouping(Grouping)
self:T(self.lid.."SetDefaultA2GGrouping")
self.capgrouping = Grouping or 2
return self
end
--- Set A2G mission start to vary randomly between Start end End seconds.
-- @param #EASYA2G self
-- @param #number Start
-- @param #number End
-- @return #EASYA2G self
function EASYA2G:SetA2GStartTimeVariation(Start, End)
self.capOptionVaryStartTime = Start or 5
self.capOptionVaryEndTime = End or 60
return self
end
--- Set which target types A2G flights will prefer to engage, defaults to {"Ground"}
-- @param #EASYA2G self
-- @param #table types Table of comma separated #string entries, defaults to {"Ground"} (everything that is ground and is not a weapon). Useful other options are e.g. {"Armored vehicles"}, {"Tanks"},
-- or {"APC"} or combinations like {"APC", "Tanks", "Artillery"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes).
-- @return #EASYA2G self
function EASYA2G:SetA2GEngageTargetTypes(types)
self.EngageTargetTypes = types or {"Ground"}
return self
end
--- Add a A2G patrol/holding point to a Wing
-- @param #EASYA2G self
-- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet ASL.
-- @param #number Speed Defaults to 300 knots TAS.
-- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM.
-- @return #EASYA2G self
function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddHoldingPointA2G")--..Coordinate:ToStringLLDDM())
local coordinate = Coordinate
local EntryCAP = {} -- #EASYGCICAP.CapPoint
if Coordinate:IsInstanceOf("ZONE_BASE") then
-- adjust coordinate and get the coordinate from the zone
coordinate = Coordinate:GetCoordinate()
EntryCAP.Zone = Coordinate
end
EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = coordinate
EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 5
self.ManagedCP[#self.ManagedCP+1] = EntryCAP
if self.debug then
local mark = MARKER:New(coordinate,self.lid.."Holding Point"):ToAll()
end
return self
end
--- (Internal) Add a Squadron to an Airwing of the manager
-- @param #EASYA2G self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio Frequency to be used.
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
-- @return #EASYA2G self
function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
self:T(self.lid.."_AddSquadron "..SquadName)
-- Add Squadrons
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE})
--Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE},75)
return self
end
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
-- @param #EASYA2G self
-- @param #table ReadyFlightGroups ReadyFlightGroups
-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag
-- @param Wrapper.Group#GROUP Group The Target
-- @param #number WingSize Calculated number of Flights
-- @return #boolean assigned
-- @return #number leftover
function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize)
self:T("_TryAssignMission for size "..WingSize or 1)
local assigned = false
local wingsize = WingSize or 1
local mindist = 0
local disttable = {}
if Group and Group:IsAlive() then
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
for _name,_FG in pairs(ReadyFlightGroups or {}) do
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
local fcoord = FG:GetCoordinate()
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
disttable[#disttable+1] = { FG=FG, dist=dist}
if dist>mindist then mindist=dist end
end
local function sortDistance(a, b)
return a.dist < b.dist
end
table.sort(disttable, sortDistance)
for _,_entry in ipairs(disttable) do
local FG = _entry.FG -- Ops.FlightGroup#FLIGHTGROUP
FG:AddMission(Auftrag)
local cm = FG:GetMissionCurrent()
if cm then cm:Cancel() end
wingsize = wingsize - 1
self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
if wingsize == 0 then
assigned = true
break
end
end
end
return assigned, wingsize
end
--- Find a holding point closest to the group to be attacked (if any set)
-- @param #EASYA2G self
-- @param Wrapper.Group#GROUP Group
-- @return Core.Point#COORDINATE Point (can be nil!)
function EASYA2G:_GetClosestHoldingPoint(Group)
local point = nil
local mindist = 0
if Group and Group:IsAlive() then
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
for _,_data in pairs(self.ManagedCP or {}) do
local data = _data -- #EASYGCICAP.CapPoint
--data.Coordinate
local dist = math.floor(UTILS.Round(data.Coordinate:Get2DDistance(gcoord)/1000,1))
self:T(self.lid..string.format("Holding Point Distance %dkm",dist))
if dist>mindist then
mindist=dist
point=data.Coordinate
end
end
end
return point
end
--- Here, we'll decide if we need to launch an attacking flight, and from where
-- @param #EASYA2G self
-- @param Ops.Intel#INTEL.Cluster Cluster
-- @return #EASYA2G self
function EASYA2G:_AssignMission(Cluster)
self:I(self.lid.."_AssignMission")
-- Here, we'll decide if we need to launch an attacking flight, and from where
local overhead = self.overhead
local capspeed = self.capspeed + 100
local capalt = self.capalt or 5000
local maxsize = self.maxinterceptsize
local repeatsonfailure = self.repeatsonfailure
local wings = self.wings
local ctlpts = self.ManagedCP
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
local nogozoneset = self.NoGoZoneSet
local conflictzoneset = self.ConflictZoneSet
local ReadyFlightGroups = self.ReadyFlightGroups
-- Aircraft?
if Cluster.ctype == INTEL.Ctype.AIRCRAFT then return end
-- Threatlevel 0..10
local contact = self.Intel:GetHighestThreatContact(Cluster)
local name = contact.groupname --#string
local threat = contact.threatlevel --#number
local position = self.Intel:CalcClusterFuturePosition(Cluster,300)
-- calculate closest zone
local bestdistance = 2000*1000 -- 2000km
local targetairwing = nil -- Ops.Airwing#AIRWING
local targetawname = "" -- #string
local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1
local wingsize = math.abs(overhead * (clustersize+1))
if wingsize > maxsize then wingsize = maxsize end
-- existing mission, and if so - done?
local retrymission = true
if Cluster.mission and (not Cluster.mission:IsOver()) then
retrymission = false
end
if (retrymission) and (wingsize >= 1) then
MESSAGE:New(string.format("**** %s Attackers need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"A2G"):ToAllIf(self.debug):ToLog()
for _,_data in pairs (wings) do
local airwing = _data[1] -- Ops.Airwing#AIRWING
local zone = _data[2] -- Core.Zone#ZONE
local zonecoord = zone:GetCoordinate()
local name = _data[3] -- #string
local coa = AIRBASE:FindByName(name):GetCoalition()
local distance = position:DistanceFromPointVec2(zonecoord)
local airframes = airwing:CountAssets(true)
local samecoalitionab = coa == self.coalition and true or false
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
bestdistance = distance
targetairwing = airwing
targetawname = name
end
end
for _,_data in pairs (ctlpts) do
--local airwing = _data[1] -- Ops.Airwing#AIRWING
--local zone = _data[2] -- Core.Zone#ZONE
--local zonecoord = zone:GetCoordinate()
--local name = _data[3] -- #string
local data = _data -- #EASYGCICAP.CapPoint
local name = data.AirbaseName
local zonecoord = data.Coordinate
if data.Zone then
-- refresh coordinate in case we have a (moving) zone
zonecoord = data.Zone:GetCoordinate()
end
local airwing = wings[name][1]
local coa = AIRBASE:FindByName(name):GetCoalition()
local samecoalitionab = coa == self.coalition and true or false
local distance = position:DistanceFromPointVec2(zonecoord)
local airframes = airwing:CountAssets(true)
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
bestdistance = distance
targetairwing = airwing -- Ops.Airwing#AIRWING
targetawname = name
end
end
local text = string.format("Closest Airwing is %s", targetawname)
local m = MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog()
-- Do we have a matching airwing?
if targetairwing then
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
local missioncount = self:_CountAliveAuftrags()
-- Enough airframes on mission already?
self:T(self.lid.." Assets on Mission "..AssetCount)
if missioncount < MaxAliveMissions then
local repeats = repeatsonfailure
local Vec1 = contact.group:GetVec2()
local Vec2 = targetairwing:GetVec2()
--local HoldingVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
local IngressCoordinate = self:_GetClosestHoldingPoint(contact.group)
if IngressCoordinate == nil then
local IngressVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
IngressCoordinate = COORDINATE:NewFromVec2(IngressVec2)
end
local InterceptAuftrag = AUFTRAG:NewBAI(contact.group,capalt)
:SetMissionRange(150)
:SetPriority(1,true,1)
:SetRepeatDelay(300)
--:SetRequiredAssets(wingsize)
:SetRepeatOnFailure(repeats)
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
:SetMissionAltitude(capalt)
-- TODO: Refine this
--:SetMissionHoldingCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed,120)
:SetMissionIngressCoord(IngressCoordinate,capalt,capspeed)
--:SetMissionEgressCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed)
if nogozoneset:Count() > 0 then
InterceptAuftrag:AddConditionSuccess(
function(group,zoneset,conflictset)
local success = false
if group and group:IsAlive() then
local coord = group:GetCoordinate()
if coord and zoneset:Count() > 0 and zoneset:IsCoordinateInZone(coord) then
success = true
end
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
success = false
end
else
success = true -- target dead
end
return success
end,
contact.group,
nogozoneset,
conflictzoneset
)
end
InterceptAuftrag:AddConditionFailure(
function()
local failure = false
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
return failure
end
)
table.insert(self.ListOfAuftrag,InterceptAuftrag)
local assigned, rest = self:_TryAssignMission(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
if not assigned then
InterceptAuftrag:SetRequiredAssets(rest)
targetairwing:AddMission(InterceptAuftrag)
end
Cluster.mission = InterceptAuftrag
end
else
MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"EasyA2G"):ToAllIf(self.debug):ToLog()
end
end
end
--- (Internal) Start detection.
-- @param #EASYA2G self
-- @return #EASYA2G self
function EASYA2G:_StartIntel()
self:T(self.lid.."_StartIntel")
-- Border GCI Detection
local BlueAir_DetectionSetGroup = SET_GROUP:New()
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
BlueAir_DetectionSetGroup:FilterStart()
-- Intel type detection
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
BlueIntel:SetClusterAnalysis(true,false,false)
BlueIntel:SetForgetTime(300)
BlueIntel:SetAcceptZones(self.GoZoneSet)
BlueIntel:SetRejectZones(self.NoGoZoneSet)
BlueIntel:SetConflictZones(self.ConflictZoneSet)
BlueIntel:SetVerbosity(0)
if self.usecorridors == true then
BlueIntel:SetCorridorZones(self.corridorzones)
if self.corridorfloor or self.corridorceiling then
BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling)
end
end
BlueIntel:Start()
if self.debug then
BlueIntel.debug = true
end
local function AssignCluster(Cluster)
self:_AssignMission(Cluster)
end
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
AssignCluster(Cluster)
end
self.Intel = BlueIntel
return self
end
-------------------------------------------------------------------------
-- TODO FSM Functions
-------------------------------------------------------------------------
--- (Internal) FSM Function onafterStart
-- @param #EASYA2G self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYA2G self
function EASYA2G:onafterStart(From,Event,To)
self:T({From,Event,To})
self:_StartIntel()
self:_CreateAirwings()
self:_CreateSquads()
--self:_SetCAPPatrolPoints()
self:_SetTankerPatrolPoints()
self:_SetAwacsPatrolPoints()
self:_SetReconPatrolPoints()
self:__Status(-10)
return self
end
--- (Internal) FSM Function onafterStatus
-- @param #EASYA2G self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYA2G self
function EASYA2G:onafterStatus(From,Event,To)
self:T({From,Event,To})
-- cleanup
local cleaned = false
local cleanlist = {}
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
table.insert(cleanlist,auftrag)
cleaned = true
end
end
if cleaned == true then
self.ListOfAuftrag = nil
self.ListOfAuftrag = cleanlist
end
-- Gather Some Stats
local function counttable(tbl)
local count = 0
for _,_data in pairs(tbl) do
count = count + 1
end
return count
end
local wings = counttable(self.ManagedAW)
local squads = counttable(self.ManagedSQ)
local caps = counttable(self.ManagedCP)
local assets = 0
local instock = 0
local capmission = 0
local interceptmission = 0
local reconmission = 0
local awacsmission = 0
local tankermission = 0
local alert5mission = 0
for _,_wing in pairs(self.wings) do
local count = _wing[1]:CountAssetsOnMission(MissionTypes,Cohort)
local count2 = _wing[1]:CountAssets(true,MissionTypes,Attributes)
--capmission = capmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.PATROLRACETRACK})
interceptmission = interceptmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI})
reconmission = reconmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON})
awacsmission = awacsmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS})
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
alert5mission = alert5mission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5})
assets = assets + count
instock = instock + count2
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5})
-- update ready groups
self.ReadyFlightGroups = nil
self.ReadyFlightGroups = {}
for _,_asset in pairs(assetsonmission or {}) do
local asset = _asset -- Functional.Warehouse#WAREHOUSE.Assetitem
local FG = asset.flightgroup -- Ops.FlightGroup#FLIGHTGROUP
if FG then
local name = FG:GetName()
local engage = FG:IsEngaging()
local hasmissiles = FG:CanAirToGround()
--self:T("Is Alert5? "..tostring(FG:GetMissionCurrent().type))
local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()), tostring(isalert5)))
if ready then
self.ReadyFlightGroups[name] = FG
end
end
end
end
if self.Monitor then
local threatcount = #self.Intel.Clusters or 0
local text = self.alias
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
text = text.."\nThreats: "..threatcount
text = text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission
--text = text.."\n - A2G Holding: "..capmission
text = text.."\n - A2G Attack: "..interceptmission
text = text.."\n - AWACS: "..awacsmission
text = text.."\n - TANKER: "..tankermission
text = text.."\n - Recon: "..reconmission
text = text.."\n - Alert5 "..alert5mission
text = text.."\nMission Limit: "..self.MaxAliveMissions
MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug)
end
self:__Status(30)
return self
end
+165 -18
View File
@@ -1,3 +1,12 @@
--- **Ops** - Create your A2A Defenses.
--
-- **Main Features:**
--
-- * Automatically create and manage A2A defenses using an AirWing and Squadrons for one coalition
-- * Easy set-up
--
-- ===
--
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Easy CAP/GCI Class, based on OPS classes -- Easy CAP/GCI Class, based on OPS classes
------------------------------------------------------------------------- -------------------------------------------------------------------------
@@ -12,7 +21,7 @@
-- --
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Date: September 2023 -- Date: September 2023
-- Last Update: Aug 2025 -- Last Update: Jan 2026
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- --
--- **Ops** - Easy GCI & CAP Manager --- **Ops** - Easy GCI & CAP Manager
@@ -80,6 +89,8 @@
-- @field #number FuelCriticalThreshold -- @field #number FuelCriticalThreshold
-- @field #boolean showpatrolpointmarks -- @field #boolean showpatrolpointmarks
-- @field #table EngageTargetTypes -- @field #table EngageTargetTypes
-- @field #number maintenancetime
-- @field #number repairtime
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
@@ -109,7 +120,7 @@
-- ### Prerequisites -- ### Prerequisites
-- --
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!** -- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ. -- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
-- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role. -- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role.
-- Put an EW radar system on the map and name it aptly, like "Blue EWR". -- Put an EW radar system on the map and name it aptly, like "Blue EWR".
-- --
@@ -205,7 +216,7 @@ EASYGCICAP = {
coalition = "blue", coalition = "blue",
alias = nil, alias = nil,
wings = {}, wings = {},
Intel = nil, Intel = nil, -- Ops.Intel#INTEL
resurrection = 900, resurrection = 900,
capspeed = 300, capspeed = 300,
capalt = 25000, capalt = 25000,
@@ -275,10 +286,11 @@ EASYGCICAP = {
--- EASYGCICAP class version. --- EASYGCICAP class version.
-- @field #string version -- @field #string version
EASYGCICAP.version="0.1.30" EASYGCICAP.version="0.1.34"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list --
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: TBD -- TODO: TBD
@@ -300,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
-- defaults -- defaults
self.alias = Alias or AirbaseName.." CAP Wing" self.alias = Alias or AirbaseName.." CAP Wing"
-- Set some string id for output to DCS.log file.
self.lid=string.format("EASYGCICAP %s | ", self.alias)
self.coalitionname = string.lower(Coalition) or "blue" self.coalitionname = string.lower(Coalition) or "blue"
self.coalition = self.coalitionname == "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 = {}
@@ -333,10 +349,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self.FuelCriticalThreshold = 10 self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false self.showpatrolpointmarks = false
self.EngageTargetTypes = {"Air"} self.EngageTargetTypes = {"Air"}
self:SetDefaultTurnoverTime()
-- Set some string id for output to DCS.log file.
self.lid=string.format("EASYGCICAP %s | ", self.alias)
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
self:SetStartState("Stopped") self:SetStartState("Stopped")
@@ -350,6 +364,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self:__Start(math.random(6,12)) self:__Start(math.random(6,12))
--- On Before "Start" event.
-- @function [parent=#EASYGCICAP] OnBeforeStart
-- @param #EASYGCICAP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "Start" event.
-- @function [parent=#EASYGCICAP] OnAfterStart
-- @param #EASYGCICAP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On Before "Status" event.
-- @function [parent=#EASYGCICAP] OnBeforeStatus
-- @param #EASYGCICAP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "Status" event.
-- @function [parent=#EASYGCICAP] OnAfterStatus
-- @param #EASYGCICAP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
return self return self
end end
@@ -369,6 +411,28 @@ function EASYGCICAP:GetAirwing(AirbaseName)
return nil return nil
end end
--- Add an agent to the underlying INTEL detection - caution, we need to be started first for this to work!
-- Normally this isn't necessary when the Group name is correctly filled (see EWRName in `New()`).
-- @param #EASYGCICAP self
-- @param Wrapper.Group#GROUP Group The group object to be added as Intel Agent.
-- @return #EASYGCICAP self
function EASYGCICAP:AddAgent(Group)
self:T(self.lid.."AddAgent")
if Group:IsInstanceOf("GROUP") and self.Intel ~= nil then
self.Intel:AddAgent(Group)
if self.TankerInvisible == true then
Group:SetCommandInvisible(true)
Group:OptionROEHoldFire()
if Group:IsAir() then
Group:OptionROTEvadeFire()
else
Group:OptionDisperseOnAttack(30)
end
end
end
return self
end
--- Get a table of all managed AirWings --- Get a table of all managed AirWings
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @return #table Table of Ops.AirWing#AIRWING Airwings -- @return #table Table of Ops.AirWing#AIRWING Airwings
@@ -548,6 +612,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range)
return self return self
end end
--- Set default turnover times for squadrons in minutes
-- @param #EASYGCICAP self
-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 5 min.
-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 10 min.
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultTurnoverTime(MaintenanceTime,RepairTime)
self:T(self.lid.."SetDefaultTurnoverTime")
self.maintenancetime=MaintenanceTime or 5
self.repairtime=RepairTime or 10
return self
end
--- Set default number of airframes standing by for intercept tasks (visible on the airfield) --- Set default number of airframes standing by for intercept tasks (visible on the airfield)
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #number Airframes defaults to 2 -- @param #number Airframes defaults to 2
@@ -611,7 +687,6 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End)
return self return self
end end
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"} --- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"}, -- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"},
@@ -656,7 +731,7 @@ function EASYGCICAP:_CreateAirwings()
return self return self
end end
--- (internal) Create and add another AirWing to the manager --- (Internal) Create and add another AirWing to the manager
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #string Airbasename -- @param #string Airbasename
-- @param #string Alias -- @param #string Alias
@@ -763,8 +838,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
end end
end end
if self.noalert5 > 0 then if self.noalert5 > 0 then
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) local alert
if self.ClassName == "EASYGCICAP" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
elseif self.ClassName == "EASYA2G" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
end
alert:SetRequiredAssets(self.noalert5) alert:SetRequiredAssets(self.noalert5)
alert:SetRepeat(99) alert:SetRepeat(99)
CAP_Wing:AddMission(alert) CAP_Wing:AddMission(alert)
@@ -1153,7 +1233,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5}) Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5})
--Squadron_One:SetFuelLowRefuel(true) --Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3) Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20) Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex) Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery) Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
@@ -1184,7 +1264,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF
Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON}) Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON})
--Squadron_One:SetFuelLowRefuel(true) --Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3) Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20) Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex) Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery) Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
@@ -1218,7 +1298,7 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air
Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER}) Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER})
--Squadron_One:SetFuelLowRefuel(true) --Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3) Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20) Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex) Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery) Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
@@ -1255,7 +1335,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF
Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS}) Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS})
--Squadron_One:SetFuelLowRefuel(true) --Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3) Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20) Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex) Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery) Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
@@ -1300,6 +1380,54 @@ function EASYGCICAP:AddConflictZone(Zone)
return self return self
end end
--- Function to set corridor zones.
-- @param #EASYGCICAP self
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
-- @return #EASYGCICAP self
function EASYGCICAP:SetCorridorZones(CorridorZones)
self:T(self.lid .. "SetCorridorZones")
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
self.corridorzones = CorridorZones
self.usecorridors = true
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
self.corridorzones:AddZone(CorridorZones)
self.usecorridors = true
end
return self
end
--- Function to add one corridor zone.
-- @param #EASYGCICAP self
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
-- @return #EASYGCICAP self
function EASYGCICAP:AddCorridorZone(CorridorZone)
self:T(self.lid .. "AddCorridorZone")
self:SetCorridorZones(CorridorZone)
return self
end
--- Function to set corridor zone floor and ceiling in FEET.
-- @param #EASYGCICAP self
-- @param #number Floor Floor altitude ASL in feet.
-- @param #number Ceiling Ceiling altitude ASL in feet.
-- @return #EASYGCICAP self
function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
self.corridorfloor = UTILS.FeetToMeters(Floor)
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
return self
end
--- Function to set corridor zone floor and ceiling in METERS.
-- @param #EASYGCICAP self
-- @param #number Floor Floor altitude ASL in meters.
-- @param #number Ceiling Ceiling altitude ASL in meters.
-- @return #EASYGCICAP self
function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
self.corridorfloor = Floor
self.corridorceiling = Ceiling
return self
end
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready. --- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
@@ -1459,6 +1587,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
success = false success = false
end end
else
success = true
end end
return success return success
end, end,
@@ -1468,6 +1598,14 @@ function EASYGCICAP:_AssignIntercept(Cluster)
) )
end end
InterceptAuftrag:AddConditionFailure(
function()
local failure = false
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
return failure
end
)
table.insert(self.ListOfAuftrag,InterceptAuftrag) table.insert(self.ListOfAuftrag,InterceptAuftrag)
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
if not assigned then if not assigned then
@@ -1500,6 +1638,14 @@ function EASYGCICAP:_StartIntel()
BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet)
BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet)
BlueIntel:SetVerbosity(0) BlueIntel:SetVerbosity(0)
if self.usecorridors == true then
BlueIntel:SetCorridorZones(self.corridorzones)
if self.corridorfloor or self.corridorceiling then
BlueIntel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
BlueIntel:Start() BlueIntel:Start()
if self.debug then if self.debug then
@@ -1603,7 +1749,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
assets = assets + count assets = assets + count
instock = instock + count2 instock = instock + count2
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5, AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
-- update ready groups -- update ready groups
self.ReadyFlightGroups = nil self.ReadyFlightGroups = nil
self.ReadyFlightGroups = {} self.ReadyFlightGroups = {}
@@ -1614,7 +1760,8 @@ function EASYGCICAP:onafterStatus(From,Event,To)
local name = FG:GetName() local name = FG:GetName()
local engage = FG:IsEngaging() local engage = FG:IsEngaging()
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne() local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
--self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready))) --self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
if ready then if ready then
self.ReadyFlightGroups[name] = FG self.ReadyFlightGroups[name] = FG
+172 -10
View File
@@ -27,6 +27,9 @@
-- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered. -- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered.
-- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones. -- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones.
-- @field Core.Set#SET_ZONE conflictzoneset Set of conflict zones. Contacts in these zones are considered, even if they are not in accept zones or if they are in reject zones. -- @field Core.Set#SET_ZONE conflictzoneset Set of conflict zones. Contacts in these zones are considered, even if they are not in accept zones or if they are in reject zones.
-- @field Core.Set#SET_ZONE corridorzoneset Set of corridor zones. Contacts in these zones are never considered. Also see corridorfloorheight and corridorfloorceiling.
-- @field #number corridorfloor [Air] Contacts below this height (ASL!) are considered, even if they are in a corridor zone.
-- @field #number corridorceiling [Air] Contacts above this height (ASL!) are considered, even if they are in a corridor zone.
-- @field #table Contacts Table of detected items. -- @field #table Contacts Table of detected items.
-- @field #table ContactsLost Table of lost detected items. -- @field #table ContactsLost Table of lost detected items.
-- @field #table ContactsUnknown Table of new detected items. -- @field #table ContactsUnknown Table of new detected items.
@@ -39,6 +42,9 @@
-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @field #number prediction Seconds default to be used with CalcClusterFuturePosition.
-- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`. -- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`.
-- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative. -- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative.
-- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity).
-- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters.
-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.HELICOPTER}
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Top Secret! --- Top Secret!
@@ -102,6 +108,9 @@ INTEL = {
clusterarrows = false, clusterarrows = false,
prediction = 300, prediction = 300,
detectStatics = false, detectStatics = false,
DetectAccoustic = false,
DetectAccousticRadius = 1000,
DetectAccousticUnitTypes = {Unit.Category.HELICOPTER},
} }
--- Detected item info. --- Detected item info.
@@ -160,14 +169,14 @@ INTEL.Ctype={
--- INTEL class version. --- INTEL class version.
-- @field #string version -- @field #string version
INTEL.version="0.3.6" INTEL.version="0.3.10"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add min cluster size. Only create new clusters if they have a certain group size. -- TODO: Add min cluster size. Only create new clusters if they have a certain group size.
-- TODO: process detected set asynchroniously for better performance. -- NODO: process detected set asynchroniously for better performance.
-- DONE: Add statics. -- DONE: Add statics.
-- DONE: Filter detection methods. -- DONE: Filter detection methods.
-- DONE: Accept zones. -- DONE: Accept zones.
@@ -175,6 +184,7 @@ INTEL.version="0.3.6"
-- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone. -- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone.
-- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?! -- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?!
-- DONE: Combine units to groups for all, new and lost. -- DONE: Combine units to groups for all, new and lost.
-- DONE: Add corridor zones.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor -- Constructor
@@ -266,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
self:SetForgetTime() self:SetForgetTime()
self:SetAcceptZones() self:SetAcceptZones()
self:SetRejectZones() self:SetRejectZones()
self:SetCorridorZones()
self:SetConflictZones() self:SetConflictZones()
------------------------ ------------------------
@@ -398,6 +409,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet)
return self return self
end end
--- Set to accept accoustic detection.
-- @param #INTEL self
-- @param #number Radius Radius in which we can "hear" units. Defaults to 1000 meters.
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}`
-- @return #INTEL self
function INTEL:SetAccousticDetectionOn(Radius,UnitCategories)
self.DetectAccoustic = true
self.DetectAccousticRadius = Radius or 1000
self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.HELICOPTER}
return self
end
--- Switch off accoustic detection.
-- @param #INTEL self
-- @return #INTEL self
function INTEL:SetAccousticDetectionOff()
self.DetectAccoustic = false
return self
end
--- Add an accept zone. Only contacts detected in this zone are considered. --- Add an accept zone. Only contacts detected in this zone are considered.
-- @param #INTEL self -- @param #INTEL self
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
@@ -475,7 +506,69 @@ function INTEL:RemoveConflictZone(ConflictZone)
return self return self
end end
--- **OBSOLETE, will be removed in next version!** Set forget contacts time interval. --- Set corrdidor zones. Contacts detected in this/these zone(s) are never reported by the detection.
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
-- @param #INTEL self
-- @param Core.Set#SET_ZONE CorridorZoneSet Set of corrdidor zone(s).
-- @return #INTEL self
function INTEL:SetCorridorZones(CorridorZoneSet)
self.corridorzoneset=CorridorZoneSet or SET_ZONE:New()
return self
end
--- Add a corrdidor zone. Contacts detected in this zone are corrdidored and not reported by the detection.
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
-- @param #INTEL self
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corrdidor zone set.
-- @return #INTEL self
function INTEL:AddCorridorZone(CorridorZone)
self.corridorzoneset:AddZone(CorridorZone)
return self
end
--- Remove a corrdidor zone from the corrdidor zone set.
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
-- @param #INTEL self
-- @param Core.Zone#ZONE CorridorZone Remove a zone from the corrdidor zone set.
-- @return #INTEL self
function INTEL:RemoveCorridorZone(CorridorZone)
self.corridorzoneset:Remove(CorridorZone:GetName(), true)
return self
end
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
-- Overrides corridor exception for objects flying outside this limitations.
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
-- `mycorridorzone:SetProperty("CorridorFloor",500)` -- meters, case sensitivity matters!
-- `mycorridorzone:SetProperty("CorridorCeiling",10000)` -- meters, case sensitivity matters!
-- @param #INTEL self
-- @param #number Floor Floor altitude in meters.
-- @param #number Ceiling Ceiling altitude in meters.
-- @return #INTEL self
function INTEL:SetCorridorLimits(Floor,Ceiling)
self.corridorceiling = Ceiling or 10000
self.corridorfloor = Floor or 1
return self
end
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
-- Overrides corridor exception for objects flying outside this limitations.
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
-- `mycorridorzone:SetProperty("CorridorFloor",UTILS.FeetToMeters(5000))` -- feet, case sensitivity matters!
-- `mycorridorzone:SetProperty("CorridorCeiling",UTILS.FeetToMeters(20000))` -- feet, case sensitivity matters!
-- @param #INTEL self
-- @param #number Floor Floor altitude in feet.
-- @param #number Ceiling Ceiling altitude in feet.
-- @return #INTEL self
function INTEL:SetCorridorLimitsFeet(Floor,Ceiling)
local Ceiling = Ceiling or 25000
local Floor = Floor or 15000
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
self.corridorfloor = UTILS.FeetToMeters(Floor)
return self
end
--- **OBSOLETE, not functional!** Set forget contacts time interval.
-- Previously known contacts that are not detected any more, are "lost" after this time. -- Previously known contacts that are not detected any more, are "lost" after this time.
-- This avoids fast oscillations between a contact being detected and undetected. -- This avoids fast oscillations between a contact being detected and undetected.
-- @param #INTEL self -- @param #INTEL self
@@ -831,6 +924,18 @@ function INTEL:UpdateIntel()
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK)
end end
if self.DetectAccoustic then
local recce = group:GetFirstUnitAlive()
local detectionzone = group:GetProperty("INTEL_DETECT_ACCZONE")
if not detectionzone then
detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000)
group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone)
end
if recce and recce:IsGround() then
self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone)
end
end
end end
end end
@@ -884,6 +989,36 @@ function INTEL:UpdateIntel()
table.insert(remove, unitname) table.insert(remove, unitname)
end end
end end
-- Check if unit is in any of the corridor zones.
if self.corridorzoneset:Count()>0 then
self:T("Corridorzone Check for unit "..unit:GetName())
local inzone = false
for _,_zone in pairs(self.corridorzoneset.Set) do
local zone=_zone --Core.Zone#ZONE
if unit:IsInZone(zone) then
local corridorfloor = zone:GetProperty("CorridorFloor") or self.corridorfloor
local corridorceiling = zone:GetProperty("CorridorCeiling") or self.corridorceiling
local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n"
debugtext = debugtext .. string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())),
tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling)))
MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1)
if unit:IsAir() and (corridorfloor ~= nil or corridorceiling ~= nil) then
local alt = unit:GetAltitude()
if corridorfloor and alt > corridorfloor then inzone = true end
if corridorceiling and (inzone == true or corridorfloor == nil) and alt < corridorceiling then inzone = true else inzone = false end
if inzone == true then break end
else
inzone=true
break
end
end
end
-- Unit is inside a corridor zone ==> remove!
if inzone then
table.insert(remove, unitname)
end
end
-- Filter unit categories. Added check that we have a UNIT and not a STATIC object because :GetUnitCategory() is only available for units. -- Filter unit categories. Added check that we have a UNIT and not a STATIC object because :GetUnitCategory() is only available for units.
if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then
@@ -1040,7 +1175,7 @@ function INTEL:_CreateContact(Positionable, RecceName)
item.category=3 --static:GetCategory() item.category=3 --static:GetCategory()
item.categoryname=static:GetCategoryName() or "Unknown" item.categoryname=static:GetCategoryName() or "Unknown"
item.threatlevel=static:GetThreatLevel() or 0 item.threatlevel=static:GetThreatLevel() or 0
item.position=static:GetCoordinate() item.position=static:GetCoord()
item.velocity=static:GetVelocityVec3() item.velocity=static:GetVelocityVec3()
item.speed=0 item.speed=0
item.recce=RecceName item.recce=RecceName
@@ -1106,7 +1241,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti
return self return self
end end
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}. --- (Internal) Return the detected target groups of the controllable as a table.
-- The optional parameters specify the detection methods that can be applied. -- The optional parameters 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.
-- @param #INTEL self -- @param #INTEL self
@@ -1147,15 +1282,15 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
local DetectionAccepted = true local DetectionAccepted = true
if self.RadarAcceptRange then if self.RadarAcceptRange then
local reccecoord = Unit:GetCoordinate() local reccecoord = Unit:GetCoord()
local coord = unit:GetCoordinate() local coord = unit:GetCoord()
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
end end
if self.RadarBlur then if self.RadarBlur then
local reccecoord = Unit:GetCoordinate() local reccecoord = Unit:GetCoord()
local coord = unit:GetCoordinate() local coord = unit:GetCoord()
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
local AGL = unit:GetAltitude(true) local AGL = unit:GetAltitude(true)
local minheight = self.RadarBlurMinHeight or 250 -- meters local minheight = self.RadarBlurMinHeight or 250 -- meters
@@ -1202,6 +1337,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
end end
end end
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}.
-- @param #INTEL self
-- @param Wrapper.Unit#UNIT Recce The unit detecting.
-- @param #table DetectedUnits Table of detected units to be filled.
-- @param #table RecceDetecting Table of recce per unit to be filled.
-- @param Core.Zone#ZONE_GROUP detectionzone The zone where to look.
function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone)
local othercoalition = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
self:T("Other coalition = "..othercoalition)
if detectionzone then
-- Get detected units
local reccename = Recce:GetName()
local DetectAccousticUnitTypes = self.DetectAccousticUnitTypes or {Unit.Category.HELICOPTER}
detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes)
local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT
self:T("Accoustic detection found #Units "..unitset:CountAlive())
for _,_unit in pairs(unitset.Set or {}) do
if _unit and _unit:IsAlive() and _unit:GetCoalition() ~= self.coalition then
local name = _unit:GetName() or "none"
DetectedUnits[name]=_unit
RecceDetecting[name]=reccename
self:T("Unit name = "..name)
end
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Events -- FSM Events
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -2144,7 +2306,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update)
return Cluster.coordinate return Cluster.coordinate
end end
--- Check if the coorindate of the cluster changed. --- Check if the coordindate of the cluster changed.
-- @param #INTEL self -- @param #INTEL self
-- @param #INTEL.Cluster Cluster The cluster. -- @param #INTEL.Cluster Cluster The cluster.
-- @param #number Threshold in meters. Default 100 m. -- @param #number Threshold in meters. Default 100 m.
+379 -36
View File
@@ -21,7 +21,7 @@
-- === -- ===
-- @module Ops.PlayerTask -- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg -- @image OPS_PlayerTask.jpg
-- @date Last Update Oct 2025 -- @date Last Update Dec 2025
do do
@@ -61,6 +61,7 @@ do
-- @field #number PreviousCount -- @field #number PreviousCount
-- @field #boolean CanSmoke -- @field #boolean CanSmoke
-- @field #boolean ShowThreatDetails -- @field #boolean ShowThreatDetails
-- @field #boolean PersistMe
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -98,11 +99,12 @@ PLAYERTASK = {
PreviousCount = 0, PreviousCount = 0,
CanSmoke = true, CanSmoke = true,
ShowThreatDetails = true, ShowThreatDetails = true,
PersistMe = false,
} }
--- PLAYERTASK class version. --- PLAYERTASK class version.
-- @field #string version -- @field #string version
PLAYERTASK.version="0.1.29" PLAYERTASK.version="0.1.31"
--- Generic task condition. --- Generic task condition.
-- @type PLAYERTASK.Condition -- @type PLAYERTASK.Condition
@@ -235,7 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
-- @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.
-- @param #boolen Silent If true, suppress message output on cancel. -- @param #boolean Silent If true, suppress message output on cancel.
--- On After "Planned" event. Task has been planned. --- On After "Planned" event. Task has been planned.
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned -- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
@@ -401,8 +403,17 @@ function PLAYERTASK:CanJoinTask(Group, Client)
return true return true
end end
--- [User] Set this task for persistance, if persistance is enabled on the PLAYERTASKCONTROLLER instance.
-- @param #PLAYERTASK self
-- @return #PLAYERTASK self
function PLAYERTASK:EnablePersistance()
self.PersistMe = true
return self
end
--- [Internal] Add a PLAYERTASKCONTROLLER for this task --- [Internal] Add a PLAYERTASKCONTROLLER for this task
-- @param #PLAYERTASK self -- @param #PLAYERTASK self
--
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller -- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
-- @return #PLAYERTASK self -- @return #PLAYERTASK self
function PLAYERTASK:_SetController(Controller) function PLAYERTASK:_SetController(Controller)
@@ -1088,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
return self return self
end end
--- [Internal] On after progress call --- [Internal] On after progress call
-- @param #PLAYERTASK self -- @param #PLAYERTASK self
-- @param #string From -- @param #string From
@@ -1363,6 +1373,12 @@ do
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask -- @field Core.ClientMenu#CLIENTMENU MenuNoTask
-- @field #boolean InformationMenu Show Radio Info Menu -- @field #boolean InformationMenu Show Radio Info Menu
-- @field #number TaskInfoDuration How long to show the briefing info on the screen -- @field #number TaskInfoDuration How long to show the briefing info on the screen
-- @field #table TaskPersistance Table for persistance data
-- @field #boolean TaskPersistanceSwitch Switch for persisting tasks
-- @field #string TaskPersistancePath File path for persisting tasks
-- @field #string TaskPersistanceFilename File name for persisting tasks
-- @field #table TasksPersistable List of persistable tasks
-- @field #number SceneryExplosivesAmount Kgs of TNT to explode scenery on task persistance loading
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- ---
@@ -1583,6 +1599,7 @@ do
-- ELEVATION = "\nTarget Elevation: %s %s", -- ELEVATION = "\nTarget Elevation: %s %s",
-- METER = "meter", -- METER = "meter",
-- FEET = "feet", -- FEET = "feet",
-- INTERCEPTCOURSE = "Intercept course",
-- }, -- },
-- --
-- e.g. -- e.g.
@@ -1669,8 +1686,28 @@ do
-- --
-- Set a marker on the map and add the following text to create targets from it: "TARGET". This is effectively the same as adding a COORDINATE object as target. -- Set a marker on the map and add the following text to create targets from it: "TARGET". This is effectively the same as adding a COORDINATE object as target.
-- The marker can be deleted any time. -- The marker can be deleted any time.
-- --
-- ## 9 Discussion -- ## 9 Single Task Persistence for mission designer added tasks
--
-- The class can persist the state of single tasks of type BOMBING, PRECISIONBOMBING, ARTY and SEAD, i.e. tasks which have a GROUND(!) GROUP, UNIT, STATIC or SCENERY as target.
-- This requires the task to have a unique(!) menu name set, a TARGET which already exists on the map at mission start(!), and a flag that this task is actually to be persisted.
-- Also, you need to desanitize the mission scripting environment, i.e. "lfs" and "io" must be available so we can write to disk.
--
-- -- First, we need to enable on the PLAYERTASKCONTROLLER itself
-- taskmanager:EnableTaskPersistance([[C:\Users\myname\Saved Games\DCS\Missions\MyMisionFolder\]],"Mission Tasks.csv") -- Path and Filename
--
-- -- Then, we can design a task marking mission progress that we want to persist
-- local RussianRadios = SET_STATIC:New():FilterPrefixes("Comms Tower Russia"):FilterOnce()
--
-- local RadioTask = PLAYERTASK:New(AUFTRAG.Type.BOMBING,RussianRadios,true,5,"Bombing")
-- RadioTask:SetMenuName("Neutralize Comms Towers") -- UNIQUE menu name so we can find the task later!
-- RadioTask:AddFreetext("Find and neutralize the two communication towers near NB70 East of Fulda on Streufelsberg!")
-- RadioTask:AddFreetextTTS("Find and neutralize the two communication towers naer N;B;7;zero; East of Fulda on Streufelsberg!")
-- RadioTask:EnablePersistance() -- Enable persistence for this task
--
-- taskmanager:AddPlayerTaskToQueue(RadioTask,true,false)
--
-- ## 10 Discussion
-- --
-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-playertask channel. -- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-playertask channel.
-- --
@@ -1720,6 +1757,12 @@ PLAYERTASKCONTROLLER = {
MenuNoTask = nil, MenuNoTask = nil,
InformationMenu = false, InformationMenu = false,
TaskInfoDuration = 30, TaskInfoDuration = 30,
TaskPersistance = {},
TaskPersistanceSwitch = false,
TaskPersistancePath = nil,
TaskPersistanceFilename = nil,
TasksPersistable = {},
SceneryExplosivesAmount = 300,
} }
--- ---
@@ -1740,6 +1783,7 @@ AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing"
AUFTRAG.Type.CTLD = "Combat Transport" AUFTRAG.Type.CTLD = "Combat Transport"
AUFTRAG.Type.CSAR = "Combat Rescue" AUFTRAG.Type.CSAR = "Combat Rescue"
AUFTRAG.Type.CONQUER = "Conquer" AUFTRAG.Type.CONQUER = "Conquer"
--- ---
-- @type Scores -- @type Scores
PLAYERTASKCONTROLLER.Scores = { PLAYERTASKCONTROLLER.Scores = {
@@ -1759,6 +1803,24 @@ PLAYERTASKCONTROLLER.Scores = {
[AUFTRAG.Type.CAP] = 100, [AUFTRAG.Type.CAP] = 100,
[AUFTRAG.Type.CAPTUREZONE] = 100, [AUFTRAG.Type.CAPTUREZONE] = 100,
} }
---
-- @type TasksPersistable
PLAYERTASKCONTROLLER.TasksPersistable = {
[AUFTRAG.Type.PRECISIONBOMBING] = true,
[AUFTRAG.Type.BOMBING] = true,
[AUFTRAG.Type.ARTY] = true,
[AUFTRAG.Type.SEAD] = true,
}
---
-- @type PersistenceData
-- @field #number ID
-- @field #string Name
-- @field #string Type
-- @field #number InitialTargets
-- @field #number Targetsleft
-- @field #boolean updated
--- ---
-- @type SeadAttributes -- @type SeadAttributes
@@ -1860,6 +1922,7 @@ PLAYERTASKCONTROLLER.Messages = {
DESTROYER = "Destroyer", DESTROYER = "Destroyer",
CARRIER = "Aircraft Carrier", CARRIER = "Aircraft Carrier",
RADIOS = "Radios", RADIOS = "Radios",
INTERCEPTCOURSE = "Intercept course",
}, },
DE = { DE = {
TASKABORT = "Auftrag abgebrochen!", TASKABORT = "Auftrag abgebrochen!",
@@ -1947,12 +2010,13 @@ PLAYERTASKCONTROLLER.Messages = {
DESTROYER = "Zerstörer", DESTROYER = "Zerstörer",
CARRIER = "Flugzeugträger", CARRIER = "Flugzeugträger",
RADIOS = "Frequenzen", RADIOS = "Frequenzen",
INTERCEPTCOURSE = "Abfangkurs",
}, },
} }
--- PLAYERTASK class version. --- PLAYERTASK class version.
-- @field #string version -- @field #string version
PLAYERTASKCONTROLLER.version="0.1.71" PLAYERTASKCONTROLLER.version="0.1.73"
--- Create and run a new TASKCONTROLLER instance. --- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
@@ -2059,7 +2123,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
self:AddTransition("*", "Stop", "Stopped") self:AddTransition("*", "Stop", "Stopped")
self:__Start(2) self:__Start(2)
local starttime = math.random(5,10) local starttime = math.random(10,15)
self:__Status(starttime) self:__Status(starttime)
self:I(self.lid..self.version.." Started.") self:I(self.lid..self.version.." Started.")
@@ -2174,6 +2238,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
end end
--- [User] Enable Task persistance (for specific gound target tasks)
-- @param #PLAYERTASKCONTROLLER self
-- @param #string Path Path where to save the task data
-- @param #string Filename File name under which to save the task data
-- @param #number KgsOfTNT (Optional) Explosives kgs used to remove scenery for persistence, defaults to 300
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT)
self.TaskPersistanceSwitch = true
self.TaskPersistancePath = Path
self.TaskPersistanceFilename = Filename
self.SceneryExplosivesAmount = KgsOfTNT or 300
return self
end
--- [User] Disable Task persistance (for specific gound target tasks)
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:DisableTaskPersistance()
self.TaskPersistanceSwitch = false
self.TaskPersistancePath = nil
self.TaskPersistanceFilename = nil
return self
end
--- [User] Set or create a SCORING object for this taskcontroller --- [User] Set or create a SCORING object for this taskcontroller
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object -- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
@@ -2861,15 +2949,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
end end
end end
--[[
for _type,_data in pairs(tasktypes) do
self:I("Task Type: ".._type)
for _id,_task in pairs(_data) do
self:I("Task Name: ".._task.Target:GetName())
end
end
--]]
return tasktypes return tasktypes
end end
@@ -3566,9 +3645,9 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
PlayerTask:_SetController(self) PlayerTask:_SetController(self)
PlayerTask:SetCoalition(self.Coalition) PlayerTask:SetCoalition(self.Coalition)
self.TaskQueue:Push(PlayerTask) self.TaskQueue:Push(PlayerTask)
if not Silent then --if not Silent then
self:__TaskAdded(10,PlayerTask) self:__TaskAdded(10,PlayerTask,Silent)
end --end
else else
self:E(self.lid.."***** NO valid PAYERTASK object sent!") self:E(self.lid.."***** NO valid PAYERTASK object sent!")
end end
@@ -3700,6 +3779,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client)
return self return self
end end
--- Calculate group future position after given seconds.
-- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Group#GROUP group The group to calculate for.
-- @param #number seconds Time interval in seconds. Default is `self.prediction`.
-- @return Core.Point#COORDINATE Calculated future position of the cluster.
function PLAYERTASKCONTROLLER:_CalcGroupFuturePosition(group, seconds)
-- Get current position of the cluster.
local p=group:GetCoordinate()
-- Velocity vector in m/s.
local v=group:GetVelocityVec3()
-- Time in seconds.
local t=seconds or self.prediction
-- Extrapolated vec3.
local Vec3={x=p.x+v.x*t, y=p.y+v.y*t, z=p.z+v.z*t}
-- Future position.
local futureposition=COORDINATE:NewFromVec3(Vec3)
-- Create an arrow pointing in the direction of the movement.
if self.verbose == true then
local markerID = group:GetProperty("PLAYERTASK_ARROW")
if markerID then
COORDINATE:RemoveMark(markerID)
end
markerID = p:ArrowToAll(futureposition, self.coalition, {1,0,0}, 1, {1,1,0}, 0.5, 2, true, "Position Calc")
group:SetProperty("PLAYERTASK_ARROW",markerID)
end
return futureposition
end
--- [Internal] Flashing directional info for a client --- [Internal] Flashing directional info for a client
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
@@ -3709,16 +3823,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo()
if _client and _client:IsAlive() then if _client and _client:IsAlive() then
if self.TasksPerPlayer:HasUniqueID(_playername) then if self.TasksPerPlayer:HasUniqueID(_playername) then
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
local Coordinate = task.Target:GetCoordinate() local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE
local CoordText = "" local CoordText = ""
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic) CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic)
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
local text = targettxt..": "..CoordText
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
else else
CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic) CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic)
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
local text = targettxt..": "..CoordText
-- calc intercept position
local name=task.Target:GetName()
local group = GROUP:FindByName(name)
local clientcoord = _client:GetCoordinate()
if group and clientcoord and group:IsAlive() and task.Type==AUFTRAG.Type.INTERCEPT then
local speed = math.max(UTILS.KnotsToMps(350) or _client:GetVelocityMPS())
local dist = Coordinate:Get3DDistance(clientcoord)
local iTime = math.floor(dist/speed)+5
if iTime < 10 then iTime = 10
elseif iTime > 600 then iTime = 600 end
local npos = self:_CalcGroupFuturePosition(group,iTime)
local BR = npos:ToStringBearing(clientcoord,nil,self.ShowMagnetic,0 )
local Intercepttext = self.gettext:GetEntry("INTERCEPTCOURSE",self.locale)
text = text .. "\n"..Intercepttext.." "..BR
end
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
end end
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
local text = "Target: "..CoordText
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
end end
end end
end end
@@ -3770,7 +3902,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local CoordTextLLDM = nil local CoordTextLLDM = nil
local ShowThreatInfo = task.ShowThreatDetails local ShowThreatInfo = task.ShowThreatDetails
local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
else else
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
@@ -3807,8 +3939,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
Elevation = math.floor(UTILS.MetersToFeet(Elevation)) Elevation = math.floor(UTILS.MetersToFeet(Elevation))
end end
-- ELEVATION = "\nTarget Elevation: %s %s", -- ELEVATION = "\nTarget Elevation: %s %s",
local elev = self.gettext:GetEntry("ELEVATION",self.locale) if task.Type ~= AUFTRAG.Type.INTERCEPT then
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) local elev = self.gettext:GetEntry("ELEVATION",self.locale)
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
end
-- Prec bombing -- Prec bombing
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if LasingDrone and LasingDrone.playertask then if LasingDrone and LasingDrone.playertask then
@@ -4584,6 +4718,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
return self return self
end end
--- [User] Add an corridor zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corridor zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone)
self:T(self.lid.."AddCorridorZone")
if self.Intel then
self.Intel:AddCorridorZone(CorridorZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Add an corridor SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Set#SET_ZONE CorridorZoneSet Add a SET_ZONE to the corridor zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet)
self:T(self.lid.."AddCorridorZoneSet")
if self.Intel then
self.Intel.corridorzoneset:AddSet(CorridorZoneSet)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Remove an corridor zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE CorridorZone Remove this zone from the corridor zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone)
self:T(self.lid.."RemoveCorridorZone")
if self.Intel then
self.Intel:RemoveCorridorZone(CorridorZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- Function to set corridor zone floor and ceiling in FEET.
-- @param #PLAYERTASKCONTROLLER self
-- @param #number Floor Floor altitude ASL in feet.
-- @param #number Ceiling Ceiling altitude ASL in feet.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
if self.Intel then
self.Intel:SetCorridorLimitsFeet(Floor,Ceiling)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- Function to set corridor zone floor and ceiling in METERS.
-- @param #PLAYERTASKCONTROLLER self
-- @param #number Floor Floor altitude ASL in meters.
-- @param #number Ceiling Ceiling altitude ASL in meters.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
if self.Intel then
self.Intel:SetCorridorLimits(Floor,Ceiling)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Set the top menu name to a custom string. --- [User] Set the top menu name to a custom string.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param #string Name The name to use as the top menu designation. -- @param #string Name The name to use as the top menu designation.
@@ -4765,6 +4969,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
return self return self
end end
---
-- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerTask#PLASERTASK Task
-- @param #number TargetsLeft
function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft)
self:T(self.lid.."_UpdateTargetsAlive")
local delta = Task.Target:CountTargets() - TargetsLeft
if delta > 0 then
self:T("Delta targets to be removed: "..delta)
local count = 0
local targets = Task.Target:GetObjects()
for _,_object in pairs(targets or {}) do
if _object and _object.ClassName and (_object:IsInstanceOf("GROUP") or _object:IsInstanceOf("UNIT") or _object:IsInstanceOf("STATIC") or _object:IsInstanceOf("SCENERY")) then
if count < delta then
count = count + 1
if not _object:IsInstanceOf("SCENERY") then
_object:Destroy(true)
else
_object:Explode(self.SceneryExplosivesAmount)
end
end
end
end
end
return self
end
---
-- @param #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_LoadTasksPersisted()
self:T(self.lid.."_LoadTasksPersisted")
local function MatchTask(Type,Name)
local foundtask
self.TaskQueue:ForEach(
function(_task)
local task = _task -- #PLAYERTASK
if task.Type == Type and task.Target.name and task.Target.name == Name then
foundtask = task
end
end
)
return foundtask
end
if lfs and io then
local ok,data = UTILS.LoadFromFile(self.TaskPersistancePath,self.TaskPersistanceFilename)
if ok == true then
table.remove(data, 1)
for _,_entry in pairs(data) do
-- "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
local dataset = UTILS.Split(_entry,";;")
local Taskdata = {} -- #PersistenceData
Taskdata.ID = tonumber(dataset[1])
Taskdata.Name = tostring(dataset[2])
Taskdata.InitialTargets = tonumber(dataset[3])
Taskdata.Targetsleft = tonumber(dataset[4])
Taskdata.Type = tostring(dataset[5])
Taskdata.Task = MatchTask(Taskdata.Type,Taskdata.Name)
if Taskdata.Task == nil then
self:E(self.lid.."No actual task found for "..Taskdata.Name)
else
self:T(self.lid.."Task loaded and match found for "..Taskdata.Name)
end
Taskdata.updated = Taskdata.InitialTargets == Taskdata.Targetsleft and true or false
if Taskdata.Task and Taskdata.updated == false then
self:_UpdateTargetsAlive(Taskdata.Task,Taskdata.Targetsleft)
Taskdata.updated = true
end
self.TaskPersistance[Taskdata.ID] = Taskdata
end
end
end
return self
end
--- [User] Clear persisted data on disk.
-- @param #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:ClearPersistedData()
if lfs and io then
local text = "-- Data Cleared\n"
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
end
return self
end
---
-- @param #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_SaveTasksPersisted()
if lfs and io then
local text = "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
for _,_data in pairs(self.TaskPersistance) do
local data = _data -- #PersistenceData
data.Targetsleft = data.Task.Target:CountTargets() -- recount
if data.Task and data.Task:IsDone() then data.Targetsleft = 0 end
local tasktext = string.format("%d;;%s;;%d;;%d;;%s\n",data.ID,data.Name,data.InitialTargets,data.Targetsleft,data.Type)
text = text..tasktext
end
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
end
return self
end
---
-- @param #PLAYERTASKCONTROLLER self
-- @param #PLAYERTASK Task
function PLAYERTASKCONTROLLER:_AddPersistenceData(Task)
local Taskdata = {} -- #PersistenceData
if not self.TaskPersistance[Task.PlayerTaskNr] then
Taskdata.ID = Task.PlayerTaskNr
Taskdata.Name = Task.Target.name or "none"
Taskdata.InitialTargets = Task.Target:CountTargets()
Taskdata.Targetsleft = Taskdata.InitialTargets
Taskdata.Type = Task.Type
Taskdata.updated = true
Taskdata.Task = Task
self.TaskPersistance[Task.PlayerTaskNr] = Taskdata
end
return self
end
------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------
-- FSM Functions PLAYERTASKCONTROLLER -- FSM Functions PLAYERTASKCONTROLLER
-- TODO: FSM Functions PLAYERTASKCONTROLLER -- TODO: FSM Functions PLAYERTASKCONTROLLER
@@ -4788,7 +5114,12 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To)
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.UnitLost, self._EventHandler) self:HandleEvent(EVENTS.UnitLost, self._EventHandler)
self:SetEventPriority(5) self:SetEventPriority(5)
-- Persistence
if self.TaskPersistanceSwitch == true then
self:ScheduleOnce(5,self._LoadTasksPersisted,self)
--self:_LoadTasksPersisted()
end
return self return self
end end
@@ -4828,6 +5159,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
self:I(text) self:I(text)
end end
-- Persistence
if self.TaskPersistanceSwitch == true then
self:_SaveTasksPersisted()
end
if self:GetState() ~= "Stopped" then if self:GetState() ~= "Stopped" then
self:__Status(-30) self:__Status(-30)
end end
@@ -4983,19 +5319,26 @@ end
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @param Ops.PlayerTask#PLAYERTASK Task -- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Silent
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task) function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task, Silent)
self:T({From, Event, To}) self:T({From, Event, To})
self:T(self.lid.."TaskAdded") self:T(self.lid.."TaskAdded")
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale) local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type)) local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
if not self.NoScreenOutput then if not Silent then
self:_SendMessageToClients(taskname,15) if not self.NoScreenOutput then
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) self:_SendMessageToClients(taskname,15)
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
end
if self.UseSRS then
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
end end
if self.UseSRS then self:T(self.lid..string.format("Pers = %s | Type = %s | TypePers = %s | TaskFlag = %s",tostring(self.TaskPersistanceSwitch),tostring(Task.Type),tostring(self.TasksPersistable[Task.Type]),tostring(Task.PersistMe)))
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType)) if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) self:_AddPersistenceData(Task)
end end
return self return self
end end
+34 -4
View File
@@ -310,7 +310,7 @@ _RECOVERYTANKERID=0
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RECOVERYTANKER.version="1.0.10" RECOVERYTANKER.version="1.0.11"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
Spawn:InitRadioCommsOnOff(true) Spawn:InitRadioCommsOnOff(true)
Spawn:InitRadioFrequency(self.RadioFreq) Spawn:InitRadioFrequency(self.RadioFreq)
Spawn:InitRadioModulation(self.RadioModu) Spawn:InitRadioModulation(self.RadioModu)
if self.callsignname and self.callsignnumber then
local grp = GROUP:FindByName(self.tankergroupname)
if grp then
local typename = grp:GetTypeName() or ""
--self:I(self.lid.."Typename: "..typename)
local Name
local enumerator = CALLSIGN.Tanker
if typename == "A6E" then
enumerator = CALLSIGN.Intruder
end
Name = self:_GetCallsignName(self.callsignname,enumerator)
--self:I(self.lid.."CallsignName: "..Name)
Spawn:InitCallSign(self.callsignname,Name,self.callsignnumber,self.callsignnumber)
end
end
Spawn:InitModex(self.modex) Spawn:InitModex(self.modex)
-- Spawn on carrier. -- Spawn on carrier.
@@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
-- Task combo. -- Task combo.
-- Be a tanker or be an AWACS. -- Be a tanker or be an AWACS.
local taskroll = self.tanker:EnRouteTaskTanker() local taskrole = self.tanker:EnRouteTaskTanker()
if self.awacs then if self.awacs then
taskroll=self.tanker:EnRouteTaskAWACS() taskrole=self.tanker:EnRouteTaskAWACS()
end end
--local taskeplrs=self.tanker:TaskEPLRS(true, 2) --local taskeplrs=self.tanker:TaskEPLRS(true, 2)
@@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
local taskroute = self.tanker:TaskRoute(wp) local taskroute = self.tanker:TaskRoute(wp)
-- Note that the order is important here! tasktanker has to come first. Otherwise it does not work. -- Note that the order is important here! tasktanker has to come first. Otherwise it does not work.
local taskcombo = self.tanker:TaskCombo({taskroll, taskroute}) local taskcombo = self.tanker:TaskCombo({taskrole, taskroute})
-- Set task. -- Set task.
self.tanker:SetTask(taskcombo, 1) self.tanker:SetTask(taskcombo, 1)
@@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
end end
--- Get clear name callsign for spawn from enumerator
-- @param #RECOVERYTANKER self
-- @param #number Callsign
-- @param #table Enumerator The table of callsigns, e.g. `CALLSIGN.Tanker`
-- @return #string Name Name or "" if not found
function RECOVERYTANKER:_GetCallsignName(Callsign, Enumerator)
for name, value in pairs(Enumerator or {}) do
if value==Callsign then
return name
end
end
return ""
end
--- A unit crashed or died. --- A unit crashed or died.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param Core.Event#EVENTDATA EventData Event data. -- @param Core.Event#EVENTDATA EventData Event data.
+4 -5
View File
@@ -884,7 +884,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
else else
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
-- Set modex for spawn. -- Set modex for spawn.
Spawn:InitModex(self.modex) Spawn:InitModex(self.modex)
@@ -933,8 +933,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname)) self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname))
return return
end end
end
elseif UsesAliveGroup==false then elseif UsesAliveGroup==false then
-- Spawn at airbase. -- Spawn at airbase.
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable) self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
@@ -948,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
delay=60 delay=60
end end
end
end end
-- Set of group(s) to follow Mother. -- Set of group(s) to follow Mother.
self.followset=SET_GROUP:New() self.followset=SET_GROUP:New()
+303 -1
View File
@@ -515,7 +515,8 @@ ENUMS.ReportingName =
Atlas = "A400", Atlas = "A400",
Lancer = "B1-B", Lancer = "B1-B",
Stratofortress = "B-52H", Stratofortress = "B-52H",
Hercules = "C-130", Herc = "C-130",
Hercules = "C-130J-30",
Super_Hercules = "Hercules", Super_Hercules = "Hercules",
Globemaster = "C-17", Globemaster = "C-17",
Greyhound = "C-2A", Greyhound = "C-2A",
@@ -528,6 +529,7 @@ ENUMS.ReportingName =
Orion = "P-3C", Orion = "P-3C",
Viking = "S-3B", Viking = "S-3B",
Osprey = "V-22", Osprey = "V-22",
Intruder = "A6E",
-- Bomber Rus -- Bomber Rus
Badger = "H6-J", Badger = "H6-J",
Bear_J = "Tu-142", -- also Tu-95 Bear_J = "Tu-142", -- also Tu-95
@@ -1772,6 +1774,297 @@ ENUMS.Storage.weapons.adapters.lau105 = 'weapons.adapters.lau-105'
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.bombs.Mk_82 = 'weapons.bombs.Mk_82' ENUMS.Storage.weapons.bombs.Mk_82 = 'weapons.bombs.Mk_82'
ENUMS.Storage.weapons.adapters.BRU42_LS_SUU25 = 'weapons.adapters.BRU-42_LS_(SUU-25)' ENUMS.Storage.weapons.adapters.BRU42_LS_SUU25 = 'weapons.adapters.BRU-42_LS_(SUU-25)'
ENUMS.Storage.weapons.missiles.Aster_30_Blk_1 = 'weapons.missiles.Aster 30 Blk 1'
ENUMS.Storage.weapons.missiles.Aster_30_Blk_1NT = 'weapons.missiles.Aster 30 Blk 1NT'
ENUMS.Storage.weapons.missiles.Aster_30_Blk_2 = 'weapons.missiles.Aster 30 Blk 2'
ENUMS.Storage.weapons.missiles.SA9M83M = 'weapons.missiles.SA9M83M'
ENUMS.Storage.weapons.gunmounts.C130_M4_Rifle = 'weapons.gunmounts.C130_M4_Rifle'
ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm_ = 'weapons.gunmounts.{C130-M18-Sidearm}'
ENUMS.Storage.weapons.gunmounts.C130_Cargo_Bay_M4 = 'weapons.gunmounts.{C130-Cargo-Bay-M4}'
ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm = 'weapons.gunmounts.C130_M18_Sidearm'
ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_R = 'weapons.droptanks.C130J_Ext_Tank_R'
ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_L = 'weapons.droptanks.C130J_Ext_Tank_L'
ENUMS.Storage.weapons.missiles.SAHQ2 = 'weapons.missiles.SAHQ2'
ENUMS.Storage.weapons.missiles.Strela_2 = 'weapons.missiles.Strela-2'
ENUMS.Storage.weapons.missiles.Strela_2M = 'weapons.missiles.Strela-2M'
ENUMS.Storage.weapons.missiles.Strela_3 = 'weapons.missiles.Strela-3'
ENUMS.Storage.weapons.missiles.SA9M83 = 'weapons.missiles.SA9M83'
ENUMS.Storage.weapons.missiles.SAV601P = 'weapons.missiles.SAV601P'
ENUMS.Storage.weapons.missiles.SA2V759 = 'weapons.missiles.SA2V759'
ENUMS.Storage.weapons.missiles.SA9M317 = 'weapons.missiles.SA9M317'
ENUMS.Storage.weapons.missiles.SA9M82M = 'weapons.missiles.SA9M82M'
ENUMS.Storage.weapons.missiles.SA9M82 = 'weapons.missiles.SA9M82'
ENUMS.Storage.weapons.missiles.Igla_S = 'weapons.missiles.Igla_S'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
ENUMS.Storage.weapons.shells.M882_9x19 = 'weapons.shells.9x19_m882'
ENUMS.Storage.weapons.adapters.HB_F_4E_ORD_LAU_77 = 'weapons.adapters.HB_F-4E_ORD_LAU_77'
ENUMS.Storage.weapons.adapters.hb_a_6e_lau7_adu299 = 'weapons.adapters.hb_a-6e_lau7_adu299'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_BLUE = 'weapons.bombs.AH6_SMOKE_BLUE'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_GREEN = 'weapons.bombs.AH6_SMOKE_GREEN'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_RED = 'weapons.bombs.AH6_SMOKE_RED'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_YELLOW = 'weapons.bombs.AH6_SMOKE_YELLOW'
ENUMS.Storage.weapons.missiles.HB_AGM_78 = 'weapons.missiles.HB_AGM_78'
ENUMS.Storage.weapons.missiles.V_1 = 'weapons.missiles.V-1'
ENUMS.Storage.weapons.shells.Oerlikon_20mm_HE = 'weapons.shells.Oerlikon_20mm_HE'
ENUMS.Storage.weapons.shells.RM_15cm_HE = 'weapons.shells.RM_15cm_HE'
ENUMS.Storage.weapons.shells.HE_T_MkII_40mm = 'weapons.shells.HE_T_MkII_40mm'
ENUMS.Storage.weapons.shells.APCBC = 'weapons.shells.APCBC'
ENUMS.Storage.weapons.shells.Sprgr_43_L71 = 'weapons.shells.Sprgr_43_L71'
ENUMS.Storage.weapons.shells.Mk_20_HE_shell = 'weapons.shells.Mk_20_HE_shell'
ENUMS.Storage.weapons.shells.Pzgr_39_42 = 'weapons.shells.Pzgr_39/42'
ENUMS.Storage.weapons.shells.M1_37mm_37AP_T = 'weapons.shells.M1_37mm_37AP-T'
ENUMS.Storage.weapons.shells.Sprgr_38 = 'weapons.shells.Sprgr_38'
ENUMS.Storage.weapons.shells.Sprgr_39 = 'weapons.shells.Sprgr_39'
ENUMS.Storage.weapons.shells.Pzgr_39_43 = 'weapons.shells.Pzgr_39/43'
ENUMS.Storage.weapons.shells.Pzgr_39_5cm = 'weapons.shells.Pzgr_39_5cm'
ENUMS.Storage.weapons.shells.AP_2A20_115mm = 'weapons.shells.2A20_115mm_AP'
ENUMS.Storage.weapons.shells.AP_T_MkI_40mm = 'weapons.shells.AP_T_MkI_40mm'
ENUMS.Storage.weapons.shells.AP_37x263 = 'weapons.shells.37x263_AP'
ENUMS.Storage.weapons.shells.AP_20x138B = 'weapons.shells.20x138B_AP'
ENUMS.Storage.weapons.shells.Besa7_92x57T = 'weapons.shells.Besa7_92x57T'
ENUMS.Storage.weapons.shells.Sprgr_34_L70 = 'weapons.shells.Sprgr_34_L70'
ENUMS.Storage.weapons.shells.QF94_AA_HE = 'weapons.shells.QF94_AA_HE'
ENUMS.Storage.weapons.shells.AH_6762x51mm_M62 = 'weapons.shells.AH-6 7.62x51mm M62'
ENUMS.Storage.weapons.shells.AH_6762x51mm_M80 = 'weapons.shells.AH-6 7.62x51mm M80'
ENUMS.Storage.weapons.shells.AH_6_762x51mm_M61 = 'weapons.shells.AH-6 7.62x51mm M61'
ENUMS.Storage.weapons.shells.leFH18_105HE = 'weapons.shells.leFH18_105HE'
ENUMS.Storage.weapons.shells.M63_37HE = 'weapons.shells.M63_37HE'
ENUMS.Storage.weapons.shells.QF95_206R_fixed = 'weapons.shells.QF95_206R_fixed'
ENUMS.Storage.weapons.shells.UBR_365_85AP = 'weapons.shells.UBR_365_85AP'
ENUMS.Storage.weapons.shells.M101 = 'weapons.shells.M101'
ENUMS.Storage.weapons.shells.HE_M1_Shell = 'weapons.shells.HE_M1_Shell'
ENUMS.Storage.weapons.shells.UO_365K_85HE = 'weapons.shells.UO_365K_85HE'
ENUMS.Storage.weapons.shells.Flak41_Sprgr_39 = 'weapons.shells.Flak41_Sprgr_39'
ENUMS.Storage.weapons.shells.M1_37mm_HE_T = 'weapons.shells.M1_37mm_HE-T'
ENUMS.Storage.weapons.shells.QF17_HE = 'weapons.shells.QF17_HE'
ENUMS.Storage.weapons.shells.Pzgr_39 = 'weapons.shells.Pzgr_39'
ENUMS.Storage.weapons.shells.Besa7_92x57 = 'weapons.shells.Besa7_92x57'
ENUMS.Storage.weapons.shells.I_Gr_33 = 'weapons.shells.I_Gr_33'
ENUMS.Storage.weapons.shells.M62_APC = 'weapons.shells.M62_APC'
ENUMS.Storage.weapons.shells.Mk_12_HE_shell = 'weapons.shells.Mk_12_HE_shell'
ENUMS.Storage.weapons.shells.M51_37AP = 'weapons.shells.M51_37AP'
ENUMS.Storage.weapons.shells.M42A1_HE = 'weapons.shells.M42A1_HE'
ENUMS.Storage.weapons.shells.HE_20x138B = 'weapons.shells.20x138B_HE'
ENUMS.Storage.weapons.shells.HE_37x263 = 'weapons.shells.37x263_HE'
ENUMS.Storage.weapons.shells.HE_2A20_115mm = 'weapons.shells.2A20_115mm_HE'
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_L = 'weapons.gunmounts.B17_TailTurret_M2_L'
ENUMS.Storage.weapons.gunmounts.AH6_M134L = 'weapons.gunmounts.{AH6_M134L}'
ENUMS.Storage.weapons.gunmounts.B17_Left_Nose_M2 = 'weapons.gunmounts.B17_Left_Nose_M2'
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Top_Right_MG_81 = 'weapons.gunmounts.Ju88_Turret_Top_Right_MG_81'
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Bottom_MG_81_L = 'weapons.gunmounts.Ju88_Turret_Bottom_MG_81_L'
ENUMS.Storage.weapons.gunmounts.B17_ChinTurret_M2_R = 'weapons.gunmounts.B17_ChinTurret_M2_R'
ENUMS.Storage.weapons.gunmounts.B17_Waist_Right_M2 = 'weapons.gunmounts.B17_Waist_Right_M2'
ENUMS.Storage.weapons.gunmounts.AH_6_Door_Gun = 'weapons.gunmounts.AH-6_Door_Gun'
ENUMS.Storage.weapons.gunmounts.B17_BallTurret_M2_L = 'weapons.gunmounts.B17_BallTurret_M2_L'
ENUMS.Storage.weapons.gunmounts.B17_BallTurret_M2_R = 'weapons.gunmounts.B17_BallTurret_M2_R'
ENUMS.Storage.weapons.gunmounts.B17_TopTurret_M2_R = 'weapons.gunmounts.B17_TopTurret_M2_R'
ENUMS.Storage.weapons.gunmounts.B17_Right_Nose_M2 = 'weapons.gunmounts.B17_Right_Nose_M2'
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Bottom_MG_81_R = 'weapons.gunmounts.Ju88_Turret_Bottom_MG_81_R'
ENUMS.Storage.weapons.gunmounts.B17_TopTurret_M2_L = 'weapons.gunmounts.B17_TopTurret_M2_L'
ENUMS.Storage.weapons.gunmounts.B17_Waist_Left_M2 = 'weapons.gunmounts.B17_Waist_Left_M2'
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_ahead_MG_81 = 'weapons.gunmounts.Ju88_Turret_ahead_MG_81'
ENUMS.Storage.weapons.gunmounts.AH6_M134R = 'weapons.gunmounts.{AH6_M134R}'
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Top_Left_MG_81 = 'weapons.gunmounts.Ju88_Turret_Top_Left_MG_81'
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_R = 'weapons.gunmounts.B17_TailTurret_M2_R'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
ENUMS.Storage.weapons.gunmounts.B17_ChinTurret_M2_L = 'weapons.gunmounts.B17_ChinTurret_M2_L'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
ENUMS.Storage.weapons.gunmounts.AH_6_Door = 'weapons.gunmounts.{AH-6_Door}'
ENUMS.Storage.weapons.gunmounts.AH_6_FN_HMP400 = 'weapons.gunmounts.{AH-6_FN_HMP400}'
ENUMS.Storage.weapons.gunmounts.AH_6_M134L = 'weapons.gunmounts.AH-6_M134L'
ENUMS.Storage.weapons.gunmounts.AH_6_M134R = 'weapons.gunmounts.AH-6_M134R'
ENUMS.Storage.weapons.gunmounts.AH_6_HMP400 = 'weapons.gunmounts.AH-6_HMP400'
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
ENUMS.Storage.weapons.droptanks.HB_A6E_AERO1D_EMPTY = 'weapons.droptanks.HB_A6E_AERO1D_EMPTY'
ENUMS.Storage.weapons.droptanks.Drop_tank_75gal = 'weapons.droptanks.Drop tank 75gal'
ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
ENUMS.Storage.weapons.droptanks.PTB_3000 = 'weapons.droptanks.PTB-3000'
ENUMS.Storage.weapons.droptanks.HB_A6E_D704 = 'weapons.droptanks.HB_A6E_D704'
ENUMS.Storage.weapons.droptanks.PTB_1150_29 = 'weapons.droptanks.PTB-1150-29'
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
ENUMS.Storage.weapons.droptanks.F4_BAK_L = 'weapons.droptanks.F4-BAK-L'
ENUMS.Storage.weapons.droptanks.HB_A6E_AERO1D = 'weapons.droptanks.HB_A6E_AERO1D'
ENUMS.Storage.weapons.droptanks.IAFS_ComboPak_100 = 'weapons.droptanks.{IAFS_ComboPak_100}'
ENUMS.Storage.weapons.droptanks.PTB_2000 = 'weapons.droptanks.PTB-2000'
ENUMS.Storage.weapons.droptanks.M2000_PTB = 'weapons.droptanks.M2000-PTB'
ENUMS.Storage.weapons.droptanks.PTB_150 = 'weapons.droptanks.PTB-150'
ENUMS.Storage.weapons.droptanks.PTB_1150 = 'weapons.droptanks.PTB-1150'
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = 'weapons.droptanks.fuel_tank_300gal'
ENUMS.Storage.weapons.droptanks.MIG_25_PTB = 'weapons.droptanks.MIG-25-PTB'
ENUMS.Storage.weapons.droptanks.PTB_1500 = 'weapons.droptanks.PTB-1500'
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
ENUMS.Storage.weapons.droptanks.FT600 = 'weapons.droptanks.FT600'
ENUMS.Storage.weapons.droptanks.F15_PTB = 'weapons.droptanks.F15-PTB'
ENUMS.Storage.weapons.droptanks.ah6_auxtank = 'weapons.droptanks.ah6_auxtank'
ENUMS.Storage.weapons.droptanks.T_PTB = 'weapons.droptanks.T-PTB'
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
ENUMS.Storage.weapons.droptanks.F4_BAK_C = 'weapons.droptanks.F4-BAK-C'
ENUMS.Storage.weapons.droptanks.F16_PTB_N2 = 'weapons.droptanks.F-16-PTB-N2'
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
ENUMS.Storage.weapons.droptanks.T_PTB = 'weapons.droptanks.T-PTB'
ENUMS.Storage.weapons.droptanks.F16_PTB_N2 = 'weapons.droptanks.F-16-PTB-N2'
ENUMS.Storage.weapons.droptanks.F4_BAK_C = 'weapons.droptanks.F4-BAK-C'
ENUMS.Storage.weapons.droptanks.PTB_1150_29 = 'weapons.droptanks.PTB-1150-29'
ENUMS.Storage.weapons.droptanks.PTB_1150 = 'weapons.droptanks.PTB-1150'
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = 'weapons.droptanks.fuel_tank_300gal'
ENUMS.Storage.weapons.droptanks.MIG_25_PTB = 'weapons.droptanks.MIG-25-PTB'
ENUMS.Storage.weapons.droptanks.PTB_1500 = 'weapons.droptanks.PTB-1500'
ENUMS.Storage.weapons.droptanks.FT600 = 'weapons.droptanks.FT600'
ENUMS.Storage.weapons.droptanks.Drop_tank_75gal = 'weapons.droptanks.Drop tank 75gal'
ENUMS.Storage.weapons.droptanks.F15_PTB = 'weapons.droptanks.F15-PTB'
ENUMS.Storage.weapons.droptanks.M2000_PTB = 'weapons.droptanks.M2000-PTB'
ENUMS.Storage.weapons.droptanks.PTB_150 = 'weapons.droptanks.PTB-150'
ENUMS.Storage.weapons.droptanks.F4_BAK_L = 'weapons.droptanks.F4-BAK-L'
ENUMS.Storage.weapons.droptanks.PTB_3000 = 'weapons.droptanks.PTB-3000'
ENUMS.Storage.weapons.droptanks.PTB_2000 = 'weapons.droptanks.PTB-2000'
ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
ENUMS.Storage.weapons.containers.OH58D_M3P_L500 = 'weapons.containers.OH58D_M3P_L500'
ENUMS.Storage.weapons.containers.GIAT_M621_HE = 'weapons.containers.{GIAT_M621_HE}'
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_L = 'weapons.containers.KORD_12_7_MI24_L'
ENUMS.Storage.weapons.containers.CH47_STBD_M240H = 'weapons.containers.{CH47_STBD_M240H}'
ENUMS.Storage.weapons.containers.CH47_AFT_M60D = 'weapons.containers.{CH47_AFT_M60D}'
ENUMS.Storage.weapons.containers.UPK_23_250_MiG_21 = 'weapons.containers.{UPK-23-250 MiG-21}'
ENUMS.Storage.weapons.containers.CH47_STBD_M134D = 'weapons.containers.{CH47_STBD_M134D}'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer = 'weapons.containers.{GAU_12_Equalizer}'
ENUMS.Storage.weapons.containers.PKT_7_62 = 'weapons.containers.PKT_7_62'
ENUMS.Storage.weapons.containers.CH47_AFT_M240H = 'weapons.containers.{CH47_AFT_M240H}'
ENUMS.Storage.weapons.containers.GIAT_M621_SAPHEI = 'weapons.containers.{GIAT_M621_SAPHEI}'
ENUMS.Storage.weapons.containers.ADEN_GUNPOD = 'weapons.containers.{ADEN_GUNPOD}'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_HE = 'weapons.containers.{GAU_12_Equalizer_HE}'
ENUMS.Storage.weapons.containers.M60_SIDE_L = 'weapons.containers.M60_SIDE_L'
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_R = 'weapons.containers.KORD_12_7_MI24_R'
ENUMS.Storage.weapons.containers.GIAT_M621_APHE = 'weapons.containers.{GIAT_M621_APHE}'
ENUMS.Storage.weapons.containers.MB339_ANM3_L = 'weapons.containers.{MB339_ANM3_L}'
ENUMS.Storage.weapons.containers.FN_HMP400 = 'weapons.containers.{FN_HMP400}'
ENUMS.Storage.weapons.containers.AH_6_Door = 'weapons.containers.{AH-6_Door}'
ENUMS.Storage.weapons.containers.MB339_DEFA553_L = 'weapons.containers.{MB339_DEFA553_L}'
ENUMS.Storage.weapons.containers.MB339_ANM3_R = 'weapons.containers.{MB339_ANM3_R}'
ENUMS.Storage.weapons.containers.PK_3 = 'weapons.containers.{PK-3}'
ENUMS.Storage.weapons.containers.GUV_VOG = 'weapons.containers.GUV_VOG'
ENUMS.Storage.weapons.containers.SA342_M134_SIDE_R = 'weapons.containers.{SA342_M134_SIDE_R}'
ENUMS.Storage.weapons.containers.OH58D_M3P_L100 = 'weapons.containers.OH58D_M3P_L100'
ENUMS.Storage.weapons.containers.MXU_648 = 'weapons.containers.MXU-648'
ENUMS.Storage.weapons.containers.OH58D_M3P_L400 = 'weapons.containers.OH58D_M3P_L400'
ENUMS.Storage.weapons.containers.FN_HMP400_200 = 'weapons.containers.{FN_HMP400_200}'
ENUMS.Storage.weapons.containers.GIAT_M621_HE = 'weapons.containers.{GIAT_M621_HE}'
ENUMS.Storage.weapons.containers.M134_L = 'weapons.containers.M134_L'
ENUMS.Storage.weapons.containers.OH58D_M3P_L200 = 'weapons.containers.OH58D_M3P_L200'
ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_AP}'
ENUMS.Storage.weapons.containers.CH47_STBD_M134D = 'weapons.containers.{CH47_STBD_M134D}'
ENUMS.Storage.weapons.containers.PKT_7_62 = 'weapons.containers.PKT_7_62'
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
ENUMS.Storage.weapons.containers.GIAT_M621_SAPHEI = 'weapons.containers.{GIAT_M621_SAPHEI}'
ENUMS.Storage.weapons.containers.M60_SIDE_L = 'weapons.containers.M60_SIDE_L'
ENUMS.Storage.weapons.containers.CH47_PORT_M134D = 'weapons.containers.{CH47_PORT_M134D}'
ENUMS.Storage.weapons.containers.CH47_PORT_M60D = 'weapons.containers.{CH47_PORT_M60D}'
ENUMS.Storage.weapons.containers.ADEN_GUNPOD = 'weapons.containers.{ADEN_GUNPOD}'
ENUMS.Storage.weapons.containers.C_101_DEFA553 = 'weapons.containers.{C-101-DEFA553}'
ENUMS.Storage.weapons.containers.MB339_ANM3_R = 'weapons.containers.{MB339_ANM3_R}'
ENUMS.Storage.weapons.containers.CH47_AFT_M60D = 'weapons.containers.{CH47_AFT_M60D}'
ENUMS.Storage.weapons.containers.KORD_12_7 = 'weapons.containers.KORD_12_7'
ENUMS.Storage.weapons.containers.GIAT_M621_HEAP = 'weapons.containers.{GIAT_M621_HEAP}'
ENUMS.Storage.weapons.containers.CH47_AFT_M3M = 'weapons.containers.{CH47_AFT_M3M}'
ENUMS.Storage.weapons.containers.GUV_YakB_GSHP = 'weapons.containers.GUV_YakB_GSHP'
ENUMS.Storage.weapons.containers.R_73U = 'weapons.containers.R-73U'
ENUMS.Storage.weapons.containers.GUV_VOG = 'weapons.containers.GUV_VOG'
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_L = 'weapons.containers.KORD_12_7_MI24_L'
ENUMS.Storage.weapons.containers.OH58D_M3P_L100 = 'weapons.containers.OH58D_M3P_L100'
ENUMS.Storage.weapons.containers.OH58D_M3P_L400 = 'weapons.containers.OH58D_M3P_L400'
ENUMS.Storage.weapons.containers.CH47_STBD_M240H = 'weapons.containers.{CH47_STBD_M240H}'
ENUMS.Storage.weapons.containers.CH47_PORT_M240H = 'weapons.containers.{CH47_PORT_M240H}'
ENUMS.Storage.weapons.containers.CC420_GUN_POD = 'weapons.containers.{CC420_GUN_POD}'
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
ENUMS.Storage.weapons.containers.MB339_ANM3_L = 'weapons.containers.{MB339_ANM3_L}'
ENUMS.Storage.weapons.containers.MB339_DEFA553_R = 'weapons.containers.{MB339_DEFA553_R}'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_HE = 'weapons.containers.{GAU_12_Equalizer_HE}'
ENUMS.Storage.weapons.containers.OH58D_M3P_L500 = 'weapons.containers.OH58D_M3P_L500'
ENUMS.Storage.weapons.containers.SUU_23_POD = 'weapons.containers.{SUU_23_POD}'
ENUMS.Storage.weapons.containers.PK_3 = 'weapons.containers.{PK-3}'
ENUMS.Storage.weapons.containers.AKAN = 'weapons.containers.{AKAN}'
ENUMS.Storage.weapons.containers.CH47_STBD_M60D = 'weapons.containers.{CH47_STBD_M60D}'
ENUMS.Storage.weapons.containers.SA342_M134_SIDE_R = 'weapons.containers.{SA342_M134_SIDE_R}'
ENUMS.Storage.weapons.containers.M60_SIDE_R = 'weapons.containers.M60_SIDE_R'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_AP = 'weapons.containers.{GAU_12_Equalizer_AP}'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer = 'weapons.containers.{GAU_12_Equalizer}'
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_R = 'weapons.containers.KORD_12_7_MI24_R'
ENUMS.Storage.weapons.containers.M134_SIDE_R = 'weapons.containers.M134_SIDE_R'
ENUMS.Storage.weapons.containers.AKAN_NO_TRC = 'weapons.containers.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.containers.oh_58_brauning = 'weapons.containers.oh-58-brauning'
ENUMS.Storage.weapons.containers.MXU_648 = 'weapons.containers.MXU-648'
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
ENUMS.Storage.weapons.containers.GIAT_M621_APHE = 'weapons.containers.{GIAT_M621_APHE}'
ENUMS.Storage.weapons.containers.AIM_9S = 'weapons.containers.AIM-9S'
ENUMS.Storage.weapons.containers.CH47_AFT_M240H = 'weapons.containers.{CH47_AFT_M240H}'
ENUMS.Storage.weapons.containers.FN_HMP400 = 'weapons.containers.{FN_HMP400}'
ENUMS.Storage.weapons.containers.M134_SIDE_L = 'weapons.containers.M134_SIDE_L'
ENUMS.Storage.weapons.containers.MB339_DEFA553_L = 'weapons.containers.{MB339_DEFA553_L}'
ENUMS.Storage.weapons.containers.MISC_1 = 'weapons.containers.{05544F1A-C39C-466b-BC37-5BD1D52E57BB}'
ENUMS.Storage.weapons.containers.MISC_2 = 'weapons.containers.{E92CBFE5-C153-11d8-9897-000476191836}'
ENUMS.Storage.weapons.containers.hvar_SmokeGenerator = 'weapons.containers.hvar_SmokeGenerator'
ENUMS.Storage.weapons.containers.INV_SMOKE_RED = 'weapons.containers.{INV-SMOKE-RED}'
ENUMS.Storage.weapons.containers.INV_SMOKE_YELLOW = 'weapons.containers.{INV-SMOKE-YELLOW}'
ENUMS.Storage.weapons.containers.INV_SMOKE_BLUE = 'weapons.containers.{INV-SMOKE-BLUE}'
ENUMS.Storage.weapons.containers.INV_SMOKE_GREEN = 'weapons.containers.{INV-SMOKE-GREEN}'
ENUMS.Storage.weapons.containers.INV_SMOKE_WHITE = 'weapons.containers.{INV-SMOKE-WHITE}'
ENUMS.Storage.weapons.containers.INV_SMOKE_ORANGE = 'weapons.containers.{INV-SMOKE-ORANGE}'
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_AP = 'weapons.containers.{GAU_12_Equalizer_AP}'
ENUMS.Storage.weapons.containers.AH_6_Gunners = 'weapons.containers.{AH-6_Gunners}'
ENUMS.Storage.weapons.containers.AH_6_FN_HMP400 = 'weapons.containers.{AH-6_FN_HMP400}'
ENUMS.Storage.weapons.containers.R_73U = 'weapons.containers.R-73U'
ENUMS.Storage.weapons.containers.M60_SIDE_R = 'weapons.containers.M60_SIDE_R'
ENUMS.Storage.weapons.containers.CH47_AFT_M3M = 'weapons.containers.{CH47_AFT_M3M}'
ENUMS.Storage.weapons.containers.GUV_YakB_GSHP = 'weapons.containers.GUV_YakB_GSHP'
ENUMS.Storage.weapons.containers.RKL609_L = 'weapons.containers.{RKL609_L}'
ENUMS.Storage.weapons.containers.CH47_PORT_M134D = 'weapons.containers.{CH47_PORT_M134D}'
ENUMS.Storage.weapons.containers.CH47_PORT_M60D = 'weapons.containers.{CH47_PORT_M60D}'
ENUMS.Storage.weapons.containers.RKL609_R = 'weapons.containers.{RKL609_R}'
ENUMS.Storage.weapons.containers.C_101_DEFA553 = 'weapons.containers.{C-101-DEFA553}'
ENUMS.Storage.weapons.containers.AH6_M134L = 'weapons.containers.{AH6_M134L}'
ENUMS.Storage.weapons.containers.KORD_12_7 = 'weapons.containers.KORD_12_7'
ENUMS.Storage.weapons.containers.C130_M18_Sidearm = 'weapons.containers.{C130-M18-Sidearm}'
ENUMS.Storage.weapons.containers.GIAT_M621_HEAP = 'weapons.containers.{GIAT_M621_HEAP}'
ENUMS.Storage.weapons.containers.BRU_42_LS = 'weapons.containers.BRU-42_LS'
ENUMS.Storage.weapons.containers.CH47_STBD_M60D = 'weapons.containers.{CH47_STBD_M60D}'
ENUMS.Storage.weapons.containers.SUU_23_POD = 'weapons.containers.{SUU_23_POD}'
ENUMS.Storage.weapons.containers.M134_SIDE_R = 'weapons.containers.M134_SIDE_R'
ENUMS.Storage.weapons.containers.AKAN_NO_TRC = 'weapons.containers.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.containers.oh_58_brauning = 'weapons.containers.oh-58-brauning'
ENUMS.Storage.weapons.containers.AH_6_DOORS = 'weapons.containers.{AH-6_DOORS}'
ENUMS.Storage.weapons.containers.CC420_GUN_POD = 'weapons.containers.{CC420_GUN_POD}'
ENUMS.Storage.weapons.containers.CH47_PORT_M240H = 'weapons.containers.{CH47_PORT_M240H}'
ENUMS.Storage.weapons.containers.MB339_DEFA553_R = 'weapons.containers.{MB339_DEFA553_R}'
ENUMS.Storage.weapons.containers.AIM_9S = 'weapons.containers.AIM-9S'
ENUMS.Storage.weapons.containers.hvar_SmokeGenerator = 'weapons.containers.hvar_SmokeGenerator'
ENUMS.Storage.weapons.containers.M134_L = 'weapons.containers.M134_L'
ENUMS.Storage.weapons.containers.AKAN = 'weapons.containers.{AKAN}'
ENUMS.Storage.weapons.containers.C130_Cargo_Bay_M4 = 'weapons.containers.{C130-Cargo-Bay-M4}'
ENUMS.Storage.weapons.containers.M134_SIDE_L = 'weapons.containers.M134_SIDE_L'
ENUMS.Storage.weapons.containers.FN_HMP400_200 = 'weapons.containers.{FN_HMP400_200}'
ENUMS.Storage.weapons.containers.OH58D_M3P_L200 = 'weapons.containers.OH58D_M3P_L200'
ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_AP}'
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
ENUMS.Storage.weapons.containers.AH6_M134R = 'weapons.containers.{AH6_M134R}'
ENUMS.Storage.weapons.torpedoes.G7A_T1 = 'weapons.torpedoes.G7A_T1'
-- UH-60L Mod -- UH-60L Mod
ENUMS.Storage.weapons.gunmounts.UH60LGAU19 = 'weapons.gunmounts.UH-60L GAU-19' ENUMS.Storage.weapons.gunmounts.UH60LGAU19 = 'weapons.gunmounts.UH-60L GAU-19'
@@ -1788,6 +2081,15 @@ ENUMS.Storage.weapons.gunmounts.UH60L_M60_GUNNER = 'weapons.gunmounts.{UH60L_M60
ENUMS.Storage.weapons.gunmounts.UH60L_M2_GUNNER = 'weapons.gunmounts.{UH60L_M2_GUNNER}' ENUMS.Storage.weapons.gunmounts.UH60L_M2_GUNNER = 'weapons.gunmounts.{UH60L_M2_GUNNER}'
ENUMS.Storage.weapons.gunmounts.UH60_M230_LEFT = 'weapons.gunmounts.{UH60_M230_LEFT}' ENUMS.Storage.weapons.gunmounts.UH60_M230_LEFT = 'weapons.gunmounts.{UH60_M230_LEFT}'
ENUMS.Storage.weapons.gunmounts.UH60_M230_RIGHT = 'weapons.gunmounts.{UH60_M230_RIGHT}' ENUMS.Storage.weapons.gunmounts.UH60_M230_RIGHT = 'weapons.gunmounts.{UH60_M230_RIGHT}'
ENUMS.Storage.weapons.containers.UH60_M134_RIGHT = 'weapons.containers.{UH60_M134_RIGHT}'
ENUMS.Storage.weapons.containers.UH60_M134_LEFT = 'weapons.containers.{UH60_M134_LEFT}'
ENUMS.Storage.weapons.containers.UH60_M230_LEFT = 'weapons.containers.{UH60_M230_LEFT}'
ENUMS.Storage.weapons.containers.UH60L_M134_GUNNER = 'weapons.containers.{UH60L_M134_GUNNER}'
ENUMS.Storage.weapons.containers.UH60_GAU19_LEFT = 'weapons.containers.{UH60_GAU19_LEFT}'
ENUMS.Storage.weapons.containers.UH60L_M60_GUNNER = 'weapons.containers.{UH60L_M60_GUNNER}'
ENUMS.Storage.weapons.containers.UH60_GAU19_RIGHT = 'weapons.containers.{UH60_GAU19_RIGHT}'
ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}'
ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}'
--- ---
-- @type ENUMS.FARPType -- @type ENUMS.FARPType
+358 -27
View File
@@ -249,7 +249,23 @@ CALLSIGN={
Stetson = 22, Stetson = 22,
Wrath = 23, Wrath = 23,
}, },
Intruder = {
Raygun = 4,
Heartless = 5,
Viceroy = 6,
Cupcake = 7,
["Flying Tiger"] = 8,
["Flying Ace"] = 9,
Buckeye = 10,
Goldplate = 11,
Phoenix = 12,
Electron = 13,
Rustler = 14,
Vixen = 15,
Jackal = 16,
Milestone = 17,
Devil = 18,
},
} --#CALLSIGN } --#CALLSIGN
--- Utilities static class. --- Utilities static class.
@@ -1491,6 +1507,14 @@ function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end end
--- Scale a 3D vectors by multiplication with s.
-- @param DCS#Vec3 v A Vector in 3D with x, y, z components.
-- @param #number s Scale
-- @return DCS#Vec3 Vec3
function UTILS.VecScale(v, s)
return {x = v.x * s, y = v.y * s, z = v.z * s}
end
--- Substract is not a word, don't want to rename the original function because it's been around since forever --- Substract is not a word, don't want to rename the original function because it's been around since forever
function UTILS.VecSubtract(a, b) function UTILS.VecSubtract(a, b)
return UTILS.VecSubstract(a, b) return UTILS.VecSubstract(a, b)
@@ -1504,6 +1528,14 @@ function UTILS.Vec2Substract(a, b)
return {x=a.x-b.x, y=a.y-b.y} return {x=a.x-b.x, y=a.y-b.y}
end end
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector in 3D.
function UTILS.Vec3Substract(a, b)
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
end
--- Substract is not a word, don't want to rename the original function because it's been around since forever --- Substract is not a word, don't want to rename the original function because it's been around since forever
function UTILS.Vec2Subtract(a, b) function UTILS.Vec2Subtract(a, b)
return UTILS.Vec2Substract(a, b) return UTILS.Vec2Substract(a, b)
@@ -1633,6 +1665,13 @@ function UTILS.Vec2Translate(a, distance, angle)
return {x=TX, y=TY} return {x=TX, y=TY}
end end
--- Calculate the lenght of a 3D vector
-- @param DCS#Vec3 v A Vector in 3D with x, y, z components.
-- @return #number length
function UTILS.Vec3Length(v)
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
end
--- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number angle Rotation angle in degrees. -- @param #number angle Rotation angle in degrees.
@@ -2014,6 +2053,12 @@ function UTILS.GetCallsignName(Callsign)
end end
end end
for name, value in pairs(CALLSIGN.Intruder) do
if value==Callsign then
return name
end
end
return "Ghostrider" return "Ghostrider"
end end
@@ -2300,6 +2345,21 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true return true
end end
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(86)==1)then
BASE:T(unit_name.." rear doors are open")
return true
end
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(87)==1)then
BASE:T(unit_name.." Side door(s) are open")
return true
end
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(88)==1)then
BASE:T(unit_name.." Paratroop door(s) are open")
return true
end
if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1) then if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1) then
BASE:T(unit_name .. " rear doors are open") BASE:T(unit_name .. " rear doors are open")
return true return true
@@ -2863,16 +2923,27 @@ end
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position. -- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. -- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. -- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @param #boolean Resurrection If true, dead units can be restored on load. Defaults to false.
-- @param #number ResurrectPercentage Use this percentage of probability to resurrect a unit. [0..100], defaults to 25.
-- @param #number Healmin If set, life points of a resurrected unit will be randomly restored to min this percentage. [0..100], defaults to 25.
-- @param #number Healmax If set, life points of a resurrected unit will be randomly restored to max this percentage. [0..100], defaults to 75.
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` -- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density,Resurrection,ResurrectPercentage,Healmin,Healmax)
local healmin = Healmin or 25
local healmax = Healmax or 75
local resurrection = (Resurrection == true) and true or false
local resurrectpercentage = ResurrectPercentage or 25
local fires = {} local fires = {}
---
-- @param Core.Point#COORDINATE coord
local function Smokers(name,coord,effect,density) local function Smokers(name,coord,effect,density)
local eff = math.random(8) local eff = math.random(8)
if type(effect) == "number" then eff = effect end if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name) coord:BigSmokeAndFire( eff, Density, 300, 1, name )
table.insert(fires,name) table.insert(fires,name)
end end
@@ -2887,7 +2958,16 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
local name = _unit:GetName() local name = _unit:GetName()
Smokers(name,coordinate,Effect,Density) Smokers(name,coordinate,Effect,Density)
end end
_unit:Destroy(false) -- TODO Resurrection logic
local resurectok = math.random(1,100)
BASE:E(string.format("Load Group | Resurrection | Resurrect %s | Thresh %d | Random %d",tostring(resurrection),resurrectpercentage,resurectok))
if resurrection == true and (resurectok < resurrectpercentage) then
local heallife = math.random(healmin,healmax)
BASE:E("Load Group | Resurrection | Life "..heallife)
_unit:SetLife(heallife)
else
_unit:Destroy(false)
end
reduced = reduced + 1 reduced = reduced + 1
if reduced == anzahl then break end if reduced == anzahl then break end
end end
@@ -4222,25 +4302,27 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
local function PopulateStorage(Name,liquids,equip,airframes) local function PopulateStorage(Name,liquids,equip,airframes)
local newWH = STORAGE:New(Name) local newWH = STORAGE:New(Name)
if liquids and liquids > 0 then if newWH then
-- Storage fill-up if liquids and liquids > 0 then
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons -- Storage fill-up
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
end newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
end
if equip and equip > 0 then
for cat,nitem in pairs(ENUMS.Storage.weapons) do if equip and equip > 0 then
for name,item in pairs(nitem) do for cat,nitem in pairs(ENUMS.Storage.weapons) do
newWH:SetItem(item,equip) for name,item in pairs(nitem) do
newWH:SetItem(item,equip)
end
end end
end end
end
if airframes and airframes > 0 then
if airframes and airframes > 0 then for typename in pairs (CSAR.AircraftType) do
for typename in pairs (CSAR.AircraftType) do newWH:SetItem(typename,airframes)
newWH:SetItem(typename,airframes) end
end end
end end
end end
@@ -4319,8 +4401,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
} }
-- Create BIRTH event. -- Create BIRTH event.
world.onEvent(Event) world.onEvent(Event)
PopulateStorage(Name.."-1",liquids,equip,airframes)
else else
-- Spawn FARP -- Spawn FARP
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
@@ -4328,10 +4408,11 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
table.insert(ReturnObjects,spawnedfarp) table.insert(ReturnObjects,spawnedfarp)
PopulateStorage(Name,liquids,equip,airframes)
end end
PopulateStorage(Name,liquids,equip,airframes)
-- Spawn Objects -- Spawn Objects
local FARPStaticObjectsNato = { local FARPStaticObjectsNato = {
["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"},
@@ -4537,6 +4618,18 @@ function UTILS.Vec2toVec3(vec,y)
end end
end end
-- Converts a Vec3 to a Vec2
-- @param vec3 the Vec3 to convert
-- @return Vec2 The Vec2 output
function UTILS.Vec3toVec2(Vec3)
if Vec3 and type(Vec3)=="table" then
local Vec2 = {}
Vec2.x = Vec3.x or 0
Vec2.y = Vec3.z or 0
return Vec2
end
end
--- Get the correction needed for true north in radians --- Get the correction needed for true north in radians
-- @param gPoint The map point vec2 or vec3 -- @param gPoint The map point vec2 or vec3
-- @return number correction -- @return number correction
@@ -5274,3 +5367,241 @@ function UTILS.CreateAirbaseEnum()
_savefile(string.format("%s-enums.txt", env.mission.theatre), text) _savefile(string.format("%s-enums.txt", env.mission.theatre), text)
--env.info(text) --env.info(text)
end end
--- Calculate then center and radius of a circle enclosing a list if DCS#Vec2 points.
-- @param #table points Table of DCS#Vec2 entries
-- @return DCS#Vec2 center DCS#Vec2
-- @return #number radius
function UTILS.GetCenterAndRadius(points)
if #points == 0 then
return nil, nil
end
-- Calculate centroid (average of all points)
local sumX, sumY = 0, 0
for _, p in ipairs(points) do
sumX = sumX + p.x
sumY = sumY + p.y
end
local center = {
x = sumX / #points,
y = sumY / #points
}
-- Find maximum distance from center to any point
local maxDist = 0
for _, p in ipairs(points) do
local dx = p.x - center.x
local dy = p.y - center.y
local dist = math.sqrt(dx*dx + dy*dy)
if dist > maxDist then
maxDist = dist
end
end
return center, maxDist
end
--- More accurate: Minimum bounding circle (Welzl's algorithm), calculate then center and radius of a circle enclosing a list if DCS#Vec2 points.
-- @param #table points Table of DCS#Vec2 entries
-- @return DCS#Vec2 center DCS#Vec2
-- @return #number radius
function UTILS.GetMinimumBoundingCircle(points)
if #points == 0 then
return nil, nil
end
-- Calculate distance between two points
local function distance(p1, p2)
local dx = p2.x - p1.x
local dy = p2.y - p1.y
return math.sqrt(dx*dx + dy*dy)
end
-- Circle from 2 points (diameter)
local function circleFrom2Points(p1, p2)
local center = {
x = (p1.x + p2.x) / 2,
y = (p1.y + p2.y) / 2
}
local radius = distance(p1, p2) / 2
return center, radius
end
-- Circle from 3 points (circumcircle)
local function circleFrom3Points(p1, p2, p3)
local ax, ay = p1.x, p1.y
local bx, by = p2.x, p2.y
local cx, cy = p3.x, p3.y
local d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
if math.abs(d) < 0.0001 then
-- Points are collinear, use 2-point circle
return circleFrom2Points(p1, p3)
end
local aSq = ax*ax + ay*ay
local bSq = bx*bx + by*by
local cSq = cx*cx + cy*cy
local ux = (aSq * (by - cy) + bSq * (cy - ay) + cSq * (ay - by)) / d
local uy = (aSq * (cx - bx) + bSq * (ax - cx) + cSq * (bx - ax)) / d
local center = {x = ux, y = uy}
local radius = distance(center, p1)
return center, radius
end
-- Check if point is inside circle
local function isInside(center, radius, point, tolerance)
tolerance = tolerance or 0.0001
return distance(center, point) <= radius + tolerance
end
-- Welzl's algorithm (recursive)
local function welzlHelper(pts, n, boundary)
-- Base cases
if n == 0 or #boundary == 3 then
if #boundary == 0 then
return {x = 0, y = 0}, 0
elseif #boundary == 1 then
return {x = boundary[1].x, y = boundary[1].y}, 0
elseif #boundary == 2 then
return circleFrom2Points(boundary[1], boundary[2])
else
return circleFrom3Points(boundary[1], boundary[2], boundary[3])
end
end
-- Pick a random point
local p = pts[n]
-- Get circle without this point
local center, radius = welzlHelper(pts, n - 1, boundary)
-- If point is inside, we're done
if isInside(center, radius, p) then
return center, radius
end
-- Otherwise, point must be on the boundary
local newBoundary = {}
for i = 1, #boundary do
newBoundary[i] = boundary[i]
end
table.insert(newBoundary, p)
return welzlHelper(pts, n - 1, newBoundary)
end
-- Shuffle points for better average performance
local pts = {}
for i, p in ipairs(points) do
pts[i] = {x = p.x, y = p.y}
end
-- Simple shuffle
for i = #pts, 2, -1 do
local j = math.random(1, i)
pts[i], pts[j] = pts[j], pts[i]
end
return welzlHelper(pts, #pts, {})
end
--- Calculated optimal intercepting course (Bearing)
-- @param DCS#Vec3 A1 Position of flight one
-- @param DCS#Vec3 V1 VelocityVector of flight one
-- @param DCS#Vec3 A2 Position of flight two
-- @param #number V2_speed Speed of A2 in m/s
-- @return #number Bearing Bearing course for A2 to take for an intercept of A1 or nil if aspect is hot/cold.
function UTILS.CalculateInterceptBearing(A1, V1, A2, V2_speed)
local function berechne_bearing(richtung)
local bearing = math.deg(math.atan2(richtung.x, richtung.y))
if bearing < 0 then
bearing = bearing + 360
end
return bearing
end
local function vec_normalize(v)
local len = UTILS.Vec3Length(v)
if len == 0 then return {x = 0, y = 0, z = 0} end
return {x = v.x / len, y = v.y / len, z = v.z / len}
end
-- Relative Position von F1 zu F2
local rel_pos = UTILS.Vec3Substract(A1, A2)
local distance = UTILS.Vec3Length(rel_pos)
if distance == 0 then
return nil -- Bereits am gleichen Ort
end
-- Richtungsvektor von F2 zu F1 (normalisiert)
local richtung_zu_f1 = vec_normalize(rel_pos)
-- Prüfe HOT: F1 fliegt direkt auf F2 zu
-- Das ist der Fall wenn V1 in die entgegengesetzte Richtung von rel_pos zeigt
local v1_normalisiert = vec_normalize(V1)
local annaeherung = UTILS.VecDot(v1_normalisiert, richtung_zu_f1)
if annaeherung < -0.95 then -- F1 fliegt fast direkt auf F2 zu (Winkel > ~162°)
return nil -- Hot
end
-- Prüfe COLD: F1 fliegt parallel weg von F2
-- Berechne die Geschwindigkeitsdifferenz
local rel_velocity = UTILS.VecSubstract(V1, {x=0, y=0, z=0}) -- V1 relativ zu F2 (falls F2 stillsteht)
local flucht_komponente = UTILS.VecDot(vec_normalize(rel_velocity), richtung_zu_f1)
if flucht_komponente > 0.95 then -- F1 fliegt fast parallel weg (Winkel < ~18°)
return nil -- Cold
end
-- Geschwindigkeiten
local v1 = UTILS.Vec3Length(V1)
local v2 = V2_speed
-- Löse quadratische Gleichung für Abfangzeit t
local a = UTILS.VecDot(V1, V1) - v2 * v2
local b = 2 * UTILS.VecDot(rel_pos, V1)
local c = UTILS.VecDot(rel_pos, rel_pos)
local discriminant = b * b - 4 * a * c
if discriminant < 0 then
return nil -- Keine Lösung möglich
end
-- Wähle die positive, kleinste Lösung
local t1 = (-b + math.sqrt(discriminant)) / (2 * a)
local t2 = (-b - math.sqrt(discriminant)) / (2 * a)
local t = nil
if t1 > 0 and t2 > 0 then
t = math.min(t1, t2)
elseif t1 > 0 then
t = t1
elseif t2 > 0 then
t = t2
else
return nil -- Keine positive Lösung
end
-- Berechne Treffpunkt
local treffpunkt = UTILS.VecAdd(A1, UTILS.VecScale(V1, t))
-- Berechne Richtung zum Treffpunkt
local richtung = UTILS.VecSubstract(treffpunkt, A2)
-- Berechne Bearing
local bearing = berechne_bearing(richtung)
return UTILS.Round(bearing,0)
end
+181 -67
View File
@@ -27,6 +27,7 @@
-- @field #table parkingByID Parking spot data table with ID as key. -- @field #table parkingByID Parking spot data table with ID as key.
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning. -- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
-- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning. -- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning.
-- @field Core.Zone#ZONE_RADIUS parkingCircle Minimum bounding circle enclosing all parking spots.
-- @field #table runways Runways of airdromes. -- @field #table runways Runways of airdromes.
-- @field #AIRBASE.Runway runwayLanding Runway used for landing. -- @field #AIRBASE.Runway runwayLanding Runway used for landing.
-- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff. -- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff.
@@ -717,62 +718,62 @@ AIRBASE.SouthAtlantic = {
--- Airbases of the Sinai map --- Airbases of the Sinai map
-- --
-- * `AIRBASE.SinaiMap.Abu_Rudeis` Abu Rudeis -- * `AIRBASE.Sinai.Abu_Rudeis` Abu Rudeis
-- * `AIRBASE.SinaiMap.Abu_Suwayr` Abu Suwayr -- * `AIRBASE.Sinai.Abu_Suwayr` Abu Suwayr
-- * `AIRBASE.SinaiMap.Al_Bahr_al_Ahmar` Al Bahr al Ahmar -- * `AIRBASE.Sinai.Al_Bahr_al_Ahmar` Al Bahr al Ahmar
-- * `AIRBASE.SinaiMap.Al_Ismailiyah` Al Ismailiyah -- * `AIRBASE.Sinai.Al_Ismailiyah` Al Ismailiyah
-- * `AIRBASE.SinaiMap.Al_Khatatbah` Al Khatatbah -- * `AIRBASE.Sinai.Al_Khatatbah` Al Khatatbah
-- * `AIRBASE.SinaiMap.Al_Mansurah` Al Mansurah -- * `AIRBASE.Sinai.Al_Mansurah` Al Mansurah
-- * `AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base -- * `AIRBASE.Sinai.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base
-- * `AIRBASE.SinaiMap.As_Salihiyah` As Salihiyah -- * `AIRBASE.Sinai.As_Salihiyah` As Salihiyah
-- * `AIRBASE.SinaiMap.AzZaqaziq` AzZaqaziq -- * `AIRBASE.Sinai.AzZaqaziq` AzZaqaziq
-- * `AIRBASE.SinaiMap.Baluza` Baluza -- * `AIRBASE.Sinai.Baluza` Baluza
-- * `AIRBASE.SinaiMap.Ben_Gurion` Ben-Gurion -- * `AIRBASE.Sinai.Ben_Gurion` Ben-Gurion
-- * `AIRBASE.SinaiMap.Beni_Suef` Beni Suef -- * `AIRBASE.Sinai.Beni_Suef` Beni Suef
-- * `AIRBASE.SinaiMap.Bilbeis_Air_Base` Bilbeis Air Base -- * `AIRBASE.Sinai.Bilbeis_Air_Base` Bilbeis Air Base
-- * `AIRBASE.SinaiMap.Bir_Hasanah` Bir Hasanah -- * `AIRBASE.Sinai.Bir_Hasanah` Bir Hasanah
-- * `AIRBASE.SinaiMap.Birma_Air_Base` Birma Air Base -- * `AIRBASE.Sinai.Birma_Air_Base` Birma Air Base
-- * `AIRBASE.SinaiMap.Borg_El_Arab_International_Airport` Borg El Arab International Airport -- * `AIRBASE.Sinai.Borg_El_Arab_International_Airport` Borg El Arab International Airport
-- * `AIRBASE.SinaiMap.Cairo_International_Airport` Cairo International Airport -- * `AIRBASE.Sinai.Cairo_International_Airport` Cairo International Airport
-- * `AIRBASE.SinaiMap.Cairo_West` Cairo West -- * `AIRBASE.Sinai.Cairo_West` Cairo West
-- * `AIRBASE.SinaiMap.Damascus_Intl` Damascus Intl -- * `AIRBASE.Sinai.Damascus_Intl` Damascus Intl
-- * `AIRBASE.SinaiMap.Difarsuwar_Airfield` Difarsuwar Airfield -- * `AIRBASE.Sinai.Difarsuwar_Airfield` Difarsuwar Airfield
-- * `AIRBASE.SinaiMap.Ein_Shamer` Ein Shamer -- * `AIRBASE.Sinai.Ein_Shamer` Ein Shamer
-- * `AIRBASE.SinaiMap.El_Arish` El Arish -- * `AIRBASE.Sinai.El_Arish` El Arish
-- * `AIRBASE.SinaiMap.El_Gora` El Gora -- * `AIRBASE.Sinai.El_Gora` El Gora
-- * `AIRBASE.SinaiMap.El_Minya` El Minya -- * `AIRBASE.Sinai.El_Minya` El Minya
-- * `AIRBASE.SinaiMap.Fayed` Fayed -- * `AIRBASE.Sinai.Fayed` Fayed
-- * `AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base -- * `AIRBASE.Sinai.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base
-- * `AIRBASE.SinaiMap.Hatzerim` Hatzerim -- * `AIRBASE.Sinai.Hatzerim` Hatzerim
-- * `AIRBASE.SinaiMap.Hatzor` Hatzor -- * `AIRBASE.Sinai.Hatzor` Hatzor
-- * `AIRBASE.SinaiMap.Hurghada_International_Airport` Hurghada International Airport -- * `AIRBASE.Sinai.Hurghada_International_Airport` Hurghada International Airport
-- * `AIRBASE.SinaiMap.Inshas_Airbase` Inshas Airbase -- * `AIRBASE.Sinai.Inshas_Airbase` Inshas Airbase
-- * `AIRBASE.SinaiMap.Jiyanklis_Air_Base` Jiyanklis Air Base -- * `AIRBASE.Sinai.Jiyanklis_Air_Base` Jiyanklis Air Base
-- * `AIRBASE.SinaiMap.Kedem` Kedem -- * `AIRBASE.Sinai.Kedem` Kedem
-- * `AIRBASE.SinaiMap.Khalkhalah_Air_Base` Khalkhalah Air Base -- * `AIRBASE.Sinai.Khalkhalah_Air_Base` Khalkhalah Air Base
-- * `AIRBASE.SinaiMap.Kibrit_Air_Base` Kibrit Air Base -- * `AIRBASE.Sinai.Kibrit_Air_Base` Kibrit Air Base
-- * `AIRBASE.SinaiMap.King_Feisal_Air_Base` King Feisal Air Base -- * `AIRBASE.Sinai.King_Feisal_Air_Base` King Feisal Air Base
-- * `AIRBASE.SinaiMap.Kom_Awshim` Kom Awshim -- * `AIRBASE.Sinai.Kom_Awshim` Kom Awshim
-- * `AIRBASE.SinaiMap.Megiddo` Megiddo -- * `AIRBASE.Sinai.Megiddo` Megiddo
-- * `AIRBASE.SinaiMap.Melez` Melez -- * `AIRBASE.Sinai.Melez` Melez
-- * `AIRBASE.SinaiMap.Mezzeh_Air_Base` Mezzeh Air Base -- * `AIRBASE.Sinai.Mezzeh_Air_Base` Mezzeh Air Base
-- * `AIRBASE.SinaiMap.Nevatim` Nevatim -- * `AIRBASE.Sinai.Nevatim` Nevatim
-- * `AIRBASE.SinaiMap.Ovda` Ovda -- * `AIRBASE.Sinai.Ovda` Ovda
-- * `AIRBASE.SinaiMap.Palmachim` Palmachim -- * `AIRBASE.Sinai.Palmachim` Palmachim
-- * `AIRBASE.SinaiMap.Quwaysina` Quwaysina -- * `AIRBASE.Sinai.Quwaysina` Quwaysina
-- * `AIRBASE.SinaiMap.Rafic_Hariri_Intl` Rafic Hariri Intl -- * `AIRBASE.Sinai.Rafic_Hariri_Intl` Rafic Hariri Intl
-- * `AIRBASE.SinaiMap.Ramat_David` Ramat David -- * `AIRBASE.Sinai.Ramat_David` Ramat David
-- * `AIRBASE.SinaiMap.Ramon_Airbase` Ramon Airbase -- * `AIRBASE.Sinai.Ramon_Airbase` Ramon Airbase
-- * `AIRBASE.SinaiMap.Ramon_International_Airport` Ramon International Airport -- * `AIRBASE.Sinai.Ramon_International_Airport` Ramon International Airport
-- * `AIRBASE.SinaiMap.Sde_Dov` Sde Dov -- * `AIRBASE.Sinai.Sde_Dov` Sde Dov
-- * `AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport -- * `AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport
-- * `AIRBASE.SinaiMap.St_Catherine` St Catherine -- * `AIRBASE.Sinai.St_Catherine` St Catherine
-- * `AIRBASE.SinaiMap.Taba_International_Airport` Taba International Airport -- * `AIRBASE.Sinai.Taba_International_Airport` Taba International Airport
-- * `AIRBASE.SinaiMap.Tabuk` Tabuk -- * `AIRBASE.Sinai.Tabuk` Tabuk
-- * `AIRBASE.SinaiMap.TabukHeliBase` TabukHeliBase -- * `AIRBASE.Sinai.TabukHeliBase` TabukHeliBase
-- * `AIRBASE.SinaiMap.Tel_Nof` Tel Nof -- * `AIRBASE.Sinai.Tel_Nof` Tel Nof
-- * `AIRBASE.SinaiMap.Wadi_Abu_Rish` Wadi Abu Rish -- * `AIRBASE.Sinai.Wadi_Abu_Rish` Wadi Abu Rish
-- * `AIRBASE.SinaiMap.Wadi_al_Jandali` Wadi al Jandali -- * `AIRBASE.Sinai.Wadi_al_Jandali` Wadi al Jandali
-- --
-- @field Sinai -- @field Sinai
AIRBASE.Sinai = { AIRBASE.Sinai = {
@@ -833,6 +834,66 @@ AIRBASE.Sinai = {
["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_Abu_Rish"] = "Wadi Abu Rish",
["Wadi_al_Jandali"] = "Wadi al Jandali", ["Wadi_al_Jandali"] = "Wadi al Jandali",
} }
---
-- @field SinaiMap
AIRBASE.SinaiMap = {
["Abu_Rudeis"] = "Abu Rudeis",
["Abu_Suwayr"] = "Abu Suwayr",
["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar",
["Al_Ismailiyah"] = "Al Ismailiyah",
["Al_Khatatbah"] = "Al Khatatbah",
["Al_Mansurah"] = "Al Mansurah",
["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base",
["As_Salihiyah"] = "As Salihiyah",
["AzZaqaziq"] = "AzZaqaziq",
["Baluza"] = "Baluza",
["Ben_Gurion"] = "Ben-Gurion",
["Beni_Suef"] = "Beni Suef",
["Bilbeis_Air_Base"] = "Bilbeis Air Base",
["Bir_Hasanah"] = "Bir Hasanah",
["Birma_Air_Base"] = "Birma Air Base",
["Borg_El_Arab_International_Airport"] = "Borg El Arab International Airport",
["Cairo_International_Airport"] = "Cairo International Airport",
["Cairo_West"] = "Cairo West",
["Damascus_Intl"] = "Damascus Intl",
["Difarsuwar_Airfield"] = "Difarsuwar Airfield",
["Ein_Shamer"] = "Ein Shamer",
["El_Arish"] = "El Arish",
["El_Gora"] = "El Gora",
["El_Minya"] = "El Minya",
["Fayed"] = "Fayed",
["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base",
["Hatzerim"] = "Hatzerim",
["Hatzor"] = "Hatzor",
["Hurghada_International_Airport"] = "Hurghada International Airport",
["Inshas_Airbase"] = "Inshas Airbase",
["Jiyanklis_Air_Base"] = "Jiyanklis Air Base",
["Kedem"] = "Kedem",
["Khalkhalah_Air_Base"] = "Khalkhalah Air Base",
["Kibrit_Air_Base"] = "Kibrit Air Base",
["King_Feisal_Air_Base"] = "King Feisal Air Base",
["Kom_Awshim"] = "Kom Awshim",
["Megiddo"] = "Megiddo",
["Melez"] = "Melez",
["Mezzeh_Air_Base"] = "Mezzeh Air Base",
["Nevatim"] = "Nevatim",
["Ovda"] = "Ovda",
["Palmachim"] = "Palmachim",
["Quwaysina"] = "Quwaysina",
["Rafic_Hariri_Intl"] = "Rafic Hariri Intl",
["Ramat_David"] = "Ramat David",
["Ramon_Airbase"] = "Ramon Airbase",
["Ramon_International_Airport"] = "Ramon International Airport",
["Sde_Dov"] = "Sde Dov",
["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport",
["St_Catherine"] = "St Catherine",
["Taba_International_Airport"] = "Taba International Airport",
["Tabuk"] = "Tabuk",
["TabukHeliBase"] = "TabukHeliBase",
["Tel_Nof"] = "Tel Nof",
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
["Wadi_al_Jandali"] = "Wadi al Jandali",
}
--- Airbases of the Kola map --- Airbases of the Kola map
-- --
@@ -1552,7 +1613,7 @@ AIRBASE.SpotStatus = {
--- Runway data. --- Runway data.
-- @type AIRBASE.Runway -- @type AIRBASE.Runway
-- @field #string name Runway name. -- @field #string name Runway name.
-- @field #string idx Runway ID: heading 070° ==> idx="07". -- @field #string idx Runway ID: heading 070° ==> idx="07". Mostly same as magheading.
-- @field #number heading True heading of the runway in degrees. -- @field #number heading True heading of the runway in degrees.
-- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway. -- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway.
-- @field #number length Length of runway in meters. -- @field #number length Length of runway in meters.
@@ -1662,6 +1723,10 @@ end
else else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
end end
if Nrunways>0 then
self:GetMinimumBoundingCircleFromParkingSpots()
end
-- Debug info. -- Debug info.
self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName))) self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName)))
@@ -2104,7 +2169,7 @@ end
function AIRBASE:GetParkingSpotsNumber(termtype) function AIRBASE:GetParkingSpotsNumber(termtype)
-- Get free parking spots data. -- Get free parking spots data.
local parkingdata=self:GetParkingData(false) local parkingdata=self:GetParkingData(false) or {}
local nspots=0 local nspots=0
for _,parkingspot in pairs(parkingdata) do for _,parkingspot in pairs(parkingdata) do
@@ -2124,7 +2189,7 @@ end
function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
-- Get free parking spots data. -- Get free parking spots data.
local parkingdata=self:GetParkingData(true) local parkingdata=self:GetParkingData(true) or {}
local nfree=0 local nfree=0
for _,parkingspot in pairs(parkingdata) do for _,parkingspot in pairs(parkingdata) do
@@ -2147,7 +2212,7 @@ end
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
-- Get free parking spots data. -- Get free parking spots data.
local parkingdata=self:GetParkingData(true) local parkingdata=self:GetParkingData(true) or {}
-- Put coordinates of free spots into table. -- Put coordinates of free spots into table.
local spots={} local spots={}
@@ -2191,13 +2256,62 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype)
return spots return spots
end end
--- Get the DCS#Vec2s of all parking spots at an airbase. Optionally only those of a specific terminal type. Spots on runways are excluded if not explicitly requested by terminal type.
-- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype (Optional) Terminal type. Default all.
-- @return #table Table of DCS#Vec2 of parking spots.
function AIRBASE:GetParkingSpotsVec2s(termtype)
-- Get all parking spots data.
local parkingdata=self:GetParkingData(false)
-- Put coordinates of free spots into table.
local spots={}
for _,parkingspot in ipairs(parkingdata) do
-- Coordinates on runway are not returned unless explicitly requested.
if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then
-- Get coordinate from Vec3 terminal position.
local vec2 = { x = parkingspot.vTerminalPos.x, y = parkingspot.vTerminalPos.z }
-- Add to table.
table.insert(spots, vec2)
end
end
return spots
end
--- Get the the bounding circular zone around all parking spots of an airbase.
-- @param #AIRBASE self
-- @param #boolean mark (Optional) Draw zone on map on first call of this function.
-- @return Core.Zone#ZONE_RADIUS BoundingZone
function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark)
if self.isAirdrome then
if not self.parkingCircle then
local spots = self:GetParkingSpotsVec2s()
if #spots == 0 then return self.AirbaseZone end
local center, radius = UTILS.GetMinimumBoundingCircle(spots)
self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50)
if mark == true then
self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3)
end
end
return self.parkingCircle
else
return self.AirbaseZone
end
end
--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase.
-- @param #AIRBASE self -- @param #AIRBASE self
-- @return#AIRBASE self -- @return#AIRBASE self
function AIRBASE:_InitParkingSpots() function AIRBASE:_InitParkingSpots()
-- Get parking data of all spots (free or occupied) -- Get parking data of all spots (free or occupied)
local parkingdata=self:GetParkingData(false) local parkingdata=self:GetParkingData(false) or {}
-- Init table. -- Init table.
self.parking={} self.parking={}
@@ -2273,10 +2387,10 @@ end
function AIRBASE:GetParkingSpotsTable(termtype) function AIRBASE:GetParkingSpotsTable(termtype)
-- Get parking data of all spots (free or occupied) -- Get parking data of all spots (free or occupied)
local parkingdata=self:GetParkingData(false) local parkingdata=self:GetParkingData(false) or {}
-- Get parking data of all free spots. -- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true) local parkingfree=self:GetParkingData(true) or {}
-- Function to ckeck if any parking spot is free. -- Function to ckeck if any parking spot is free.
local function _isfree(_tocheck) local function _isfree(_tocheck)
@@ -2325,7 +2439,7 @@ end
function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
-- Get parking data of all free spots. -- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true) local parkingfree=self:GetParkingData(true) or {}
-- Put coordinates of free spots into table. -- Put coordinates of free spots into table.
local freespots={} local freespots={}
@@ -2355,7 +2469,7 @@ end
function AIRBASE:GetParkingSpotData(TerminalID) function AIRBASE:GetParkingSpotData(TerminalID)
-- Get parking data. -- Get parking data.
local parkingdata=self:GetParkingSpotsTable() local parkingdata=self:GetParkingSpotsTable() or {}
for _,_spot in pairs(parkingdata) do for _,_spot in pairs(parkingdata) do
local spot=_spot --#AIRBASE.ParkingSpot local spot=_spot --#AIRBASE.ParkingSpot
@@ -2381,7 +2495,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
end end
-- Get parking data from getParking() wrapper function. -- Get parking data from getParking() wrapper function.
local parkingdata=self:GetParkingSpotsTable(termtype) local parkingdata=self:GetParkingSpotsTable(termtype) or {}
-- Get airbase name. -- Get airbase name.
local airbasename=self:GetName() local airbasename=self:GetName()
@@ -2496,7 +2610,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
local markobstacles=false local markobstacles=false
-- Loop over all known parking spots -- Loop over all known parking spots
for _,parkingspot in pairs(parkingdata) do for _,parkingspot in pairs(parkingdata or {}) do
-- Coordinate of the parking spot. -- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
+13 -3
View File
@@ -2439,7 +2439,7 @@ 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 if _DATABASE.Templates.Groups[self.GroupName] and _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 else
return {} return {}
@@ -2467,7 +2467,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
--self:T3( { GroupName } ) --self:T3( { GroupName } )
local Template = _DATABASE.Templates.Groups[GroupName].Template local Template = _DATABASE.Templates.Groups[GroupName] and _DATABASE.Templates.Groups[GroupName].Template or nil
if Template then if Template then
if not Begin then if not Begin then
@@ -2491,7 +2491,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
end end
return Points return Points
else else
error( "Template not found for Group : " .. GroupName ) BASE:E( "Template not found for Group : " .. GroupName )
end end
return nil return nil
@@ -2944,6 +2944,16 @@ function GROUP:EnableEmission(switch)
return self return self
end end
--- Set a formation for this group.
-- @param #GROUP self
-- @param #number Formation See. ENUMS.Formation or [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) for options.
-- @return #GROUP self
function GROUP:SetFormation(Formation)
self:SetOption(AI.Option.Air.id.FORMATION,Formation)
return self
end
--- Switch on/off invisible flag for the group. --- Switch on/off invisible flag for the group.
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled. -- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
+1 -1
View File
@@ -485,7 +485,7 @@ function NET:GetPlayerIDByName(Name)
if not Name then return nil end if not Name then return nil end
local playerList = net.get_player_list() local playerList = net.get_player_list()
for i=1,#playerList do for i=1,#playerList do
local playerName = net.get_name(i) local playerName = net.get_name(playerList[i])
if playerName == Name then if playerName == Name then
return playerList[i] return playerList[i]
end end
@@ -729,16 +729,23 @@ end
--- Returns the altitude above sea level of the POSITIONABLE. --- Returns the altitude above sea level of the POSITIONABLE.
-- @param #POSITIONABLE self -- @param #POSITIONABLE self
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
-- @return DCS#Distance The altitude of the POSITIONABLE. -- @return DCS#Distance The altitude of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive. -- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetAltitude() function POSITIONABLE:GetAltitude(FromGround)
self:F2() self:F2()
local DCSPositionable = self:GetDCSObject() local DCSPositionable = self:GetDCSObject()
if DCSPositionable then if DCSPositionable then
local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3 local altitude = 0
return PositionablePointVec3.y local point = DCSPositionable:getPoint() --DCS#Vec3
altitude = point.y
if FromGround then
local land = land.getHeight({ x = point.x, y = point.z }) or 0
altitude = altitude - land
end
return altitude
end end
BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } )
+1 -1
View File
@@ -75,7 +75,7 @@ function SCENERY:Register( SceneryName, SceneryObject, SceneryZone )
self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil
end end
if SceneryObject then if SceneryObject and SceneryObject.getPoint then
local vec3 = SceneryObject:getPoint() local vec3 = SceneryObject:getPoint()
self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z } self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z }
self.Vec2 = { x = vec3.x, y = vec3.z } self.Vec2 = { x = vec3.x, y = vec3.z }
+25 -6
View File
@@ -1352,9 +1352,7 @@ function UNIT:GetThreatLevel()
end end
ThreatText = ThreatLevels[ThreatLevel + 1] ThreatText = ThreatLevels[ThreatLevel + 1]
end elseif self:IsAir() then
if self:IsAir() then
local ThreatLevels = { local ThreatLevels = {
[1] = "Unarmed", [1] = "Unarmed",
@@ -1397,9 +1395,7 @@ function UNIT:GetThreatLevel()
end end
ThreatText = ThreatLevels[ThreatLevel + 1] ThreatText = ThreatLevels[ThreatLevel + 1]
end elseif self:IsShip() then
if self:IsShip() then
--["Aircraft Carriers"] = {"Heavy armed ships",}, --["Aircraft Carriers"] = {"Heavy armed ships",},
--["Cruisers"] = {"Heavy armed ships",}, --["Cruisers"] = {"Heavy armed ships",},
@@ -1953,3 +1949,26 @@ end
function UNIT:SetValidateAndRepositionGroundUnits(Enabled) function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
self.ValidateAndRepositionGroundUnits = Enabled self.ValidateAndRepositionGroundUnits = Enabled
end end
--- Get the max kgs of fuel this unit can hold in its *internal* tank(s) and overall (with external tanks) in kgs.
-- @param #UNIT self
-- @return #number InternalFuel Max internal fuel in kgs. Zero if it cannot be determined.
-- @return #number Overall Fuel Overall max in case there are external tanks in kgs. Zero if it cannot be determined.
function UNIT:GetFuelMassMax()
local Desc = self:GetDesc() or {}
local massFuelMax=Desc.fuelMassMax or 0
local relFuel=math.min(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks.
local massFuel=massFuelMax*relFuel
return massFuel, massFuelMax
end
--- Get the current kgs of fuel this unit holds in all of its tanks.
-- @param #UNIT self
-- @param #number Filling Fuel in kgs.
function UNIT:GetCurrentFuelKgs()
local fuel, maxfuel = self:GetFuelMassMax()
local relfuel = self:GetFuel()
local maxfilling = math.max(fuel,maxfuel)
local mass = maxfilling*relfuel
return mass
end
+1 -1
View File
@@ -350,7 +350,7 @@ end
-- myweapon:SetFuncImpact(OnImpact) -- myweapon:SetFuncImpact(OnImpact)
-- --
-- -- Start tracking. -- -- Start tracking.
-- myweapon:Track() -- myweapon:StartTrack()
-- --
function WEAPON:SetFuncImpact(FuncImpact, ...) function WEAPON:SetFuncImpact(FuncImpact, ...)
self.impactFunc=FuncImpact self.impactFunc=FuncImpact
+1
View File
@@ -104,6 +104,7 @@ Ops/FlightControl.lua
Ops/PlayerTask.lua Ops/PlayerTask.lua
Ops/PlayerRecce.lua Ops/PlayerRecce.lua
Ops/EasyGCICAP.lua Ops/EasyGCICAP.lua
Ops/EasyA2G.lua
Ops/OpsZone.lua Ops/OpsZone.lua
Ops/ArmyGroup.lua Ops/ArmyGroup.lua
Ops/OpsTransport.lua Ops/OpsTransport.lua