Compare commits

..

331 Commits

Author SHA1 Message Date
Frank 0beb6b2022 Warehouse & OpsTransport
**WAREHOUSE**
- Switched from AI_CARGO_DISPATCHER to OPSTRANSPORT

**OPSTRANSPORT**
- Fixed bugs
2026-02-07 22:34:36 +01:00
Thomas 462b545968 Merge pull request #2502 from FlightControl-Master/Applevangelist-patch-2
Delete Moose Development/Moose/Modules_local.lua
2026-02-06 14:37:40 +01:00
Thomas d12ad85fbd Delete Moose Development/Moose/Modules_local.lua 2026-02-06 14:37:23 +01:00
Thomas 887b918f4d Merge pull request #2501 from FlightControl-Master/Applevangelist-patch-1
Delete Moose Development/Moose/AI directory
2026-02-06 14:36:45 +01:00
Thomas d4de219032 Delete Moose Development/Moose/AI directory 2026-02-06 14:36:32 +01:00
Applevangelist 7616b63fae Merge remote-tracking branch 'origin/develop' into develop 2026-02-06 14:35:52 +01:00
Applevangelist aade99a0e6 Merge remote-tracking branch 'origin/master-ng' into develop
# Conflicts:
#	Moose Development/Moose/AI/AI_A2G_BAI.lua
#	Moose Development/Moose/AI/AI_CAP.lua
#	Moose Development/Moose/Modules_local.lua
2026-02-06 14:35:47 +01:00
Thomas 1781f5e7d6 Delete Moose Development/Moose/Functional/MissileTrainer.lua 2026-02-06 14:29:19 +01:00
Applevangelist f8b5bd8002 #RAT - added new RAT:SetMinCruiseSpeed() method from @HawaiianRyan 2026-02-06 14:10:48 +01:00
Thomas 1d4e77cf05 Update build-docs.yml 2026-02-06 13:53:52 +01:00
Thomas b77a7255cb Update build-docs.yml 2026-02-06 13:06:16 +01:00
Thomas 353904d52f Update build-docs.yml 2026-02-06 12:58:55 +01:00
Thomas 01bd1e4428 Update build-includes.yml 2026-02-06 12:56:08 +01:00
Thomas 05b06680bc Update gh-pages.yml 2026-02-06 12:55:19 +01:00
Thomas 1f71d11713 Update Modules.lua 2026-02-06 12:51:47 +01:00
Thomas bd6486fb8a Delete Moose Development/Moose/Modules_local.lua 2026-02-06 12:48:30 +01:00
Thomas d28ab00443 Delete Moose Development/Moose/Tasking directory
prep master-ng
2026-02-06 12:48:08 +01:00
Thomas 8cb06cc3b0 Delete Moose Development/Moose/Actions directory
prep master-ng
2026-02-06 12:47:49 +01:00
Thomas ea330128ca Delete Moose Development/Moose/AI directory
Prep master-ng branch
2026-02-06 12:47:29 +01:00
Thomas 8ce00545ee Merge pull request #2500 from FlightControl-Master/master
Merge from master
2026-02-06 12:38:49 +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 63ba258085 Merge remote-tracking branch 'origin/master' into develop 2026-02-05 12:21:31 +01:00
Applevangelist 5e2481af7a xx 2026-02-05 12:21:13 +01:00
Applevangelist dab6f48fe9 xx 2026-02-05 12:20:48 +01:00
Thomas 6dc840c24d Merge pull request #2498 from FlightControl-Master/master
Merge from master
2026-02-05 12:19:30 +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 f6555a45ae Merge pull request #2496 from FlightControl-Master/master
Merge
2026-01-30 06:40:52 +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 e996b5fc16 Merge remote-tracking branch 'origin/master' into develop 2026-01-28 14:42:20 +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 02d1f70386 Merge remote-tracking branch 'origin/develop' into develop 2026-01-25 13:15:21 +01:00
Applevangelist 5251041ed2 Merge remote-tracking branch 'origin/master' into develop 2026-01-25 13:15:17 +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 38f4b0c53d Merge pull request #2493 from FlightControl-Master/master
Merge
2026-01-24 18:47:12 +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 4ff7e74e3c Merge remote-tracking branch 'origin/master' into develop 2026-01-24 16:21:58 +01:00
Applevangelist 1ee90e67b2 #UTILS - LoadStationaryListOfGroups() added option to (randomly) restore dead units on reload. 2026-01-24 16:21:37 +01:00
Applevangelist 419d85831c Merge remote-tracking branch 'origin/master' into develop 2026-01-24 16:17:27 +01:00
Applevangelist d0ce7f1aa6 xx 2026-01-24 16:16:50 +01:00
Thomas 754f090c61 Merge pull request #2491 from FlightControl-Master/master
Merge
2026-01-23 19:22:30 +01:00
Thomas c387749a81 Merge pull request #2490 from leka1986/patch-7
Fixed bugs
2026-01-23 19:21:22 +01:00
Applevangelist f68a2e6272 Merge remote-tracking branch 'origin/master' into develop 2026-01-23 18:21:41 +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 2e69911bcc Merge remote-tracking branch 'origin/master' into develop 2026-01-22 17:19:21 +01:00
Applevangelist 3ab3278db4 xx 2026-01-22 17:18:59 +01:00
Applevangelist 70ad9d442b Merge remote-tracking branch 'origin/master' into develop 2026-01-22 12:35:52 +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 c839a015af Merge pull request #2489 from FlightControl-Master/master
Update Utils.lua
2026-01-20 11:41:20 +01:00
Thomas 9c0394f035 Update Utils.lua
#UTILS - Added  `UTILS.Vec3toVec2(Vec3)`
2026-01-20 11:38:34 +01:00
Applevangelist be42b7e604 Merge remote-tracking branch 'origin/master' into develop 2026-01-12 13:14:17 +01:00
Applevangelist 9536d368b0 xx 2026-01-12 13:13:48 +01:00
Applevangelist c94f0fa34b Merge remote-tracking branch 'origin/master' into develop 2026-01-12 08:49:07 +01:00
Applevangelist 9426905959 #AIRBASE - Fix parking data table could be nil 2026-01-12 08:48:37 +01:00
Applevangelist ffaf2efd06 Merge remote-tracking branch 'origin/master' into develop 2026-01-09 15:41:19 +01:00
Applevangelist 99b7b4b057 #EASYGCICAP - Fix error in setting floor/ceiling for corridors 2026-01-09 15:38:05 +01:00
Applevangelist 22a63a8fb0 Merge remote-tracking branch 'origin/master' into develop 2026-01-08 18:52:27 +01:00
Applevangelist 2fdbdb4058 #RECOVERYTANKER - Ensure correct "F10 Name" for A6E if used as tanker 2026-01-08 18:51:13 +01:00
Applevangelist 0a509c4723 Merge remote-tracking branch 'origin/master' into develop 2026-01-08 13:11:49 +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 30f0eca0f4 Merge pull request #2488 from FlightControl-Master/master
Merge
2026-01-05 07:38:54 +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 87082de9c4 Merge remote-tracking branch 'origin/master' into develop 2026-01-04 17:53:36 +01:00
Applevangelist 3cfaa60ebb #VARIOUS fixes 2026-01-04 17:52:55 +01:00
Applevangelist e51c1d0b85 Merge remote-tracking branch 'origin/develop' into develop 2026-01-03 17:26:39 +01:00
Applevangelist ca540bfd90 Merge remote-tracking branch 'origin/master' into develop 2026-01-03 17:26:37 +01:00
Applevangelist 2d930ff2dd #SCORING - missed data for saving in format() 2026-01-03 17:26:17 +01:00
Frank e29f545c1e Merge branch 'master' into develop 2025-12-31 13:17:27 +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 bf155a0beb Merge remote-tracking branch 'origin/master' into develop 2025-12-30 10:40:50 +01:00
Applevangelist f7556fac0e #UNIT - Added GetFuelMassMax() and GetCurrentFileKgs() 2025-12-30 10:40:27 +01:00
Applevangelist a3d77a5a71 Merge remote-tracking branch 'origin/master' into develop 2025-12-30 09:26:48 +01:00
Applevangelist 394f3ffd88 #AIRBASE - Small fix for #ParkingSpots == 0 2025-12-30 09:26:07 +01:00
Applevangelist bff5f79282 Merge remote-tracking branch 'origin/master' into develop 2025-12-29 18:09:16 +01:00
Applevangelist a3d7b4eba9 #AIRWING - Check if STORAGE object has NO warehouse! 2025-12-29 18:08:53 +01:00
Applevangelist a49288200e Merge remote-tracking branch 'origin/master' into develop 2025-12-29 14:01:43 +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 fc809964c7 Merge remote-tracking branch 'origin/master' into develop 2025-12-29 10:48:57 +01:00
Applevangelist d000ad76f8 #CSAR - Corrected Airbase dist finding solution 2025-12-29 10:48:23 +01:00
Applevangelist 3f5936bceb Merge remote-tracking branch 'origin/master' into develop 2025-12-28 17:56:48 +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 fef1976a6f Merge remote-tracking branch 'origin/master' into develop 2025-12-28 16:52:14 +01:00
Applevangelist 1eb6dbc7f2 xx 2025-12-28 16:51:41 +01:00
Applevangelist d16eb74e1b Merge remote-tracking branch 'origin/master' into develop 2025-12-28 14:06:38 +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 646fa8b7e6 Merge pull request #2485 from FlightControl-Master/master
Merge
2025-12-28 09:01:42 +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 7ff8978ca7 Merge pull request #2483 from FlightControl-Master/master
Merge from master
2025-12-24 08:21:06 +01: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 635c8dc2ad Merge remote-tracking branch 'origin/master' into develop 2025-12-23 17:17:34 +01:00
Applevangelist 8c0e021e82 #CTLD - Make FSM event CratesDropped useable for tasking 2025-12-23 17:16:57 +01:00
Applevangelist 022854fa37 Merge remote-tracking branch 'origin/master' into develop 2025-12-22 14:10:14 +01:00
Applevangelist a72d3afd1c xx 2025-12-22 14:09:37 +01:00
Applevangelist 1f062fe7b4 Merge remote-tracking branch 'origin/master' into develop 2025-12-22 12:22:13 +01:00
Applevangelist 68aab10902 xx 2025-12-22 12:21:42 +01:00
Applevangelist b9be0341f5 Merge remote-tracking branch 'origin/master' into develop 2025-12-21 19:17:31 +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 28e08035f2 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Moose Development/Moose/Ops/EasyA2G.lua
#	Moose Development/Moose/Ops/EasyGCICAP.lua
2025-12-21 15:40:46 +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 3cac18f634 Merge remote-tracking branch 'origin/master' into develop 2025-12-21 12:50:04 +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 1fbfc8f039 Merge remote-tracking branch 'origin/master' into develop 2025-12-21 11:44:00 +01:00
Applevangelist af891f3fc2 #2479 Added S-300VM/S-300V4/S-400 SAM data 2025-12-21 11:43:29 +01:00
Thomas 47bb760d86 Merge pull request #2481 from FlightControl-Master/master
Merge
2025-12-21 09:26:19 +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 c24acd2681 Merge pull request #2478 from FlightControl-Master/master
Merge
2025-12-20 14:06:52 +01: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 b63fe40cc8 Merge pull request #2475 from FlightControl-Master/master
Merge from master
2025-12-18 10:20:06 +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 e41eec2238 Merge pull request #2473 from FlightControl-Master/master
Update Database.lua
2025-12-17 19:37:36 +01:00
Thomas 87b87fa2ca Update Database.lua 2025-12-17 19:34:06 +01:00
Applevangelist 3c996b6dd7 Merge remote-tracking branch 'origin/master' into develop 2025-12-17 11:32:28 +01:00
Applevangelist a39f39646d #MARKEROPS - Put case sensitivity switching into own functions 2025-12-17 11:31:55 +01:00
Thomas 2012e81acb Merge pull request #2471 from FlightControl-Master/master
Merge from master
2025-12-16 08:40:00 +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 4c841fd1d2 Merge pull request #2469 from FlightControl-Master/master
Merge from master
2025-12-15 17:41:37 +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 ef5c7e5972 Merge remote-tracking branch 'origin/master' into develop 2025-12-15 12:13:32 +01:00
Applevangelist 33ff723bd8 Further refinement for acoustic detection and smoke 2025-12-15 12:13:04 +01:00
Applevangelist 21c744b27a Merge remote-tracking branch 'origin/master' into develop 2025-12-15 09:41:13 +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 38b36c0344 Merge remote-tracking branch 'origin/master' into develop 2025-12-14 16:05:44 +01:00
Applevangelist b6a90d4633 #GROUP Added GROUP:SetFormation(Formation) 2025-12-14 16:05:18 +01:00
Applevangelist 5145dd14ea Merge remote-tracking branch 'origin/master' into develop 2025-12-14 14:37:36 +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 51f40081f3 docu header 2025-12-12 12:51:44 +01:00
Applevangelist 7e878b5702 Merge remote-tracking branch 'origin/develop' into develop 2025-12-12 10:51:58 +01:00
Applevangelist 804e0ed69d Docu fixes 2025-12-12 10:51:55 +01:00
Thomas 46c0d47175 Update build-docs.yml 2025-12-11 16:37:30 +01:00
Applevangelist 147df1498e Merge remote-tracking branch 'origin/master' into develop 2025-12-11 15:48:41 +01:00
Applevangelist f9410a9169 #EASYA2G - Initial release 2025-12-11 15:48:13 +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
Applevangelist 9b3aa423aa xx 2025-12-11 09:34:19 +01:00
Applevangelist 07876cb46c Merge remote-tracking branch 'origin/develop' into develop 2025-12-10 11:30:59 +01:00
Applevangelist de48701d15 Merge remote-tracking branch 'origin/master' into develop 2025-12-10 11:30:55 +01:00
Applevangelist c5678404d5 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Moose Development/Moose/Functional/PseudoATC.lua
2025-12-10 11:29:39 +01:00
Thomas 4e2dcb1c05 Merge pull request #2466 from FlightControl-Master/master
xx
2025-12-10 09:58:38 +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
Frank 60b561449b Merge branch 'master' into develop 2025-11-23 20:40:48 +01:00
Frank 88396b5a5e Merge pull request #2452 from FlightControl-Master/FF/MasterDev
AIRBASE name enums
2025-11-23 20:39:19 +01:00
Frank a0c5466f7f Update Airbase.lua
- Updated TheChannel map airbase enums
- Fixed some Umlaute in the Germany map
2025-11-23 20:35:12 +01:00
Thomas b62b218257 Merge pull request #2451 from FlightControl-Master/master
Merge from master
2025-11-23 19:39:38 +01:00
Thomas 7a3c23527f Merge pull request #2450 from shaji-Dev/master
[FIXED] SCENERY:GetVec2() and SCENERY:GetVec3() return nil if not registered with SceneryZone
2025-11-23 19:38:32 +01:00
Frank 2f002703ee Update Airbase.lua
- Updated Syria map airbase enums
2025-11-23 18:45:09 +01:00
Frank b0c6ed1d59 Update Airbase.lua
- Updated Sinai map airbase enums
2025-11-23 17:40:05 +01:00
Frank 950e97b6d7 Update Airbase.lua
- Updated Persian Gulf map airbase enums
2025-11-23 17:35:38 +01:00
Frank 6d54e5a47f Update Airbase.lua
- Updated Normand map airbase enums
2025-11-23 17:28:31 +01:00
Shafik 87d65123ba [FIXED] SCENERY:GetVec2() and SCENERY:GetVec3() return nil if not registered with Scenery Zone 2025-11-23 18:25:47 +02:00
Frank 35c8803f8b Update Airbase.lua
- Update Nevada map airbase enums
2025-11-23 17:19:18 +01:00
Frank d9c8542993 Update Airbase.lua
- Update of Mariana Island (WWII) airbase enums
2025-11-23 17:15:30 +01:00
Shafik d7a6809fec [FIXED] SCENERY:GetVec2() and SCENERY:GetVec3() return nil if not registered with Scenery Zone 2025-11-23 18:14:45 +02:00
Frank f3c38a4288 Update Airbase.lua
- Updated Kola map airbase enums
2025-11-23 16:46:23 +01:00
Frank 626b03c93a Update Airbase.lua
- Updated Iraq map airbase enums
2025-11-23 16:44:40 +01:00
Frank cf5c55209a Update Airbase
- Updated South Atlantic airbase enums
2025-11-23 16:36:20 +01:00
Frank 19921414cf Update Airbase.lua
- Update Caucasus Airbase Enums
2025-11-23 16:20:49 +01:00
Frank c4de2b6990 Update Airbase.lua
- Afghanistan Airbase Enums updated
2025-11-23 16:10:40 +01:00
Frank 7907e4b830 Airbase
- Added new Germany CW airbases
- Added function in UTILS to create airbase enums
2025-11-23 16:08:28 +01:00
Applevangelist 9e55118d3e #AUFTRAG/#OPS/#RESCUEHELO - make the NewRescueHelo AUFTRAG use Ops.Rescuehelo as basis. #2199 2025-11-22 16:18:28 +01:00
Thomas a5076eddba Merge pull request #2449 from FlightControl-Master/master
Merge from master
2025-11-22 15:24:16 +01:00
Thomas af11264841 Merge pull request #2447 from shaji-Dev/develop
[FIXED] attempt to index local 'asset' (a nil value)
2025-11-22 15:20:29 +01:00
Applevangelist 5955c2d61f #VARIOUS Small fixes 2025-11-22 15:17:26 +01:00
Shafik 95e2bf4a21 [FIXED] attempt to index local 'asset' (a nil value) 2025-11-22 13:37:22 +02:00
Shafik 6f22534b8d Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-11-22 13:35:11 +02:00
Applevangelist 5d1123e7df xx 2025-11-16 16:54:04 +01:00
Applevangelist 5929b7b0ed #COORDINATE
* Remove POINT_VEC2/3 classses
2025-11-16 16:53:59 +01:00
Applevangelist 0de95b1830 #image path 2025-11-15 16:58:56 +01:00
Applevangelist ccaf926c6d #FORMATION - move useable #FORMATION class from AI into functional
#AIRBOSS - add F-14A Early model support
2025-11-15 16:49:20 +01:00
Applevangelist 26fbae8672 #AIRBOSS Added early F-14A 2025-11-15 16:48:05 +01:00
Applevangelist cdbf1e147e Merge remote-tracking branch 'origin/master' into develop 2025-11-14 17:27:02 +01:00
Applevangelist 5089e7df25 xx 2025-11-14 17:26:24 +01:00
Thomas 1de19731d0 Merge pull request #2446 from FlightControl-Master/master
merge from master
2025-11-14 13:46:24 +01:00
Thomas 0c0d1cc794 Merge pull request #2445 from shaji-Dev/master
[ADDED] STATIC:GetVec2Cached() and STATIC:GetVec3Cached()
2025-11-14 13:45:22 +01:00
Shafik 21b968a9c6 [ADDED] STATIC:GetVec2Cached() and STATIC:GetVec3Cached() for statics considering respawns 2025-11-14 14:42:52 +02:00
Applevangelist d857e3acad #NAVIGATION - some fixes and additions 2025-11-14 13:23:57 +01:00
Thomas d7b0b3c898 Merge pull request #2444 from FlightControl-Master/master
Merge from master
2025-11-11 12:57:41 +01:00
Thomas 704bb8668f Merge pull request #2443 from FlightControl-Master/Applevangelist-DC-1
Update DynamicCargo.lua
2025-11-11 12:54:42 +01:00
Thomas 763bfb4bb1 Update DynamicCargo.lua
Added Destroy-Functionality
2025-11-11 12:41:30 +01:00
Shafik e43173de60 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-11-09 23:27:00 +02:00
Frank 16706cd483 Merge pull request #2442 from FlightControl-Master/FF/Ops
New Classes + OPS Improvements
2025-11-09 22:02:44 +01:00
Frank 505c86399c Merge branch 'develop' into FF/Ops 2025-11-09 21:46:36 +01:00
Frank 65ddff13f7 OPS Ammo
- Added guns/cannons differentiation to ammo count
2025-11-09 21:46:23 +01:00
Frank bedd105649 Update Auftrag.lua
- added NAVALENGAGEMENT to :NewFromTarget
2025-11-09 18:22:39 +01:00
Applevangelist c10e57ec3b Merge remote-tracking branch 'origin/master' into develop 2025-11-09 17:19:57 +01:00
Applevangelist 046bd37fd5 xx 2025-11-09 17:19:29 +01:00
Applevangelist 55c3e7aba6 Merge remote-tracking branch 'origin/master' into develop 2025-11-09 16:53:39 +01:00
Applevangelist fbf83b3aed #SET - Rationalize FilterCoalitions() and allow values like coalition.side.BLUE 2025-11-09 16:53:10 +01:00
Frank ea60795e84 Assets
- remove assets from warehouse if they are removed from cohort
2025-11-09 16:21:38 +01:00
Applevangelist 4a5e262695 Merge remote-tracking branch 'origin/master' into develop 2025-11-09 14:43:32 +01:00
Applevangelist 22097987dc #CTLD, #DYNAMICCARGO - C-130j-30 additions 2025-11-09 14:43:11 +01:00
Thomas 94617e573f Merge pull request #2441 from FlightControl-Master/master
Merge from master
2025-11-09 09:28:05 +01:00
Thomas 98d02d7532 Merge pull request #2440 from leka1986/patch-2
Update CTLD.lua
2025-11-09 09:24:48 +01:00
leka1986 7b44e7a3f6 Update CTLD.lua
Removed quantity menu if there is only one set.
2025-11-09 02:16:02 +01:00
leka1986 aa4cf37aed Clean up comments in CTLD.lua
Removed unnecessary comment lines in CTLD.lua.
2025-11-08 02:52:23 +01:00
leka1986 caf2b4e736 Update CTLD.lua
Added ability to drop "Sets" from 2 and up will be sets, else it will be like before.

Changed _PackCratesNearby to handle all cargo within the range instead of the nearest.
2025-11-08 02:47:56 +01:00
Frank da516a0167 Merge pull request #2439 from shaji-Dev/master
[FIXED] `dcsgroup:getUnit(1)` nil pointer
2025-11-07 21:53:26 +01:00
Frank 1267a64fcb Ops Navy Update 2025-11-06 23:26:30 +01:00
Shafik 17d1cf673d [ADDED] IsRed, IsBlue and IsNeutral to IDENTIFIABLE 2025-11-06 12:23:13 +02:00
Frank 74c3b9fbcb Update Auftrag.lua 2025-11-05 22:05:02 +01:00
Shafik 1fcd70b0d0 Merge remote-tracking branch 'origin/master' 2025-11-05 12:36:03 +02:00
Shafik 832941494b [FIXED] dcsgroup:getUnit(1) nil pointer 2025-11-05 12:35:55 +02:00
Shafik 581d812cd4 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-11-05 07:47:04 +02:00
Shafik 56dbf6bbe1 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-11-05 07:46:28 +02:00
Thomas d5792fb02a Merge pull request #2438 from FlightControl-Master/master
Merge
2025-11-05 06:23:06 +01:00
Thomas 0e82cb977e Merge pull request #2437 from leka1986/patch-2
Update CTLD.lua
2025-11-05 06:22:03 +01:00
leka1986 e9237146a3 Update CTLD.lua
Fixed a bug. 

When dropping crates, while choosing "Drop and build" The first drop will put the crate into a crate that can be loaded but a second later, that crate will be cleaned up due to build. This makes sure that the crates are not in the "load crates" list.
2025-11-04 20:09:48 +01:00
Thomas b72899b500 Merge pull request #2436 from shaji-Dev/master
[ADDED] Complete list of DCS warehouse equipment
2025-11-04 13:24:09 +01:00
Shafik 2f39e38264 [ADDED] CTLD. option to load back crates from save file. 2025-11-04 11:32:24 +02:00
Shafik db54672fbd [ADDED] CTLD. option to load back crates from save file. 2025-11-04 11:30:59 +02:00
Shafik 30d9a7d703 [ADDED] Complete list of DCS warehouse equipment 2025-11-04 10:27:16 +02:00
Thomas 4ade4c78ba Merge pull request #2435 from FlightControl-Master/master
Merge
2025-11-03 06:30:00 +01:00
Thomas 5a088a740e Merge pull request #2434 from nasgroup94/master
more T-45 AOA adjustments
2025-11-03 06:29:14 +01:00
nasgroup94 be220b6c1d more T-45 AOA adjustments 2025-11-02 19:48:16 -05:00
Frank e194d6073f OPS improvements
- AUFTRAG success is checked based on really alive targets
- AUFTRAG is done if all groups are done with the mission
2025-11-02 22:45:52 +01:00
Thomas 38858cd01f Merge pull request #2433 from FlightControl-Master/master
Merge from Master
2025-11-02 18:05:46 +01:00
Thomas 645c4a4dbc Merge pull request #2432 from FlightControl-Master/Applevangelist-patch-1
Update CTLD.lua
2025-11-02 17:57:09 +01:00
Thomas c2a630dabd Update CTLD.lua
Cargo counters by @lekaa
2025-11-02 17:56:55 +01:00
Frank 3c57177a11 Update Radios.lua
- Improved airbase identification by UID
2025-10-31 22:55:37 +01:00
Frank 7cc18b8082 Nav updates 2025-10-31 19:13:36 +01:00
Frank bef805d694 Update Pathline.lua 2025-10-31 14:18:38 +01:00
Frank 3479d08193 VECTOR + PATHLINE 2025-10-31 14:16:20 +01:00
Shafik 94b8835340 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-10-30 23:52:55 +02:00
Frank e3cee8dafe VECTOR 2025-10-28 23:07:59 +01:00
Frank 3686e0de63 Vector 2025-10-26 23:00:32 +01:00
Frank 2931b32ce6 Vector 0.0.3 2025-10-26 20:54:52 +01:00
Frank 42221f406a Update Radios.lua 2025-10-26 15:19:34 +01:00
Shafik ebbe64f279 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop 2025-10-26 06:52:04 +02:00
Frank 654f752ca0 Merge branch 'develop' into FF/Ops 2025-10-25 21:17:01 +02:00
Shafik dcae8eb7f3 Merge branch 'develop' of https://github.com/shaji-Dev/MOOSE into develop
# Conflicts:
#	Moose Development/Moose/Ops/CTLD.lua
2025-10-20 14:57:15 +03:00
Shafik 3fa5c92058 [ADDED] UNIT:IsEWR() 2025-10-20 14:57:01 +03:00
Frank 5a92fb1f65 Merge branch 'develop' into FF/Ops 2025-09-28 20:48:52 +02:00
Frank 1ddb2aaec0 Merge branch 'develop' into FF/Ops 2025-08-25 20:53:40 +02:00
Frank 8d6d4b3765 Merge branch 'FF/MasterDevel' into FF/Ops 2025-06-23 22:29:30 +02:00
Frank 7ddb72885d Merge branch 'develop' into FF/Ops 2025-06-23 22:29:20 +02:00
Frank 1d04f7c945 Merge branch 'develop' into FF/Ops 2025-06-10 21:58:38 +02:00
Frank baa3f3234e Merge branch 'develop' into FF/Ops 2025-04-18 22:04:07 +02:00
Frank ab5a4c43c3 Merge branch 'develop' into FF/Ops 2025-04-17 21:40:58 +02:00
Frank ce61f454bf Update Beacons.lua
- Added excludelist to GetClosestBeacon function
2025-04-17 20:34:46 +02:00
Frank 4772dafe7f Update Beacons.lua
- Fixed bug in counting beacons
- Added option to mark certain beacon types
- Improved maker text
2025-04-15 22:06:53 +02:00
Frank 629925e2d8 Update Towns.lua 2025-04-14 22:42:29 +02:00
Frank 17f672dad4 Towns
- Added new Class for towns
2025-04-14 15:49:39 +02:00
Frank 0a9717a8c2 RADIOS
- Added new class RADIOS
2025-04-13 23:14:19 +02:00
Frank e0049bea2b Update Beacons.lua
- Added channel
- Added scenery
2025-04-12 21:18:19 +02:00
Frank 73525feb68 Update Beacons.lua
- Added user functions
- Fixed bug with incorrect position
2025-04-12 11:14:01 +02:00
Frank 7d3bffcfef Naviation
- Added beacons class
- Added navpoints class
- Added vector class
2025-04-09 22:36:26 +02:00
Frank 5b76ec6b99 Merge branch 'develop' into FF/Ops 2025-04-09 21:17:49 +02:00
Frank eeca95c77f Merge branch 'develop' into FF/Ops 2025-01-02 20:49:06 +01:00
Frank e406fb0c88 Update Brigade.lua 2024-12-29 21:02:56 +01:00
123 changed files with 13564 additions and 45255 deletions
+7 -7
View File
@@ -3,7 +3,7 @@ name: Moose-Docs
on:
push:
branches:
- master
- master-ng
- develop
paths:
- 'Moose Setup/**/*.lua'
@@ -77,11 +77,11 @@ jobs:
- name: Install markdown (prereq for LuaDocumentor)
run: |
sudo luarocks install markdown 0.32-2
sudo luarocks install markdown 0.33-1
- name: Install penlight (prereq for LuaDocumentor)
run: |
sudo luarocks install penlight 1.11.0-1
sudo luarocks install penlight 1.14.0-3
- name: Install metalua-compiler (prereq for LuaDocumentor)
run: |
@@ -113,7 +113,7 @@ jobs:
- name: Check replacement of head tag
run: |
head -10 ${{ github.workspace }}/build/doc/AI.AI_A2A_Cap.html
head -10 ${{ github.workspace }}/build/doc/Core.Menu.html
#########################################################################
# Push to MOOSE_DOCS
@@ -122,7 +122,7 @@ jobs:
shell: bash
id: set_doc_repo
run: |
if [[ $GITHUB_REF == 'refs/heads/master' ]]; then
if [[ $GITHUB_REF == 'refs/heads/master-ng' ]]; then
echo "docrepo=MOOSE_DOCS" >> "$GITHUB_OUTPUT"
else
echo "docrepo=MOOSE_DOCS_DEVELOP" >> "$GITHUB_OUTPUT"
@@ -134,7 +134,7 @@ jobs:
repository: ${{ github.repository_owner }}/${{ steps.set_doc_repo.outputs.docrepo }}
path: './build/MOOSE_DOCS'
fetch-depth: 0
ref: 'master'
ref: 'master-ng'
token: ${{ secrets.BOT_TOKEN }}
- name: Delete folder to remove deleted files
@@ -155,7 +155,7 @@ jobs:
git config user.email "MooseBotter@users.noreply.github.com"
git add .
git commit --allow-empty -m "Auto commit by GitHub Actions Workflow"
git push --set-upstream origin master
git push --set-upstream origin 'master-ng'
working-directory: ${{ github.workspace }}/build/MOOSE_DOCS
+2 -2
View File
@@ -3,7 +3,7 @@ name: Moose-Includes
on:
push:
branches:
- master
- master-ng
- develop
- Apple/Develop
@@ -105,7 +105,7 @@ jobs:
if: ${{ vars.SKIP_LUACHECK != true }}
continue-on-error: true
run: |
luacheck --std=lua51c --config=.luacheckrc -gurasqq "Moose Development/Moose"
luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
- name: Run LuaSrcDiet
run: |
+1 -1
View File
@@ -8,7 +8,7 @@ name: Deploy Jekyll site to Pages
on:
push:
branches: ["master"]
branches: ["master-ng"]
paths:
- 'docs/**'
- '.github/workflows/gh-pages.yml'
-220
View File
@@ -1,220 +0,0 @@
--- **AI** - Models the process of Combat Air Patrol (CAP) for airplanes.
--
-- This is a class used in the @{AI.AI_A2A_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2A_Cap
-- @image AI_Combat_Air_Patrol.JPG
-- @type AI_A2A_CAP
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
--- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_CAP\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_CAP\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG)
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1. AI_A2A_CAP constructor
--
-- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object.
--
-- ## 2. AI_A2A_CAP is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 2.1 AI_A2A_CAP States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_A2A_CAP Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_A2A_CAP.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_A2A_CAP.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{#AI_A2A_CAP.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{Core.Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{#AI_A2A_CAP.SetEngageZone}() to define that Zone.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2A_CAP
AI_A2A_CAP = {
ClassName = "AI_A2A_CAP",
}
--- Creates a new AI_A2A_CAP object
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @return #AI_A2A_CAP
function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
-- Multiple inheritance ... :-)
local AI_Air = AI_AIR:New( AICap )
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
return self
end
--- Creates a new AI_A2A_CAP object
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2A_CAP
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To )
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_A2A_CAP self
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
-- @return #AI_A2A_CAP self
function AI_A2A_CAP:SetEngageZone( EngageZone )
self:F2()
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- Set the Engage Range when the AI will engage with airborne enemies.
-- @param #AI_A2A_CAP self
-- @param #number EngageRange The Engage Range.
-- @return #AI_A2A_CAP self
function AI_A2A_CAP:SetEngageRange( EngageRange )
self:F2()
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- Evaluate the attack and create an AttackUnitTask list.
-- @param #AI_A2A_CAP self
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
-- @param #number EngageAltitude The altitude to engage the targets.
-- @return #AI_A2A_CAP self
function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
local AttackUnitTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
-- Maybe the detected set also contains
self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
end
end
return AttackUnitTasks
end
File diff suppressed because it is too large Load Diff
-149
View File
@@ -1,149 +0,0 @@
--- **AI** - Models the process of Ground Controlled Interception (GCI) for airplanes.
--
-- This is a class used in the @{AI.AI_A2A_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2A_Gci
-- @image AI_Ground_Control_Intercept.JPG
-- @type AI_A2A_GCI
-- @extends AI.AI_A2A#AI_A2A
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- This cycle will continue.
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ## 1. AI_A2A_GCI constructor
--
-- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object.
--
-- ## 2. AI_A2A_GCI is a FSM
--
-- ![Process](..\Presentations\AI_GCI\Dia2.JPG)
--
-- ### 2.1 AI_A2A_GCI States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_A2A_GCI Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_A2A_GCI.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_A2A_GCI.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2A_GCI
AI_A2A_GCI = {
ClassName = "AI_A2A_GCI",
}
--- Creates a new AI_A2A_GCI object
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @return #AI_A2A_GCI
function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local AI_Air = AI_AIR:New( AIIntercept )
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
return self
end
--- Creates a new AI_A2A_GCI object
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @return #AI_A2A_GCI
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To )
end
--- Evaluate the attack and create an AttackUnitTask list.
-- @param #AI_A2A_GCI self
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
-- @param #number EngageAltitude The altitude to engage the targets.
-- @return #AI_A2A_GCI self
function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
local AttackUnitTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
-- Maybe the detected set also contains
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
end
end
return AttackUnitTasks
end
@@ -1,411 +0,0 @@
--- **AI** - Models the process of air patrol of airplanes.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2A_Patrol
-- @image AI_Air_Patrolling.JPG
-- @type AI_A2A_PATROL
-- @extends AI.AI_A2A#AI_A2A
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_PATROL\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_PATROL\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia9.JPG)
--
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
-- use derived AI_ classes to model AI offensive or defensive behaviour.
--
-- ![Process](..\Presentations\AI_PATROL\Dia10.JPG)
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_PATROL\Dia11.JPG)
--
-- ## 1. AI_A2A_PATROL constructor
--
-- * @{#AI_A2A_PATROL.New}(): Creates a new AI_A2A_PATROL object.
--
-- ## 2. AI_A2A_PATROL is a FSM
--
-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG)
--
-- ### 2.1. AI_A2A_PATROL States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Returning** ( Group ): The AI is returning to Base.
-- * **Stopped** ( Group ): The process is stopped.
-- * **Crashed** ( Group ): The AI has crashed or is dead.
--
-- ### 2.2. AI_A2A_PATROL Events
--
-- * **Start** ( Group ): Start the process.
-- * **Stop** ( Group ): Stop the process.
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
-- * **RTB** ( Group ): Route the AI to the home base.
-- * **Detect** ( Group ): The AI is detecting targets.
-- * **Detected** ( Group ): The AI has detected new targets.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Set or Get the AI controllable
--
-- * @{#AI_A2A_PATROL.SetControllable}(): Set the AIControllable.
-- * @{#AI_A2A_PATROL.GetControllable}(): Get the AIControllable.
--
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
--
-- * @{#AI_A2A_PATROL.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
-- * @{#AI_A2A_PATROL.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
--
-- ## 5. Manage the detection process of the AI controllable
--
-- The detection process of the AI controllable can be manipulated.
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
--
-- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
-- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
--
-- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
--
-- The detection can be filtered to potential targets in a specific zone.
-- Use the method @{#AI_A2A_PATROL.SetDetectionZone}() to set the zone where targets need to be detected.
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
-- according the weather conditions.
--
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
--
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
-- while a new AI is targeted to the AI_A2A_PATROL.
-- Once the time is finished, the old AI will return to the base.
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
--
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
--
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2A_PATROL
AI_A2A_PATROL = {
ClassName = "AI_A2A_PATROL",
}
--- Creates a new AI_A2A_PATROL object
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The patrol group object.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to BARO
-- @return #AI_A2A_PATROL self
-- @usage
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
-- PatrolZone = ZONE:New( 'PatrolZone' )
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air = AI_AIR:New( AIPatrol )
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
-- defafult PatrolAltType to "BARO" if not specified
self.PatrolAltType = PatrolAltType or "BARO"
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnAfterPatrol
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] Patrol
-- @param #AI_A2A_PATROL self
--- Asynchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] __Patrol
-- @param #AI_A2A_PATROL self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
--- OnBefore Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnBeforeRoute
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnAfterRoute
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Route.
-- @function [parent=#AI_A2A_PATROL] Route
-- @param #AI_A2A_PATROL self
--- Asynchronous Event Trigger for Event Route.
-- @function [parent=#AI_A2A_PATROL] __Route
-- @param #AI_A2A_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
return self
end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_A2A_PATROL self
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_A2A_PATROL self
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
end
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @return #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__Route( 1 )
AIPatrol:OnReSpawn(
function( PatrolGroup )
self:__Reset( 1 )
self:__Route( 5 )
end
)
end
--- This static method is called from the route path within the last task at the last waypoint of the AIPatrol.
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
-- @param #AI_A2A_PATROL Fsm The FSM.
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
if AIPatrol and AIPatrol:IsAlive() then
Fsm:Route()
end
end
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
self:F2()
-- When RTB, don't allow anymore the routing.
if From == "RTB" then
return
end
if AIPatrol and AIPatrol:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIPatrol:GetCoordinate()
-- Random altitude.
local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
-- Random speed in km/h.
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
-- First waypoint is current position.
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
if self.racetrack then
-- Random heading.
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
-- Random leg length.
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
-- Random duration if any.
local duration = self.racetrackdurationmin
if self.racetrackdurationmax then
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
end
-- CAP coordinate.
local c0=self.PatrolZone:GetRandomCoordinate()
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
end
-- Race track points.
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
self:SetTargetDistance(c0) -- For RTB status check
-- Debug:
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
--c1:MarkToAll("Race track c1")
--c2:MarkToAll("Race track c2")
-- Task to orbit.
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
-- Task function to redo the patrol at other random position.
local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self)
-- Controlled task with task condition.
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
-- Second waypoint
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
else
-- Target coordinate.
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE
ToTargetCoord:SetAltitude(altitude)
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point")
end
-- ROE
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
-- Patrol.
AIPatrol:Route( PatrolRoute, 0.5)
end
end
-98
View File
@@ -1,98 +0,0 @@
--- **AI** - Models the process of air to ground BAI engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_BAI
-- @image AI_Air_To_Ground_Engage.JPG
-- @type AI_A2G_BAI
-- @extends AI.AI_A2A_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends.
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2G_BAI
AI_A2G_BAI = {
ClassName = "AI_A2G_BAI",
}
--- Creates a new AI_A2G_BAI object
-- @param #AI_A2G_BAI self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_BAI
function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air = AI_AIR:New( AIGroup )
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local self = BASE:Inherit( self, AI_Air_Engage )
return self
end
--- Creates a new AI_A2G_BAI object
-- @param #AI_A2G_BAI self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_BAI
function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
end
--- Evaluate the attack and create an AttackUnitTask list.
-- @param #AI_A2G_BAI self
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
-- @param #number EngageAltitude The altitude to engage the targets.
-- @return #AI_A2G_BAI self
function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
local AttackUnitTasks = {}
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
self:T( { "BAI Unit:", AttackUnit:GetName() } )
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
end
end
end
return AttackUnitTasks
end
-98
View File
@@ -1,98 +0,0 @@
--- **AI** - Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_CAS
-- @image AI_Air_To_Ground_Engage.JPG
-- @type AI_A2G_CAS
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL TODO: Documentation. This class does not exist, unable to determine what it extends.
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2G_CAS
AI_A2G_CAS = {
ClassName = "AI_A2G_CAS",
}
--- Creates a new AI_A2G_CAS object
-- @param #AI_A2G_CAS self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_CAS
function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air = AI_AIR:New( AIGroup )
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local self = BASE:Inherit( self, AI_Air_Engage )
return self
end
--- Creates a new AI_A2G_CAS object
-- @param #AI_A2G_CAS self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_CAS
function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
end
--- Evaluate the attack and create an AttackUnitTask list.
-- @param #AI_A2G_CAS self
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
-- @param #number EngageAltitude The altitude to engage the targets.
-- @return #AI_A2G_CAS self
function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
local AttackUnitTasks = {}
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
self:T( { "CAS Unit:", AttackUnit:GetName() } )
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
end
end
end
return AttackUnitTasks
end
File diff suppressed because it is too large Load Diff
-133
View File
@@ -1,133 +0,0 @@
--- **AI** - Models the process of air to ground SEAD engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_SEAD
-- @image AI_Air_To_Ground_Engage.JPG
-- @type AI_A2G_SEAD
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- This cycle will continue.
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ## 1. AI_A2G_SEAD constructor
--
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
--
-- ## 3. Set the Range of Engagement
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_A2G_SEAD
AI_A2G_SEAD = {
ClassName = "AI_A2G_SEAD",
}
--- Creates a new AI_A2G_SEAD object
-- @param #AI_A2G_SEAD self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_SEAD
function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air = AI_AIR:New( AIGroup )
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
local self = BASE:Inherit( self, AI_Air_Engage )
return self
end
--- Creates a new AI_A2G_SEAD object
-- @param #AI_A2G_SEAD self
-- @param Wrapper.Group#GROUP AIGroup
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_SEAD
function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
end
--- Evaluate the attack and create an AttackUnitTask list.
-- @param #AI_A2G_SEAD self
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
-- @param #number EngageAltitude The altitude to engage the targets.
-- @return #AI_A2G_SEAD self
function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
local AttackUnitTasks = {}
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do
if AttackUnit then
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
local HasRadar = AttackUnit:HasSEAD()
if HasRadar then
self:F( { "SEAD Unit:", AttackUnit:GetName() } )
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
end
end
end
end
return AttackUnitTasks
end
-841
View File
@@ -1,841 +0,0 @@
--- **AI** - Models the process of AI air operations.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air
-- @image MOOSE.JPG
---
-- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # 1) AI_AIR constructor
--
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
--
-- # 2) AI_AIR is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
--
-- ## 2.1) AI_AIR States.
--
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_AIR Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #AI_AIR
AI_AIR = {
ClassName = "AI_AIR",
}
AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI.
--- Creates a new AI_AIR process.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
-- @return #AI_AIR
function AI_AIR:New( AIGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
self:SetControllable( AIGroup )
self:SetStartState( "Stopped" )
self:AddTransition( "*", "Queue", "Queued" )
self:AddTransition( "*", "Start", "Started" )
--- Start Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Start Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Start Trigger for AI_AIR
-- @function [parent=#AI_AIR] Start
-- @param #AI_AIR self
--- Start Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Start
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Stop", "Stopped" )
--- OnLeave Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnLeaveStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnEnterStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- OnBefore Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnBeforeStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnAfterStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] Stop
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] __Stop
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnBeforeStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnAfterStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] Status
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] __Status
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnBeforeRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnAfterRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] RTB
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] __RTB
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnLeaveReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnEnterReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Refuel Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
--- Refuel Trigger for AI_AIR
-- @function [parent=#AI_AIR] Refuel
-- @param #AI_AIR self
--- Refuel Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Refuel
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "Hold", "Holding" )
self:AddTransition( "*", "Home", "Home" )
self:AddTransition( "*", "LostControl", "LostControl" )
self:AddTransition( "*", "Fuel", "Fuel" )
self:AddTransition( "*", "Damaged", "Damaged" )
self:AddTransition( "*", "Eject", "*" )
self:AddTransition( "*", "Crash", "Crashed" )
self:AddTransition( "*", "PilotDead", "*" )
self.IdleCount = 0
self.RTBSpeedMaxFactor = 0.6
self.RTBSpeedMinFactor = 0.5
return self
end
-- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff()
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_AIR:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
function AI_AIR:GetDispatcher()
return self.Dispatcher
end
function AI_AIR:SetTargetDistance( Coordinate )
local CurrentCoord = self.Controllable:GetCoordinate()
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance
end
function AI_AIR:ClearTargetDistance()
self.TargetDistance = nil
self.ClosestTargetDistance = nil
end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_AIR self
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_AIR self
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
end
--- Sets (modifies) the minimum and maximum RTB speed of the patrol.
-- @param #AI_AIR self
-- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_AIR self
function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed )
self:F( { RTBMinSpeed, RTBMaxSpeed } )
self.RTBMinSpeed = RTBMinSpeed
self.RTBMaxSpeed = RTBMaxSpeed
end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_AIR self
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_AIR self
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
end
--- Sets the home airbase.
-- @param #AI_AIR self
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
-- @return #AI_AIR self
function AI_AIR:SetHomeAirbase( HomeAirbase )
self:F2( { HomeAirbase } )
self.HomeAirbase = HomeAirbase
end
--- Sets to refuel at the given tanker.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
-- @return #AI_AIR self
function AI_AIR:SetTanker( TankerName )
self:F2( { TankerName } )
self.TankerName = TankerName
end
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
-- @param #AI_AIR self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_AIR self
function AI_AIR:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_AIR self
-- @return #AI_AIR self
function AI_AIR:SetStatusOff()
self:F2()
self.CheckStatus = false
end
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_AIR.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_AIR self
-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_AIR self
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
self.FuelThresholdPercentage = FuelThresholdPercentage
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
return self
end
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on.
-- Therefore, when the damage threshold is reached,
-- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
-- @param #AI_AIR self
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_AIR self
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_AIR self
-- @return #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR:onafterStart( Controllable, From, Event, To )
self:__Status( 10 ) -- Check status status every 30 seconds.
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
self:HandleEvent( EVENTS.Crash, self.OnCrash )
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
end
--- Coordinates the approriate returning action.
-- @param #AI_AIR self
-- @return #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR:onafterReturn( Controllable, From, Event, To )
self:__RTB( self.TaskDelay )
end
-- @param #AI_AIR self
function AI_AIR:onbeforeStatus()
return self.CheckStatus
end
-- @param #AI_AIR self
function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive() then
local RTB = false
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if DistanceFromHomeBase > self.DisengageRadius then
self:T( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
end
-- I think this code is not requirement anymore after release 2.5.
-- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
-- if DistanceFromHomeBase < 5000 then
-- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
-- self:Home( "Destroy" )
-- end
-- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then
local Fuel = self.Controllable:GetFuelMin()
-- If the fuel in the controllable is below the threshold percentage,
-- then send for refuel in case of a tanker, otherwise RTB.
if Fuel < self.FuelThresholdPercentage then
if self.TankerName then
self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
self:Fuel()
RTB = true
end
else
end
end
if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then
RTB = true
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
local InitialLife = self.Controllable:GetLife0()
-- If the group is damaged, then RTB.
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:T( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged()
RTB = true
self:SetStatusOff()
end
-- Check if planes went RTB and are out of control.
-- We only check if planes are out of control, when they are in duty.
if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and
not self:Is( "Stopped" ) and
not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and
not self:Is( "Home" ) then
if self.IdleCount >= 10 then
if Damage ~= InitialLife then
self:Damaged()
else
self:T( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
else
self.IdleCount = self.IdleCount + 1
end
end
else
self.IdleCount = 0
end
if RTB == true then
self:__RTB( self.TaskDelay )
end
if not self:Is("Home") then
self:__Status( 10 )
end
end
end
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBRoute( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:RTB()
end
end
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( Fsm.TaskDelay )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
end
end
--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6.
-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket).
-- A random value in this bracket is then applied in the waypoint routing generation.
-- @param #AI_AIR self
-- @param #number MinFactor Lower bracket factor. Defaults to 0.5.
-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6.
-- @return #AI_AIR self
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
self.RTBSpeedMaxFactor = MaxFactor or 0.6
self.RTBSpeedMinFactor = MinFactor or 0.5
return self
end
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
if AIGroup and AIGroup:IsAlive() then
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
--AIGroup:ClearTasks()
AIGroup:OptionProhibitAfterburner(true)
local EngageRoute = {}
--- Calculate the target route point.
local FromCoord = AIGroup:GetCoordinate()
if not FromCoord then return end
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
local ToTargetVec3 = ToTargetCoord:GetVec3()
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
local RTBSpeedMax = AIGroup:GetSpeedMax()
local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6
local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5
self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor)
end
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
local ToAirbaseCoord = ToTargetCoord2
if Distance < 5000 then
self:T( "RTB and near the airbase!" )
self:Home()
return
end
if not AIGroup:InAir() == true then
self:T( "Not anymore in the air, considered Home." )
self:Home()
return
end
--- Create a route point of type air.
local FromRTBRoutePoint = FromCoord:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
RTBSpeed,
true
)
--- Create a route point of type air.
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
RTBSpeed,
true
)
EngageRoute[#EngageRoute+1] = FromRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
--- NOW ROUTE THE GROUP!
AIGroup:Route( EngageRoute, self.TaskDelay )
end
end
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:T( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
end
end
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:T( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local Coordinate = AIGroup:GetCoordinate()
if Coordinate == nil then return end
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed, Coordinate )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_AIR", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
end
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.Resume( AIGroup, Fsm )
AIGroup:T( { "AI_AIR.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( Fsm.TaskDelay )
end
end
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
if AIGroup and AIGroup:IsAlive() then
-- Get tanker group.
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then
self:T( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
local RefuelRoute = {}
--- Calculate the target route point.
local FromRefuelCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true)
--- Create a route point of type air. NOT used!
local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
-- Get Class name for .Resume function
local classname=self:GetClassName()
-- AI_A2A_CAP can call this function but does not have a .Resume function. Try to fix.
if classname=="AI_A2A_CAP" then
classname="AI_AIR_PATROL"
end
env.info("FF refueling classname="..classname)
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, self.TaskDelay )
else
-- No tanker defined ==> RTB!
self:RTB()
end
end
end
-- @param #AI_AIR self
function AI_AIR:onafterDead()
self:SetStatusOff()
end
-- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
if #self.Controllable:GetUnits() == 1 then
self:__Crash( self.TaskDelay, EventData )
end
end
end
-- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnEjection( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__Eject( self.TaskDelay, EventData )
end
end
-- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnPilotDead( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__PilotDead( self.TaskDelay, EventData )
end
end
File diff suppressed because it is too large Load Diff
@@ -1,595 +0,0 @@
--- **AI** - Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air_Engage
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_AIR_ENGAGE
-- @extends AI.AI_AIR#AI_AIR
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- This cycle will continue.
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ## 1. AI_AIR_ENGAGE constructor
--
-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object.
--
-- ## 2. Set the Zone of Engagement
--
-- An optional @{Core.Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_CAP#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_AIR_ENGAGE
AI_AIR_ENGAGE = {
ClassName = "AI_AIR_ENGAGE",
}
--- Creates a new AI_AIR_ENGAGE object
-- @param #AI_AIR_ENGAGE self
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
-- @param Wrapper.Group#GROUP AIGroup The AI group.
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
-- @return #AI_AIR_ENGAGE
function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE
self.Accomplished = false
self.Engaging = false
local SpeedMax = AIGroup:GetSpeedMax()
self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5
self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75
self.EngageFloorAltitude = EngageFloorAltitude or 1000
self.EngageCeilingAltitude = EngageCeilingAltitude or 1500
self.EngageAltType = EngageAltType or "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event EngageRoute.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event EngageRoute.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event EngageRoute.
-- @function [parent=#AI_AIR_ENGAGE] EngageRoute
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event EngageRoute.
-- @function [parent=#AI_AIR_ENGAGE] __EngageRoute
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_AIR_ENGAGE] Engage
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_AIR_ENGAGE] __Engage
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterFired
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_AIR_ENGAGE] Fired
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_AIR_ENGAGE] __Fired
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_AIR_ENGAGE] Destroy
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_AIR_ENGAGE] __Destroy
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_AIR_ENGAGE] Abort
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_AIR_ENGAGE] __Abort
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_AIR_ENGAGE] Accomplish
-- @param #AI_AIR_ENGAGE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_AIR_ENGAGE] __Accomplish
-- @param #AI_AIR_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
return self
end
--- onafter event handler for Start event.
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To )
self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To )
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- onafter event handler for Engage event.
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To )
-- TODO: This function is overwritten below!
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- onbefore event handler for Engage event.
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
end
return true
end
--- onafter event handler for Abort event.
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
self:Return()
end
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
self.Accomplished = true
--self:SetDetectionOff()
end
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
-- @param #AI_AIR_ENGAGE self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_ENGAGE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( self.TaskDelay, EventData )
end
end
end
-- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
Fsm:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive() then
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
end
end
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:T( { DefenderGroup, From, Event, To, AttackSetUnit } )
local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:CountAlive()
if AttackCount > 0 then
if DefenderGroup:IsAlive() then
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
-- Determine the distance to the target.
-- If it is less than 10km, then attack without a route.
-- Otherwise perform a route attack.
local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3()
if TargetCoord == nil then
self:Return()
return
end
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
if TargetDistance <= EngageDistance * 9 then
--self:T(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
self:__Engage( 0.1, AttackSetUnit )
else
--self:T(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
local EngageRoute = {}
local AttackTasks = {}
--- Calculate the target route point.
local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = ToWP
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
DefenderGroup:OptionROEReturnFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
end
end
else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:T( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
end
end
-- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
Fsm:T(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive() then
local delay=Fsm.TaskDelay or 0.1
Fsm:__Engage(delay, AttackSetUnit)
end
end
-- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:CountAlive()
self:T({AttackCount = AttackCount})
if AttackCount > 0 then
if DefenderGroup and DefenderGroup:IsAlive() then
local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3()
if not TargetCoord then
self:Return()
return
end
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
local EngageRoute = {}
local AttackTasks = {}
local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = ToWP
-- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task!
if TargetDistance <= EngageDistance * 9 then
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
if #AttackUnitTasks == 0 then
self:T( DefenderGroupName .. ": No valid targets found -> Going RTB")
self:Return()
return
else
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
self:T(text)
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat()
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks )
end
end
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
end
else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:T( DefenderGroupName .. ": No targets found -> returning.")
self:Return()
return
end
end
-- @param Wrapper.Group#GROUP AIEngage
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
AIEngage:F( { "Resume:", AIEngage:GetName() } )
if AIEngage and AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay or 0.1 )
Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit )
end
end
@@ -1,393 +0,0 @@
--- **AI** - Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air_Patrol
-- @image AI_Air_To_Ground_Patrol.JPG
-- @type AI_AIR_PATROL
-- @extends AI.AI_Air#AI_AIR
--- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_CAP\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_CAP\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG)
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1. AI_AIR_PATROL constructor
--
-- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object.
--
-- ## 2. AI_AIR_PATROL is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 2.1 AI_AIR_PATROL States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_AIR_PATROL Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_AIR_PATROL
AI_AIR_PATROL = {
ClassName = "AI_AIR_PATROL",
}
--- Creates a new AI_AIR_PATROL object
-- @param #AI_AIR_PATROL self
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
-- @param Wrapper.Group#GROUP AIGroup The AI group.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO.
-- @return #AI_AIR_PATROL
function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL
local SpeedMax = AIGroup:GetSpeedMax()
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude or 1000
self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500
self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5
self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrol
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Patrol.
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrol
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_AIR_PATROL] Patrol
-- @param #AI_AIR_PATROL self
--- Asynchronous Event Trigger for Event Patrol.
-- @function [parent=#AI_AIR_PATROL] __Patrol
-- @param #AI_AIR_PATROL self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
--- OnBefore Transition Handler for Event PatrolRoute.
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event PatrolRoute.
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event PatrolRoute.
-- @function [parent=#AI_AIR_PATROL] PatrolRoute
-- @param #AI_AIR_PATROL self
--- Asynchronous Event Trigger for Event PatrolRoute.
-- @function [parent=#AI_AIR_PATROL] __PatrolRoute
-- @param #AI_AIR_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
return self
end
--- Set the Engage Range when the AI will engage with airborne enemies.
-- @param #AI_AIR_PATROL self
-- @param #number EngageRange The Engage Range.
-- @return #AI_AIR_PATROL self
function AI_AIR_PATROL:SetEngageRange( EngageRange )
self:F2()
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone.
-- @param #AI_AIR_PATROL self
-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m.
-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m.
-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North.
-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North.
-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage.
-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage.
-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading.
-- @return #AI_AIR_PATROL self
function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates)
self.racetrack=true
self.racetracklegmin=LegMin or 10000
self.racetracklegmax=LegMax or 15000
self.racetrackheadingmin=HeadingMin or 0
self.racetrackheadingmax=HeadingMax or 180
self.racetrackdurationmin=DurationMin
self.racetrackdurationmax=DurationMax
if self.racetrackdurationmax and not self.racetrackdurationmin then
self.racetrackdurationmin=self.racetrackdurationmax
end
self.racetrackcapcoordinates=CapCoordinates
end
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_AIR_PATROL self
-- @return #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__PatrolRoute( self.TaskDelay )
AIPatrol:OnReSpawn(
function( PatrolGroup )
self:__Reset( self.TaskDelay )
self:__PatrolRoute( self.TaskDelay )
end
)
end
--- This static method is called from the route path within the last task at the last waypoint of the AIPatrol.
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
-- @param #AI_AIR_PATROL Fsm The FSM.
function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm )
AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } )
if AIPatrol and AIPatrol:IsAlive() then
Fsm:PatrolRoute()
end
end
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_AIR_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
self:F2()
-- When RTB, don't allow anymore the routing.
if From == "RTB" then
return
end
if AIPatrol and AIPatrol:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIPatrol:GetCoordinate()
local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt( altitude )
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
local speedkmh=ToTargetSpeed
local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true)
PatrolRoute[#PatrolRoute+1] = FromWP
if self.racetrack then
-- Random heading.
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
-- Random leg length.
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
-- Random duration if any.
local duration = self.racetrackdurationmin
if self.racetrackdurationmax then
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
end
-- CAP coordinate.
local c0=self.PatrolZone:GetRandomCoordinate()
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
end
-- Race track points.
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
self:SetTargetDistance(c0) -- For RTB status check
-- Debug:
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
--c1:MarkToAll("Race track c1")
--c2:MarkToAll("Race track c2")
-- Task to orbit.
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
-- Task function to redo the patrol at other random position.
local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
-- Controlled task with task condition.
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
-- Second waypoint
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
else
--- Create a route point of type air.
local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true)
PatrolRoute[#PatrolRoute+1] = ToWP
local Tasks = {}
Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
end
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
AIPatrol:Route( PatrolRoute, self.TaskDelay )
end
end
--- Resumes the AIPatrol
-- @param Wrapper.Group#GROUP AIPatrol
-- @param Core.Fsm#FSM Fsm
function AI_AIR_PATROL.Resume( AIPatrol, Fsm )
AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol and AIPatrol:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay )
Fsm:__PatrolRoute( Fsm.TaskDelay )
end
end
@@ -1,296 +0,0 @@
--- **AI** - Models squadrons for airplanes and helicopters.
--
-- This is a class used in the @{AI.AI_Air_Dispatcher} and derived dispatcher classes.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air_Squadron
-- @image MOOSE.JPG
--- @type AI_AIR_SQUADRON
-- @extends Core.Base#BASE
--- Implements the core functions modeling squadrons for airplanes and helicopters.
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_AIR_SQUADRON
AI_AIR_SQUADRON = {
ClassName = "AI_AIR_SQUADRON",
}
--- Creates a new AI_AIR_SQUADRON object
-- @param #AI_AIR_SQUADRON self
-- @return #AI_AIR_SQUADRON
function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
self:T( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON
AI_Air_Squadron.Name = SquadronName
AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName )
AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName()
if not AI_Air_Squadron.Airbase then
error( "Cannot find airbase with name:" .. AirbaseName )
end
AI_Air_Squadron.Spawn = {}
if type( TemplatePrefixes ) == "string" then
local SpawnTemplate = TemplatePrefixes
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate]
else
for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
end
end
AI_Air_Squadron.ResourceCount = ResourceCount
AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes
AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default.
return AI_Air_Squadron
end
--- Set the Name of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #string Name The Squadron Name.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetName( Name )
self.Name = Name
return self
end
--- Get the Name of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #string The Squadron Name.
function AI_AIR_SQUADRON:GetName()
return self.Name
end
--- Set the ResourceCount of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number ResourceCount The Squadron ResourceCount.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetResourceCount( ResourceCount )
self.ResourceCount = ResourceCount
return self
end
--- Get the ResourceCount of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron ResourceCount.
function AI_AIR_SQUADRON:GetResourceCount()
return self.ResourceCount
end
--- Add Resources to the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Resources The Resources to be added.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:AddResources( Resources )
self.ResourceCount = self.ResourceCount + Resources
return self
end
--- Remove Resources to the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Resources The Resources to be removed.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:RemoveResources( Resources )
self.ResourceCount = self.ResourceCount - Resources
return self
end
--- Set the Overhead of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Overhead The Squadron Overhead.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetOverhead( Overhead )
self.Overhead = Overhead
return self
end
--- Get the Overhead of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron Overhead.
function AI_AIR_SQUADRON:GetOverhead()
return self.Overhead
end
--- Set the Grouping of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Grouping The Squadron Grouping.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetGrouping( Grouping )
self.Grouping = Grouping
return self
end
--- Get the Grouping of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron Grouping.
function AI_AIR_SQUADRON:GetGrouping()
return self.Grouping
end
--- Set the FuelThreshold of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number FuelThreshold The Squadron FuelThreshold.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold )
self.FuelThreshold = FuelThreshold
return self
end
--- Get the FuelThreshold of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron FuelThreshold.
function AI_AIR_SQUADRON:GetFuelThreshold()
return self.FuelThreshold
end
--- Set the EngageProbability of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number EngageProbability The Squadron EngageProbability.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability )
self.EngageProbability = EngageProbability
return self
end
--- Get the EngageProbability of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron EngageProbability.
function AI_AIR_SQUADRON:GetEngageProbability()
return self.EngageProbability
end
--- Set the Takeoff of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Takeoff The Squadron Takeoff.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetTakeoff( Takeoff )
self.Takeoff = Takeoff
return self
end
--- Get the Takeoff of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron Takeoff.
function AI_AIR_SQUADRON:GetTakeoff()
return self.Takeoff
end
--- Set the Landing of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number Landing The Squadron Landing.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetLanding( Landing )
self.Landing = Landing
return self
end
--- Get the Landing of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #number The Squadron Landing.
function AI_AIR_SQUADRON:GetLanding()
return self.Landing
end
--- Set the TankerName of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #string TankerName The Squadron Tanker Name.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetTankerName( TankerName )
self.TankerName = TankerName
return self
end
--- Get the Tanker Name of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @return #string The Squadron Tanker Name.
function AI_AIR_SQUADRON:GetTankerName()
return self.TankerName
end
--- Set the Radio of the Squadron.
-- @param #AI_AIR_SQUADRON self
-- @param #number RadioFrequency The frequency of communication.
-- @param #number RadioModulation The modulation of communication.
-- @param #number RadioPower The power in Watts of communication.
-- @param #string Language The language of the radio speech.
-- @return #AI_AIR_SQUADRON The Squadron.
function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language )
self.RadioFrequency = RadioFrequency
self.RadioModulation = RadioModulation or radio.modulation.AM
self.RadioPower = RadioPower or 100
if self.RadioSpeech then
self.RadioSpeech:Stop()
end
self.RadioSpeech = nil
self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation )
self.RadioSpeech.power = RadioPower
self.RadioSpeech:Start( 0.5 )
self.RadioSpeech:SetLanguage( Language )
return self
end
-653
View File
@@ -1,653 +0,0 @@
--- **AI** - Peform Battlefield Area Interdiction (BAI) within an engagement zone.
--
-- **Features:**
--
-- * Hold and standby within a patrol zone.
-- * Engage upon command the assigned targets within an engagement zone.
-- * Loop the zone until all targets are eliminated.
-- * Trigger different events upon the results achieved.
-- * After combat, return to the patrol zone and hold.
-- * RTB when commanded or after out of fuel.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_BAI)
--
-- ===
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **Gunterlund**: Test case revision.
--
-- ===
--
-- @module AI.AI_BAI
-- @image AI_Battlefield_Air_Interdiction.JPG
--- AI_BAI_ZONE class
-- @type AI_BAI_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG)
--
-- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event.
--
-- ![Start Event](..\Presentations\AI_BAI\Dia4.JPG)
--
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
-- using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
--
-- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG)
--
-- When the AI is commanded to provide BattleGround Air Interdiction (through the event **Engage**), the AI will fly towards the Engage Zone.
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia6.JPG)
--
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia7.JPG)
--
-- Every target that is destroyed, is reported< by the AI.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia8.JPG)
--
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia9.JPG)
--
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
--
-- * a FAC
-- * a timed event
-- * a menu option selected by a human
-- * a condition
-- * others ...
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia10.JPG)
--
-- When the AI has accomplished the Bombing, it will fly back to the Patrol Zone.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia11.JPG)
--
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
-- It can be notified to go RTB through the **RTB** event.
--
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG)
--
-- # 1. AI_BAI_ZONE constructor
--
-- * @{#AI_BAI_ZONE.New}(): Creates a new AI_BAI_ZONE object.
--
-- ## 2. AI_BAI_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_BAI\Dia2.JPG)
--
-- ### 2.1. AI_BAI_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing BOMB.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2. AI_BAI_ZONE Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_BAI_ZONE.Engage}**: Engage the AI to provide BOMB in the Engage Zone, destroying any target it finds.
-- * **@{#AI_BAI_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task.
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
--
-- Use the method @{#AI_BAI_ZONE.SearchOff}() to specify that the EngageZone is not to be searched for potential targets (UNITs), but that the center of the zone
-- is the point where a map object is to be destroyed (like a bridge).
--
-- Example:
--
-- -- Tell the BAI not to search for potential targets in the BAIEngagementZone, but rather use the center of the BAIEngagementZone as the bombing location.
-- AIBAIZone:SearchOff()
--
-- Searching can be switched back on with the method @{#AI_BAI_ZONE.SearchOn}(). Use the method @{#AI_BAI_ZONE.SearchOnOff}() to flexibily switch searching on or off.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_BAI_ZONE
AI_BAI_ZONE = {
ClassName = "AI_BAI_ZONE",
}
--- Creates a new AI_BAI_ZONE object
-- @param #AI_BAI_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_BAI_ZONE self
function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_BAI_ZONE
self.EngageZone = EngageZone
self.Accomplished = false
self:SetDetectionZone( self.EngageZone )
self:SearchOn()
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_BAI_ZONE] OnBeforeEngage
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_BAI_ZONE] OnAfterEngage
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_BAI_ZONE] Engage
-- @param #AI_BAI_ZONE self
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_BAI_ZONE] __Engage
-- @param #AI_BAI_ZONE self
-- @param #number Delay The delay in seconds.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_BAI_ZONE] OnEnterEngaging
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_BAI_ZONE] OnBeforeFired
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_BAI_ZONE] OnAfterFired
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_BAI_ZONE] Fired
-- @param #AI_BAI_ZONE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_BAI_ZONE] __Fired
-- @param #AI_BAI_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_BAI_ZONE] OnBeforeDestroy
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_BAI_ZONE] OnAfterDestroy
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_BAI_ZONE] Destroy
-- @param #AI_BAI_ZONE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_BAI_ZONE] __Destroy
-- @param #AI_BAI_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_BAI_ZONE] OnBeforeAbort
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_BAI_ZONE] OnAfterAbort
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_BAI_ZONE] Abort
-- @param #AI_BAI_ZONE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_BAI_ZONE] __Abort
-- @param #AI_BAI_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_BAI_ZONE] OnBeforeAccomplish
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_BAI_ZONE] OnAfterAccomplish
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_BAI_ZONE] Accomplish
-- @param #AI_BAI_ZONE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_BAI_ZONE] __Accomplish
-- @param #AI_BAI_ZONE self
-- @param #number Delay The delay in seconds.
return self
end
--- Set the Engage Zone where the AI is performing BOMB. Note that if the EngageZone is changed, the AI needs to re-detect targets.
-- @param #AI_BAI_ZONE self
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing BOMB.
-- @return #AI_BAI_ZONE self
function AI_BAI_ZONE:SetEngageZone( EngageZone )
self:F2()
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- Specifies whether to search for potential targets in the zone, or let the center of the zone be the bombing coordinate.
-- AI_BAI_ZONE will search for potential targets by default.
-- @param #AI_BAI_ZONE self
-- @return #AI_BAI_ZONE
function AI_BAI_ZONE:SearchOnOff( Search )
self.Search = Search
return self
end
--- If Search is Off, the current zone coordinate will be the center of the bombing.
-- @param #AI_BAI_ZONE self
-- @return #AI_BAI_ZONE
function AI_BAI_ZONE:SearchOff()
self:SearchOnOff( false )
return self
end
--- If Search is On, BAI will search for potential targets in the zone.
-- @param #AI_BAI_ZONE self
-- @return #AI_BAI_ZONE
function AI_BAI_ZONE:SearchOn()
self:SearchOnOff( true )
return self
end
--- onafter State Transition for Event Start.
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_BAI_ZONE:onafterStart( Controllable, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
self:HandleEvent( EVENTS.Dead )
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
end
-- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageRoute( AIControllable )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_BAI#AI_BAI_ZONE
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To )
if self.Accomplished == true then
return false
end
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_BAI_ZONE:onafterTarget( Controllable, From, Event, To )
self:F({"onafterTarget",self.Search,Controllable:IsAlive()})
if Controllable:IsAlive() then
local AttackTasks = {}
if self.Search == true then
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
if DetectedUnit:IsAlive() then
if DetectedUnit:IsInZone( self.EngageZone ) then
if Detected == true then
self:F( {"Target: ", DetectedUnit } )
self.DetectedUnits[DetectedUnit] = false
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
self.Controllable:PushTask( AttackTask, 1 )
end
end
else
self.DetectedUnits[DetectedUnit] = nil
end
end
else
self:F("Attack zone")
local AttackTask = Controllable:TaskAttackMapObject(
self.EngageZone:GetPointVec2():GetVec2(),
true,
self.EngageWeaponExpend,
self.EngageAttackQty,
self.EngageDirection,
self.EngageAltitude
)
self.Controllable:PushTask( AttackTask, 1 )
end
self:__Target( -10 )
end
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_BAI_ZONE:onafterAbort( Controllable, From, Event, To )
Controllable:ClearTasks()
self:__Route( 1 )
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
EngageSpeed,
EngageAltitude,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection )
self:F("onafterEngage")
self.EngageSpeed = EngageSpeed or 400
self.EngageAltitude = EngageAltitude or 2000
self.EngageWeaponExpend = EngageWeaponExpend
self.EngageAttackQty = EngageAttackQty
self.EngageDirection = EngageDirection
if Controllable:IsAlive() then
local EngageRoute = {}
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
local AttackTasks = {}
if self.Search == true then
for DetectedUnitID, DetectedUnitData in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT
self:T( DetectedUnit )
if DetectedUnit:IsAlive() then
if DetectedUnit:IsInZone( self.EngageZone ) then
self:F( {"Engaging ", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskBombing(
DetectedUnit:GetPointVec2():GetVec2(),
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection,
EngageAltitude
)
end
else
self.DetectedUnits[DetectedUnit] = nil
end
end
else
self:F("Attack zone")
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackMapObject(
self.EngageZone:GetPointVec2():GetVec2(),
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection,
EngageAltitude
)
end
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
--- Find a random 2D point in EngageZone.
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
self:T2( ToTargetVec2 )
--- Obtain a 3D @{Point} from the 2D point + altitude.
local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Controllable:WayPointInitialize( EngageRoute )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
Controllable:SetState( Controllable, "EngageZone", self )
Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" )
--- NOW ROUTE THE GROUP!
Controllable:WayPointExecute( 1 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start targeting
end
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To )
self.Accomplished = true
self:SetDetectionDeactivated()
end
-- @param #AI_BAI_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit] = nil
end
end
-- @param #AI_BAI_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_BAI_ZONE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end
-308
View File
@@ -1,308 +0,0 @@
--- **AI** - Balance player slots with AI to create an engaging simulation environment, independent of the amount of players.
--
-- **Features:**
--
-- * Automatically spawn AI as a replacement of free player slots for a coalition.
-- * Make the AI to perform tasks.
-- * Define a maximum amount of AI to be active at the same time.
-- * Configure the behaviour of AI when a human joins a slot for which an AI is active.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Balancer)
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
-- ===
--
-- @module AI.AI_Balancer
-- @image AI_Balancing.JPG
-- @type AI_BALANCER
-- @field Core.Set#SET_CLIENT SetClient
-- @field Core.Spawn#SPAWN SpawnAI
-- @field Wrapper.Group#GROUP Test
-- @extends Core.Fsm#FSM_SET
--- ![Banner Image](..\Images\deprecated.png)
--
-- Monitors and manages as many replacement AI groups as there are
-- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players.
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
--
-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
-- An explanation about state and event transition methods can be found in the @{Core.Fsm} module documentation.
--
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
--
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
--
-- ## 1. AI_BALANCER construction
--
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
--
-- ## 2. AI_BALANCER is a FSM
--
-- ![Process](..\Presentations\AI_BALANCER\Dia13.JPG)
--
-- ### 2.1. AI_BALANCER States
--
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
--
-- ### 2.2. AI_BALANCER Events
--
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
--
-- ## 3. AI_BALANCER spawn interval for replacement AI
--
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
--
-- ## 4. AI_BALANCER returns AI to Airbases
--
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
-- When a human player joins a slot, you can configure to let the AI return to:
--
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}.
--
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #AI_BALANCER
AI_BALANCER = {
ClassName = "AI_BALANCER",
PatrolZones = {},
AIGroups = {},
Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
}
--- Creates a new AI_BALANCER object
-- @param #AI_BALANCER self
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AI_BALANCER
function AI_BALANCER:New( SetClient, SpawnAI )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
-- TODO: Define the OnAfterSpawned event
self:SetStartState( "None" )
self:AddTransition( "*", "Monitor", "Monitoring" )
self:AddTransition( "*", "Spawn", "Spawning" )
self:AddTransition( "Spawning", "Spawned", "Spawned" )
self:AddTransition( "*", "Destroy", "Destroying" )
self:AddTransition( "*", "Return", "Returning" )
self.SetClient = SetClient
self.SetClient:FilterOnce()
self.SpawnAI = SpawnAI
self.SpawnQueue = {}
self.ToNearestAirbase = false
self.ToHomeAirbase = false
self:__Monitor( 1 )
return self
end
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
-- @param #AI_BALANCER self
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
-- @param #number Latest The latest a new AI can be spawned in seconds.
-- @return self
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
self.Earliest = Earliest
self.Latest = Latest
return self
end
--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}.
-- @param #AI_BALANCER self
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to.
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnThresholdRange = ReturnThresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}.
-- @param #AI_BALANCER self
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
self.ToHomeAirbase = true
self.ReturnThresholdRange = ReturnThresholdRange
end
--- AI_BALANCER:onenterSpawning
-- @param #AI_BALANCER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param #string ClientName
-- @param Wrapper.Group#GROUP AIGroup
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
-- OK, Spawn a new group from the default SpawnAI object provided.
local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
if AIGroup then
AIGroup:T( { "Spawning new AIGroup", ClientName = ClientName } )
--TODO: need to rework UnitName thing ...
SetGroup:Remove( ClientName ) -- Ensure that the previously allocated AIGroup to ClientName is removed in the Set.
SetGroup:Add( ClientName, AIGroup )
self.SpawnQueue[ClientName] = nil
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
-- Mission designers can catch this event to bind further actions to the AIGroup.
self:Spawned( AIGroup )
end
end
--- AI_BALANCER:onenterDestroying
-- @param #AI_BALANCER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Wrapper.Group#GROUP AIGroup
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
AIGroup:Destroy()
SetGroup:Flush( self )
SetGroup:Remove( ClientName )
SetGroup:Flush( self )
end
--- RTB
-- @param #AI_BALANCER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Group#GROUP AIGroup
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
local AIGroupTemplate = AIGroup:GetTemplate()
if self.ToHomeAirbase == true then
local WayPointCount = #AIGroupTemplate.route.points
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
AIGroup:SetCommand( SwitchWayPointCommand )
AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
local PointVec2 = COORDINATE:New(AIGroup:GetVec2().x, 0, AIGroup:GetVec2().y)
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
AIGroup:RouteRTB(ClosestAirbase)
end
end
--- AI_BALANCER:onenterMonitoring
-- @param #AI_BALANCER self
function AI_BALANCER:onenterMonitoring( SetGroup )
self:T2( { self.SetClient:Count() } )
--self.SetClient:Flush()
self.SetClient:ForEachClient(
--- SetClient:ForEachClient
-- @param Wrapper.Client#CLIENT Client
function( Client )
self:T3(Client.ClientName)
local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
if AIGroup then self:T( { AIGroup = AIGroup:GetName(), IsAlive = AIGroup:IsAlive() } ) end
if Client:IsAlive() == true then
if AIGroup and AIGroup:IsAlive() == true then
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
self:Destroy( Client.UnitName, AIGroup )
else
-- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
self:T2( RangeZone )
_DATABASE:ForEachPlayerUnit(
--- Nameless function
-- @param Wrapper.Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
if RangeTestUnit:IsInZone( RangeZone ) == true then
self:T2( "in zone" )
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
self:T2( "in range" )
PlayerInRange.Value = true
end
end
end,
--- Nameless function
-- @param Core.Zone#ZONE_RADIUS RangeZone
-- @param Wrapper.Group#GROUP AIGroup
function( RangeZone, AIGroup, PlayerInRange )
if PlayerInRange.Value == false then
self:Return( AIGroup )
end
end
, RangeZone, AIGroup, PlayerInRange
)
end
self.Set:Remove( Client.UnitName )
end
else
if not AIGroup or not AIGroup:IsAlive() == true then
self:T( "Client " .. Client.UnitName .. " not alive." )
self:T( { Queue = self.SpawnQueue[Client.UnitName] } )
if not self.SpawnQueue[Client.UnitName] then
-- Spawn a new AI taking into account the spawn interval Earliest, Latest
self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
self.SpawnQueue[Client.UnitName] = true
self:T( "New AI Spawned for Client " .. Client.UnitName )
end
end
end
return true
end
)
self:__Monitor( 10 )
end
-543
View File
@@ -1,543 +0,0 @@
--- **AI** - Perform Combat Air Patrolling (CAP) for airplanes.
--
-- **Features:**
--
-- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel threshold to RTB on time.
-- * Engage the enemy when detected.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAP)
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **Quax**: Concept, Advice & Testing.
-- * **Pikey**: Concept, Advice & Testing.
-- * **Gunterlund**: Test case revision.
-- * **Whisper**: Testing.
-- * **Delta99**: Testing.
--
-- ===
--
-- @module AI.AI_CAP
-- @image AI_Combat_Air_Patrol.JPG
-- @type AI_CAP_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_CAP\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_CAP\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG)
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1. AI_CAP_ZONE constructor
--
-- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object.
--
-- ## 2. AI_CAP_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 2.1 AI_CAP_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_CAP_ZONE Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{#AI_CAP_ZONE.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{Core.Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{#AI_CAP_ZONE.SetEngageZone}() to define that Zone.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CAP_ZONE
AI_CAP_ZONE = {
ClassName = "AI_CAP_ZONE",
}
--- Creates a new AI_CAP_ZONE object
-- @param #AI_CAP_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_CAP_ZONE self
function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE
self.Accomplished = false
self.Engaging = false
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_CAP_ZONE] OnBeforeEngage
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_CAP_ZONE] OnAfterEngage
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_CAP_ZONE] Engage
-- @param #AI_CAP_ZONE self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_CAP_ZONE] __Engage
-- @param #AI_CAP_ZONE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_CAP_ZONE] OnEnterEngaging
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_CAP_ZONE] OnBeforeFired
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_CAP_ZONE] OnAfterFired
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_CAP_ZONE] Fired
-- @param #AI_CAP_ZONE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_CAP_ZONE] __Fired
-- @param #AI_CAP_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_CAP_ZONE] OnAfterDestroy
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_CAP_ZONE] Destroy
-- @param #AI_CAP_ZONE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_CAP_ZONE] __Destroy
-- @param #AI_CAP_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_CAP_ZONE] OnBeforeAbort
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_CAP_ZONE] OnAfterAbort
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_CAP_ZONE] Abort
-- @param #AI_CAP_ZONE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_CAP_ZONE] __Abort
-- @param #AI_CAP_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_CAP_ZONE] Accomplish
-- @param #AI_CAP_ZONE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_CAP_ZONE] __Accomplish
-- @param #AI_CAP_ZONE self
-- @param #number Delay The delay in seconds.
return self
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_CAP_ZONE self
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
-- @return #AI_CAP_ZONE self
function AI_CAP_ZONE:SetEngageZone( EngageZone )
self:F2()
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- Set the Engage Range when the AI will engage with airborne enemies.
-- @param #AI_CAP_ZONE self
-- @param #number EngageRange The Engage Range.
-- @return #AI_CAP_ZONE self
function AI_CAP_ZONE:SetEngageRange( EngageRange )
self:F2()
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- onafter State Transition for Event Start.
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- @param AI.AI_CAP#AI_CAP_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1 )
end
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To )
if self.Accomplished == true then
return false
end
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To )
if From ~= "Engaging" then
local Engage = false
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
self:T( DetectedUnit )
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
Engage = true
break
end
end
if Engage == true then
self:F( 'Detected -> Engaging' )
self:__Engage( 1 )
end
end
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To )
Controllable:ClearTasks()
self:__Route( 1 )
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
if Controllable and Controllable:IsAlive() then
local EngageRoute = {}
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return self end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
ToEngageZoneSpeed,
true
)
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
--- Find a random 2D point in PatrolZone.
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
self:T2( ToTargetVec2 )
--- Define Speed and Altitude.
local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
--- Obtain a 3D @{Point} from the 2D point + altitude.
local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
ToTargetSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTEvadeFire()
local AttackTasks = {}
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } )
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
if self.EngageZone then
if DetectedUnit:IsInZone( self.EngageZone ) then
self:F( {"Within Zone and Engaging ", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
end
else
if self.EngageRange then
if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then
self:F( {"Within Range and Engaging", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
end
else
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
end
end
else
self.DetectedUnits[DetectedUnit] = nil
end
end
if #AttackTasks == 0 then
self:F("No targets found -> Going back to Patrolling")
self:__Abort( 1 )
self:__Route( 1 )
self:SetDetectionActivated()
else
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self )
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
self:SetDetectionDeactivated()
end
Controllable:Route( EngageRoute, 0.5 )
end
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
-- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit] = nil
end
end
-- @param #AI_CAP_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_CAP_ZONE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end
-572
View File
@@ -1,572 +0,0 @@
--- **AI** - Perform Close Air Support (CAS) near friendlies.
--
-- **Features:**
--
-- * Hold and standby within a patrol zone.
-- * Engage upon command the enemies within an engagement zone.
-- * Loop the zone until all enemies are eliminated.
-- * Trigger different events upon the results achieved.
-- * After combat, return to the patrol zone and hold.
-- * RTB when commanded or after fuel.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAS)
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **Quax**: Concept, Advice & Testing.
-- * **Pikey**: Concept, Advice & Testing.
-- * **Gunterlund**: Test case revision.
--
-- ===
--
-- @module AI.AI_CAS
-- @image AI_Close_Air_Support.JPG
--- AI_CAS_ZONE class
-- @type AI_CAS_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- Implements the core functions to provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG)
--
-- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event.
--
-- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG)
--
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
-- using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
--
-- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG)
--
-- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone.
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia6.JPG)
--
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia7.JPG)
--
-- Every target that is destroyed, is reported< by the AI.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia8.JPG)
--
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia9.JPG)
--
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
--
-- * a FAC
-- * a timed event
-- * a menu option selected by a human
-- * a condition
-- * others ...
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia10.JPG)
--
-- When the AI has accomplished the CAS, it will fly back to the Patrol Zone.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia11.JPG)
--
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
-- It can be notified to go RTB through the **RTB** event.
--
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG)
--
-- ## AI_CAS_ZONE constructor
--
-- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object.
--
-- ## AI_CAS_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_CAS\Dia2.JPG)
--
-- ### 2.1. AI_CAS_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2. AI_CAS_ZONE Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds.
-- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CAS_ZONE
AI_CAS_ZONE = {
ClassName = "AI_CAS_ZONE",
}
--- Creates a new AI_CAS_ZONE object
-- @param #AI_CAS_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_CAS_ZONE self
function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE
self.EngageZone = EngageZone
self.Accomplished = false
self:SetDetectionZone( self.EngageZone )
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_CAS_ZONE] OnBeforeEngage
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_CAS_ZONE] OnAfterEngage
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_CAS_ZONE] Engage
-- @param #AI_CAS_ZONE self
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_CAS_ZONE] __Engage
-- @param #AI_CAS_ZONE self
-- @param #number Delay The delay in seconds.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_CAS_ZONE] OnEnterEngaging
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_CAS_ZONE] OnBeforeFired
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_CAS_ZONE] OnAfterFired
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_CAS_ZONE] Fired
-- @param #AI_CAS_ZONE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_CAS_ZONE] __Fired
-- @param #AI_CAS_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_CAS_ZONE] OnAfterDestroy
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_CAS_ZONE] Destroy
-- @param #AI_CAS_ZONE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_CAS_ZONE] __Destroy
-- @param #AI_CAS_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_CAS_ZONE] OnBeforeAbort
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_CAS_ZONE] OnAfterAbort
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_CAS_ZONE] Abort
-- @param #AI_CAS_ZONE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_CAS_ZONE] __Abort
-- @param #AI_CAS_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_CAS_ZONE] Accomplish
-- @param #AI_CAS_ZONE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_CAS_ZONE] __Accomplish
-- @param #AI_CAS_ZONE self
-- @param #number Delay The delay in seconds.
return self
end
--- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets.
-- @param #AI_CAS_ZONE self
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS.
-- @return #AI_CAS_ZONE self
function AI_CAS_ZONE:SetEngageZone( EngageZone )
self:F2()
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- onafter State Transition for Event Start.
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
self:HandleEvent( EVENTS.Dead )
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
end
-- @param AI.AI_CAS#AI_CAS_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm )
EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection )
end
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To )
if self.Accomplished == true then
return false
end
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To )
if Controllable:IsAlive() then
local AttackTasks = {}
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
if DetectedUnit:IsAlive() then
if DetectedUnit:IsInZone( self.EngageZone ) then
if Detected == true then
self:F( {"Target: ", DetectedUnit } )
self.DetectedUnits[DetectedUnit] = false
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
self.Controllable:PushTask( AttackTask, 1 )
end
end
else
self.DetectedUnits[DetectedUnit] = nil
end
end
self:__Target( -10 )
end
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To )
Controllable:ClearTasks()
self:__Route( 1 )
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
EngageSpeed,
EngageAltitude,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection )
self:F("onafterEngage")
self.EngageSpeed = EngageSpeed or 400
self.EngageAltitude = EngageAltitude or 2000
self.EngageWeaponExpend = EngageWeaponExpend
self.EngageAttackQty = EngageAttackQty
self.EngageDirection = EngageDirection
if Controllable:IsAlive() then
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
local EngageRoute = {}
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
local AttackTasks = {}
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
self:T( DetectedUnit )
if DetectedUnit:IsAlive() then
if DetectedUnit:IsInZone( self.EngageZone ) then
self:F( {"Engaging ", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit,
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection
)
end
else
self.DetectedUnits[DetectedUnit] = nil
end
end
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
--- Find a random 2D point in EngageZone.
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
self:T2( ToTargetVec2 )
--- Obtain a 3D @{Point} from the 2D point + altitude.
local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
Controllable:Route( EngageRoute, 0.5 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start targeting
end
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To )
self.Accomplished = true
self:SetDetectionDeactivated()
end
-- @param #AI_CAS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit] = nil
end
end
-- @param #AI_CAS_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_CAS_ZONE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end
-591
View File
@@ -1,591 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo
-- @image Cargo.JPG
--- @type AI_CARGO
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Base class for the dynamic cargo handling capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- The derived classes from this module are:
--
-- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones.
-- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones.
-- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #AI_CARGO
AI_CARGO = {
ClassName = "AI_CARGO",
Coordinate = nil, -- Core.Point#COORDINATE,
Carrier_Cargo = {},
}
--- Creates a new AI_CARGO object.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
-- @return #AI_CARGO self
function AI_CARGO:New( Carrier, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
self.CargoCarrier = Carrier -- Wrapper.Group#GROUP
self:SetStartState( "Unloaded" )
-- Board
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Reload", "*" )
self:AddTransition( "*", "Board", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
-- Unload
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unboard", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
--- Pickup Handler OnBefore for AI_CARGO
-- @function [parent=#AI_CARGO] OnBeforePickup
-- @param #AI_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO
-- @function [parent=#AI_CARGO] OnAfterPickup
-- @param #AI_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Pickup Trigger for AI_CARGO
-- @function [parent=#AI_CARGO] Pickup
-- @param #AI_CARGO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Pickup Asynchronous Trigger for AI_CARGO
-- @function [parent=#AI_CARGO] __Pickup
-- @param #AI_CARGO self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location.
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Deploy Handler OnBefore for AI_CARGO
-- @function [parent=#AI_CARGO] OnBeforeDeploy
-- @param #AI_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO
-- @function [parent=#AI_CARGO] OnAfterDeploy
-- @param #AI_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Deploy Trigger for AI_CARGO
-- @function [parent=#AI_CARGO] Deploy
-- @param #AI_CARGO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Deploy Asynchronous Trigger for AI_CARGO
-- @function [parent=#AI_CARGO] __Deploy
-- @param #AI_CARGO self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
--- Loaded Handler OnAfter for AI_CARGO
-- @function [parent=#AI_CARGO] OnAfterLoaded
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From
-- @param #string Event
-- @param #string To
--- Unloaded Handler OnAfter for AI_CARGO
-- @function [parent=#AI_CARGO] OnAfterUnloaded
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From
-- @param #string Event
-- @param #string To
--- On after Deployed event.
-- @function [parent=#AI_CARGO] OnAfterDeployed
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
-- @param #boolean Defend Defend for APCs.
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
CarrierUnit:SetCargoBayWeightLimit()
end
self.Transporting = false
self.Relocating = false
return self
end
function AI_CARGO:IsTransporting()
return self.Transporting == true
end
function AI_CARGO:IsRelocating()
return self.Relocating == true
end
--- On after Pickup event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
self.Transporting = false
self.Relocating = true
end
--- On after Deploy event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Deploy place.
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the deploy coordinate.
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
function AI_CARGO:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
self.Relocating = false
self.Transporting = true
end
--- On before Load event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
self:F( { Carrier, From, Event, To } )
local Boarding = false
local LoadInterval = 2
local LoadDelay = 1
local Carrier_List = {}
local Carrier_Weight = {}
if Carrier and Carrier:IsAlive() then
self.Carrier_Cargo = {}
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight()
self:F({CargoBayFreeWeight=CargoBayFreeWeight})
Carrier_List[#Carrier_List+1] = CarrierUnit
Carrier_Weight[CarrierUnit] = CargoBayFreeWeight
end
local Carrier_Count = #Carrier_List
local Carrier_Index = 1
local Loaded = false
for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
-- Try all Carriers, but start from the one according the Carrier_Index
for Carrier_Loop = 1, #Carrier_List do
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
-- This counters loop through the available Carriers.
Carrier_Index = Carrier_Index + 1
if Carrier_Index > Carrier_Count then
Carrier_Index = 1
end
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then
self:F( { "In radius", CarrierUnit:GetName() } )
local CargoWeight = Cargo:GetWeight()
local CarrierSpace=Carrier_Weight[CarrierUnit]
-- Only when there is space within the bay to load the next cargo item!
if CarrierSpace > CargoWeight then
Carrier:RouteStop()
--Cargo:Ungroup()
Cargo:__Board( -LoadDelay, CarrierUnit )
self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone )
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
-- So now this CarrierUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.Carrier_Cargo[Cargo] = CarrierUnit
Boarding = true
Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight
Loaded = true
-- Ok, we loaded a cargo, now we can stop the loop.
break
else
self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace))
end
end
end
end
end
if not Loaded == true then
-- No loading happened, so we need to pickup something else.
self.Relocating = false
end
end
return Boarding
end
--- On before Reload event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onbeforeReload( Carrier, From, Event, To )
self:F( { Carrier, From, Event, To } )
local Boarding = false
local LoadInterval = 2
local LoadDelay = 1
local Carrier_List = {}
local Carrier_Weight = {}
if Carrier and Carrier:IsAlive() then
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
Carrier_List[#Carrier_List+1] = CarrierUnit
end
local Carrier_Count = #Carrier_List
local Carrier_Index = 1
local Loaded = false
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
-- Try all Carriers, but start from the one according the Carrier_Index
for Carrier_Loop = 1, #Carrier_List do
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
-- This counters loop through the available Carriers.
Carrier_Index = Carrier_Index + 1
if Carrier_Index > Carrier_Count then
Carrier_Index = 1
end
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
Carrier:RouteStop()
Cargo:__Board( -LoadDelay, CarrierUnit )
self:__Board( LoadDelay, Cargo, CarrierUnit )
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
-- So now this CarrierUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.Carrier_Cargo[Cargo] = CarrierUnit
Boarding = true
Loaded = true
end
end
end
if not Loaded == true then
-- No loading happened, so we need to pickup something else.
self.Relocating = false
end
end
return Boarding
end
--- On after Board event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
-- @param Wrapper.Unit#UNIT CarrierUnit
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone )
self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } )
if Carrier and Carrier:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
return
end
end
self:__Loaded( 0.1, Cargo, CarrierUnit, PickupZone )
end
--- On after Loaded event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @return #boolean Cargo loaded.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone )
self:F( { Carrier, From, Event, To } )
local Loaded = true
if Carrier and Carrier:IsAlive() then
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
if Loaded then
self:__PickedUp( 0.1, PickupZone )
end
end
--- On after PickedUp event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone )
self:F( { Carrier, From, Event, To } )
Carrier:RouteResume()
local HasCargo = false
if Carrier and Carrier:IsAlive() then
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
HasCargo = true
break
end
end
self.Relocating = false
if HasCargo then
self:F( "Transporting" )
self.Transporting = true
end
end
--- On after Unload event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend )
self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } )
local UnboardInterval = 5
local UnboardDelay = 5
if Carrier and Carrier:IsAlive() then
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
Carrier:RouteStop()
for _, Cargo in pairs( CarrierUnit:GetCargo() ) do
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
if Cargo:IsLoaded() then
Cargo:__UnBoard( UnboardDelay )
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend )
if not Defend == true then
Cargo:SetDeployed( true )
end
end
end
end
end
end
--- On after Unboard event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
if Carrier and Carrier:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
return
end
end
self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend )
end
--- On after Unloaded event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
-- @param #boolean Deployed Cargo is deployed.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
local AllUnloaded = true
--Cargo:Regroup()
if Carrier and Carrier:IsAlive() then
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
local IsEmpty = CarrierUnit:IsCargoEmpty()
self:T({ IsEmpty = IsEmpty })
if not IsEmpty then
AllUnloaded = false
break
end
end
if AllUnloaded == true then
if DeployZone == true then
self.Carrier_Cargo = {}
end
self.CargoCarrier = Carrier
end
end
if AllUnloaded == true then
self:__Deployed( 5, DeployZone, Defend )
end
end
--- On after Deployed event.
-- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
-- @param #boolean Defend Defend for APCs.
function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend )
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
if not Defend == true then
self.Transporting = false
else
self:F( "Defending" )
end
end
-609
View File
@@ -1,609 +0,0 @@
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_APC
-- @image AI_Cargo_Dispatching_For_APC.JPG
-- @type AI_CARGO_APC
-- @extends AI.AI_Cargo#AI_CARGO
--- Brings a dynamic cargo handling capability for an AI vehicle group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will load automatically cargo when the APCs are within boarding or loading radius.
-- The boarding or loading radius is specified when the cargo is created in the simulation, and therefore, this radius depends on the type of cargo
-- and the specified boarding radius.
--
-- ## **Defending** the APCs when enemies nearby.
--
-- Cargo will defend the carrier with its available arms, and to avoid cargo being lost within the battlefield.
--
-- When the APCs are approaching enemy units, something special is happening.
-- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group.
-- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs,
-- to ensure that the APCs are not too far away from the following running infantry.
-- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route.
--
-- A combat radius needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
-- This combat radius will trigger the unboarding of troops when enemies are within the combat radius around the APCs.
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit radius (effectiveness) versus
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
-- A combat radius of 350 meters to 500 meters has been proven to be the most effective and efficient.
--
-- However, when the defense of the carrier, is not required, it must be switched off.
-- This is done by disabling the defense of the carrier using the method @{#AI_CARGO_APC.SetCombatRadius}(), and providing a combat radius of 0 meters.
-- It can be switched on later when required by reenabling the defense using the method and providing a combat radius larger than 0.
--
-- ## Infantry or cargo **health**.
--
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
-- ## Control the APCs on the map.
--
-- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map.
-- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch!
-- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up.
-- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine.
-- No workaround is possible on this.
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
--
-- @field #AI_CARGO_APC
AI_CARGO_APC = {
ClassName = "AI_CARGO_APC",
Coordinate = nil, -- Core.Point#COORDINATE,
}
--- Creates a new AI_CARGO_APC object.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC The carrier APC group.
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported.
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When the combat radius is 0, no defense will happen of the carrier.
-- @return #AI_CARGO_APC
function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Follow", "Following" )
self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Home", "*" )
self:AddTransition( "*", "Reload", "Boarding" )
self:AddTransition( "*", "Deployed", "*" )
self:AddTransition( "*", "PickedUp", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
self:SetCombatRadius( CombatRadius )
self:SetCarrier( APC )
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_APC
function AI_CARGO_APC:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
function CargoCarrier:OnEventDead( EventData )
self:F({"dead"})
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat radius. Unload the CargoCarrier.
AICargoTroops:Destroyed()
end
end
end
-- CargoCarrier:HandleEvent( EVENTS.Hit )
--
-- function CargoCarrier:OnEventHit( EventData )
-- self:F({"hit"})
-- local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
-- if AICargoTroops then
-- self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
-- if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- -- There are enemies within combat radius. Unload the CargoCarrier.
-- AICargoTroops:Unload( false )
-- end
-- end
-- end
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
self.Coalition = self.CargoCarrier:GetCoalition()
self:SetControllable( CargoCarrier )
self:Guard()
return self
end
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
self:SetPickupOffRoad(Offroad, Formation)
self:SetDeployOffRoad(Offroad, Formation)
return self
end
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Find a free Carrier within a radius.
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
return NearUnit:GetGroup()
end
end
end
return nil
end
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
-- This is only valid for APCs and trucks etc, thus ground vehicles.
-- @param #AI_CARGO_APC self
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
-- When the combat radius is 0, no defense will happen of the carrier.
-- When the combat radius is not provided, no defense will happen!
-- @return #AI_CARGO_APC
-- @usage
--
-- -- Disembark the infantry when the carrier is under attack.
-- AICargoAPC:SetCombatRadius( true )
--
-- -- Keep the cargo in the carrier when the carrier is under attack.
-- AICargoAPC:SetCombatRadius( false )
function AI_CARGO_APC:SetCombatRadius( CombatRadius )
self.CombatRadius = CombatRadius or 0
if self.CombatRadius > 0 then
self:__Monitor( -5 )
end
return self
end
--- Follow Infantry to the Carrier.
-- @param #AI_CARGO_APC self
-- @param #AI_CARGO_APC Me
-- @param Wrapper.Unit#UNIT APCUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_APC
function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
--if self:Is( "Following" ) then
if APCUnit:IsAlive() then
-- We check if the Cargo is near to the CargoCarrier.
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then
-- The Cargo does not need to follow the Carrier.
Me:Guard()
else
self:F( { InfantryGroup = InfantryGroup:GetName() } )
if InfantryGroup:IsAlive() then
self:F( { InfantryGroup = InfantryGroup:GetName() } )
local Waypoints = {}
-- Calculate the new Route.
local FromCoord = InfantryGroup:GetCoordinate()
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
end
end
--- On after Monitor event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
self:F( { APC, From, Event, To, IsTransporting = self:IsTransporting() } )
if self.CombatRadius > 0 then
if APC and APC:IsAlive() then
if self.CarrierCoordinate then
if self:IsTransporting() == true then
local Coordinate = APC:GetCoordinate()
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) then
-- There are no enemies within combat radius. Reload the CargoCarrier.
self:Reload()
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat radius. Unload the CargoCarrier.
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
else
if self:Is( "Unloaded" ) then
--self:Follow()
end
self:F( "I am here" .. self:GetCurrentState() )
if self:Is( "Following" ) then
for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
if Cargo:IsAlive() then
if not Cargo:IsNear( APCUnit, 40 ) then
APCUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( APCUnit, 25 ) then
APCUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = APC:GetCoordinate()
end
self:__Monitor( -5 )
end
end
--- On after Follow event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
self:F( { APC, From, Event, To } )
self:F( "Follow" )
if APC and APC:IsAlive() then
for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsUnLoaded() then
self:FollowToCarrier( self, APCUnit, Cargo )
APCUnit:RouteResume()
end
end
end
end
--- Pickup task function. Triggers Load event.
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
-- @param #number Speed Speed (not used).
-- @param Core.Zone#ZONE PickupZone Pickup zone.
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
if APC:IsAlive() then
self:Load( PickupZone )
end
end
--- Deploy task function. Triggers Unload event.
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
-- @param Core.Zone#ZONE DeployZone Deploy zone.
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
if APC:IsAlive() then
self:Unload( DeployZone )
end
end
--- On after Pickup event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
if APC and APC:IsAlive() then
if Coordinate then
self.RoutePickup = true
local _speed=Speed or APC:GetSpeedMax()*0.5
-- Route on road.
local Waypoints = {}
if self.pickupOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
end
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
else
AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
end
self:GetParent( self, AI_CARGO_APC ).onafterPickup( self, APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
end
end
--- On after Deploy event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Deploy place.
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the deploy coordinate. This parameter is ignored for APCs.
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
if APC and APC:IsAlive() then
self.RouteDeploy = true
-- Set speed in km/h.
local speedmax=APC:GetSpeedMax()
local _speed=Speed or speedmax*0.5
_speed=math.min(_speed, speedmax)
-- Route on road.
local Waypoints = {}
if self.deployOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
end
-- Task function
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
-- Last waypoint
local Waypoint = Waypoints[#Waypoints]
-- Set task function
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
-- Route group
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
-- Call parent function.
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
end
end
--- On after Unloaded event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
-- @param #boolean Deployed Cargo is deployed.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_APC:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
self:GetParent( self, AI_CARGO_APC ).onafterUnloaded( self, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
-- If Defend == true then we need to scan for possible enemies within combat zone and engage only ground forces.
if Defend == true then
self.Zone:Scan( { Object.Category.UNIT } )
if not self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
-- OK, enemies nearby, now find the enemies and attack them.
local AttackUnits = self.Zone:GetScannedUnits() -- #list<DCS#Unit>
local Move = {}
local CargoGroup = Cargo.CargoObject -- Wrapper.Group#GROUP
Move[#Move+1] = CargoGroup:GetCoordinate():WaypointGround( 70, "Custom" )
for UnitId, AttackUnit in pairs( AttackUnits ) do
local MooseUnit = UNIT:Find( AttackUnit )
if MooseUnit:GetCoalition() ~= CargoGroup:GetCoalition() then
Move[#Move+1] = MooseUnit:GetCoordinate():WaypointGround( 70, "Line abreast" )
--MoveTo.Task = CargoGroup:TaskCombo( CargoGroup:TaskAttackUnit( MooseUnit, true ) )
self:F( { MooseUnit = MooseUnit:GetName(), CargoGroup = CargoGroup:GetName() } )
end
end
CargoGroup:RoutePush( Move, 0.1 )
end
end
end
--- On after Deployed event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP Carrier
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend )
self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
self:__Guard( 0.1 )
self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend )
end
--- On after Home event.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Home place.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate. This parameter is ignored for APCs.
function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, Height, HomeZone )
if APC and APC:IsAlive() ~= nil then
self.RouteHome = true
Speed = Speed or APC:GetSpeedMax()*0.5
local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
@@ -1,512 +0,0 @@
--- **AI** - Models the intelligent transportation of cargo using airplanes.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Airplane
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
-- @type AI_CARGO_AIRPLANE
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Brings a dynamic cargo handling capability for an AI airplane group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases.
--
-- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
--
-- ## Infantry health.
--
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #AI_CARGO_AIRPLANE
AI_CARGO_AIRPLANE = {
ClassName = "AI_CARGO_AIRPLANE",
Coordinate = nil, -- Core.Point#COORDINATE
}
--- Creates a new AI_CARGO_AIRPLANE object.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Plane used for transportation of cargo.
-- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported.
-- @return #AI_CARGO_AIRPLANE
function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Home" , "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
-- @param #number Speed in km/h for travelling to pickup base.
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
-- @param #number Speed Speed in km/h for travelling to pickup base.
-- @param #number Height Height in meters to move to the pickup coordinate.
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
--- Pickup Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
-- @param #number Speed Speed in km/h for travelling to pickup base.
-- @param #number Height Height in meters to move to the pickup coordinate.
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #number Delay Delay in seconds.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
-- @param #number Speed Speed in km/h for travelling to pickup base.
-- @param #number Height Height in meters to move to the pickup coordinate.
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
-- @param #number Speed Speed in km/h for travelling to deploy base.
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
-- @param #number Speed Speed in km/h for travelling to the deploy base.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
--- Deploy Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
-- @param #number Speed Speed in km/h for travelling to the deploy base.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param #number Delay Delay in seconds.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
-- @param #number Speed Speed in km/h for travelling to the deploy base.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
--- On after Loaded event, i.e. triggered when the cargo is inside the carrier.
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
-- @param From
-- @param Event
-- @param To
--- On after Deployed event.
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed.
-- Set carrier.
self:SetCarrier( Airplane )
return self
end
--- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Transport plane.
-- @return #AI_CARGO_AIRPLANE self
function AI_CARGO_AIRPLANE:SetCarrier( Airplane )
local AICargo = self
self.Airplane = Airplane -- Wrapper.Group#GROUP
self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self )
self.RoutePickup = false
self.RouteDeploy = false
Airplane:HandleEvent( EVENTS.Dead )
Airplane:HandleEvent( EVENTS.Hit )
Airplane:HandleEvent( EVENTS.EngineShutdown )
function Airplane:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Destroyed()
end
end
end
function Airplane:OnEventHit( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Unload()
end
end
end
function Airplane:OnEventEngineShutdown( EventData )
AICargo.Relocating = false
AICargo:Landed( self.Airplane )
end
self.Coalition = self.Airplane:GetCoalition()
self:SetControllable( Airplane )
return self
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
self:SetCarrier( NearUnit )
break
end
end
end
end
--- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param From
-- @param Event
-- @param To
function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To )
self:F({Airplane, From, Event, To})
if Airplane and Airplane:IsAlive()~=nil then
-- Aircraft was sent to this airbase to pickup troops. Initiate loadling.
if self.RoutePickup == true then
self:Load( self.PickupZone )
end
-- Aircraft was send to this airbase to deploy troops. Initiate unloading.
if self.RouteDeploy == true then
self:Unload()
self.RouteDeploy = false
end
end
end
--- On after "Pickup" event. Routes transport to pickup airbase.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
-- @param #number Speed Speed in km/h for travelling to pickup base.
-- @param #number Height Height in meters to move to the pickup coordinate.
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
if Airplane and Airplane:IsAlive() then
local airbasepickup=Coordinate:GetClosestAirbase()
self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName())
-- Get closest airbase of current position.
local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase()
-- Two cases. Aircraft spawned in air or at an airbase.
if Airplane:InAir() then
self.Airbase=nil --> route will start in air
else
self.Airbase=ClosestAirbase
end
-- Set pickup airbase.
local Airbase = self.PickupZone:GetAirbase()
-- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase.
local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate())
if Airplane:InAir() or Dist>500 then
-- Route aircraft to pickup airbase.
self:Route( Airplane, Airbase, Speed, Height )
-- Set airbase as starting point in the next Route() call.
self.Airbase = Airbase
-- Aircraft is on a pickup mission.
self.RoutePickup = true
else
-- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event.
self.RoutePickup=true
self:Landed()
end
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone )
end
end
--- On after Depoly event. Routes plane to the airbase where the troops are deployed.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
-- @param #number Speed Speed in km/h for travelling to the deploy base.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
if Airplane and Airplane:IsAlive()~=nil then
local Airbase = Coordinate:GetClosestAirbase()
if DeployZone then
Airbase=DeployZone:GetAirbase()
end
-- Activate uncontrolled airplane.
if Airplane:IsAlive()==false then
Airplane:SetCommand({id = 'Start', params = {}})
end
-- Route to destination airbase.
self:Route( Airplane, Airbase, Speed, Height )
-- Aircraft is on a depoly mission.
self.RouteDeploy = true
-- Set destination airbase for next :Route() command.
self.Airbase = Airbase
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterDeploy( self, Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
end
end
--- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone )
local UnboardInterval = 10
local UnboardDelay = 10
if Airplane and Airplane:IsAlive() then
for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do
local Cargos = AirplaneUnit:GetCargo()
for CargoID, Cargo in pairs( Cargos ) do
local Angle = 180
local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
self:T( { CargoCarrierHeading, CargoDeployHeading } )
local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading )
Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate )
UnboardDelay = UnboardDelay + UnboardInterval
Cargo:SetDeployed( true )
self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone )
end
end
end
end
--- Route the airplane from one airport or it's current position to another airbase.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase.
-- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do.
-- @param #number Height Height in meters to move to the Airbase.
-- @param #boolean Uncontrolled If true, spawn group in uncontrolled state.
function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled )
if Airplane and Airplane:IsAlive() then
-- Set takeoff type.
local Takeoff = SPAWN.Takeoff.Cold
-- Get template of group.
local Template = Airplane:GetTemplate()
-- Nil check
if Template==nil then
return
end
-- Waypoints of the route.
local Points={}
-- To point.
local AirbasePointVec2 = Airbase:GetPointVec2()
local ToWaypoint = AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
--ToWaypoint["airdromeId"] = Airbase:GetID()
--ToWaypoint["speed_locked"] = true
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
if self.Airbase then
-- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine.
Template.route.points[2] = ToWaypoint
-- Respawn group at the current airbase.
Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled)
else
-- From point.
local GroupPoint = Airplane:GetVec2()
local FromWaypoint = {}
FromWaypoint.x = GroupPoint.x
FromWaypoint.y = GroupPoint.y
FromWaypoint.type = "Turning Point"
FromWaypoint.action = "Turning Point"
FromWaypoint.speed = Airplane:GetSpeedMax()*0.8
-- The two route points.
Points[1] = FromWaypoint
Points[2] = ToWaypoint
local PointVec3 = Airplane:GetPointVec3()
Template.x = PointVec3.x
Template.y = PointVec3.z
Template.route.points = Points
local GroupSpawned = Airplane:Respawn(Template)
end
end
end
--- On after Home event. Aircraft will be routed to their home base.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane The cargo plane.
-- @param From From state.
-- @param Event Event.
-- @param To To State.
-- @param Core.Point#COORDINATE Coordinate Home place (not used).
-- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to.
function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, Height, HomeZone )
if Airplane and Airplane:IsAlive() then
-- We are going home!
self.RouteHome = true
-- Home Base.
local HomeBase=HomeZone:GetAirbase()
self.Airbase=HomeBase
-- Now route the airplane home
self:Route( Airplane, HomeBase, Speed, Height )
end
end
File diff suppressed because it is too large Load Diff
@@ -1,265 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
--
-- ## Features:
--
-- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...).
-- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates.
-- * Define a list of deploy zones of various types to transport the cargo to.
-- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground.
-- * Multiple vehicles can transport multiple cargo as one vehicle group.
-- * Multiple vehicle groups can be enabled as one collaborating transportation process.
-- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles.
-- * Different ranges can be setup for enemy defenses.
-- * Different options can be setup to tweak the cargo transporation behaviour.
--
-- ===
--
-- ## Test Missions:
--
-- Test missions can be located on the main GITHUB site.
--
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_APC
-- @image AI_Cargo_Dispatching_For_APC.JPG
-- @type AI_CARGO_DISPATCHER_APC
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo transportation capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module.
--
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
--
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
--
-- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
--
--
-- # 1) AI_CARGO_DISPATCHER_APC constructor.
--
-- * @{#AI_CARGO_DISPATCHER_APC.New}(): Creates a new AI_CARGO_DISPATCHER_APC object.
--
-- ---
--
-- # 2) AI_CARGO_DISPATCHER_APC is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
-- * Monitoring => Monitor => Monitoring
-- * Monitoring => Stop => Idle
--
-- * Monitoring => Pickup => Monitoring
-- * Monitoring => Load => Monitoring
-- * Monitoring => Loading => Monitoring
-- * Monitoring => Loaded => Monitoring
-- * Monitoring => PickedUp => Monitoring
-- * Monitoring => Deploy => Monitoring
-- * Monitoring => Unload => Monitoring
-- * Monitoring => Unloaded => Monitoring
-- * Monitoring => Deployed => Monitoring
-- * Monitoring => Home => Monitoring
--
--
-- ## 2.1) AI_CARGO_DISPATCHER States.
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_CARGO_DISPATCHER Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
-- * **Home**: A Carrier is going home.
--
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
--
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
--
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
--
-- ---
--
-- # 3) Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- # 4) Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- # 5) Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER_APC.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_APC
AI_CARGO_DISPATCHER_APC = {
ClassName = "AI_CARGO_DISPATCHER_APC",
}
--- Creates a new AI_CARGO_DISPATCHER_APC object.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param Core.Set#SET_GROUP APCSet The set of @{Wrapper.Group#GROUP} objects of vehicles, trucks, APCs that will transport the cargo.
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the APCs.
-- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters.
-- @return #AI_CARGO_DISPATCHER_APC
-- @usage
--
-- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones.
--
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
--
-- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones )
-- AICargoDispatcherAPC:Start()
--
function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC
self:SetDeploySpeed( 120, 70 )
self:SetPickupSpeed( 120, 70 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:SetPickupHeight()
self:SetDeployHeight()
self:SetCombatRadius( CombatRadius )
return self
end
--- AI cargo
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param Wrapper.Group#GROUP APC The APC carrier.
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
return aicargoapc
end
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
-- This is only valid for APCs and trucks etc, thus ground vehicles.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
-- When the combat radius is 0 (default), no defense will happen of the carrier.
-- When the combat radius is not provided, no defense will happen!
-- @return #AI_CARGO_DISPATCHER_APC
-- @usage
--
-- -- Disembark the infantry when the carrier is under attack.
-- AICargoDispatcher:SetCombatRadius( 500 )
--
-- -- Keep the cargo in the carrier when the carrier is under attack.
-- AICargoDispatcher:SetCombatRadius( 0 )
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
self.CombatRadius = CombatRadius or 0
return self
end
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
self:SetPickupOffRoad(Offroad, Formation)
self:SetDeployOffRoad(Offroad, Formation)
return self
end
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
@@ -1,171 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo using Planes.
--
-- ## Features:
--
-- * The airplanes will fly towards the pickup airbases to pickup the cargo.
-- * The airplanes will fly towards the deploy airbases to deploy the cargo.
--
-- ===
--
-- ## Test Missions:
--
-- Test missions can be located on the main GITHUB site.
--
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Airplane
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
-- @type AI_CARGO_DISPATCHER_AIRPLANE
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- Brings a dynamic cargo handling capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module.
--
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
--
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
--
-- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
--
-- # 1) AI_CARGO_DISPATCHER_AIRPLANE constructor.
--
-- * @{#AI_CARGO_DISPATCHER_AIRPLANE.New}(): Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
--
-- ---
--
-- # 2) AI_CARGO_DISPATCHER_AIRPLANE is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
-- * Monitoring => Monitor => Monitoring
-- * Monitoring => Stop => Idle
--
-- * Monitoring => Pickup => Monitoring
-- * Monitoring => Load => Monitoring
-- * Monitoring => Loading => Monitoring
-- * Monitoring => Loaded => Monitoring
-- * Monitoring => PickedUp => Monitoring
-- * Monitoring => Deploy => Monitoring
-- * Monitoring => Unload => Monitoring
-- * Monitoring => Unloaded => Monitoring
-- * Monitoring => Deployed => Monitoring
-- * Monitoring => Home => Monitoring
--
--
-- ## 2.1) AI_CARGO_DISPATCHER States.
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_CARGO_DISPATCHER Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
-- * **Home**: A Carrier is going home.
--
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
--
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
--
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
--
-- @field #AI_CARGO_DISPATCHER_AIRPLANE
AI_CARGO_DISPATCHER_AIRPLANE = {
ClassName = "AI_CARGO_DISPATCHER_AIRPLANE",
}
--- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
-- @param #AI_CARGO_DISPATCHER_AIRPLANE self
-- @param Core.Set#SET_GROUP AirplaneSet The set of @{Wrapper.Group#GROUP} objects of airplanes that will transport the cargo.
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
-- @param Core.Zone#SET_ZONE PickupZoneSet The set of zone airbases where the cargo has to be picked up.
-- @param Core.Zone#SET_ZONE DeployZoneSet The set of zone airbases where the cargo is deployed. Choice for each cargo is random.
-- @return #AI_CARGO_DISPATCHER_AIRPLANE self
-- @usage
--
-- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases.
--
-- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart()
-- local PickupZoneSet = SET_ZONE:New()
-- local DeployZoneSet = SET_ZONE:New()
--
-- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) )
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) )
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) )
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) )
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) )
--
-- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet )
-- AICargoDispatcherAirplanes:Start()
--
function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE
self:SetPickupSpeed( 1200, 600 )
self:SetDeploySpeed( 1200, 600 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:SetPickupHeight( 8000, 6000 )
self:SetDeployHeight( 8000, 6000 )
self:SetMonitorTimeInterval( 600 )
return self
end
function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet )
return AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
end
@@ -1,201 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo using Helicopters.
--
-- ## Features:
--
-- * The helicopters will fly towards the pickup locations to pickup the cargo.
-- * The helicopters will fly towards the deploy zones to deploy the cargo.
-- * Precision deployment as well as randomized deployment within the deploy zones are possible.
-- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free.
--
-- ===
--
-- ## Test Missions:
--
-- Test missions can be located on the main GITHUB site.
--
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Helicopter
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
-- @type AI_CARGO_DISPATCHER_HELICOPTER
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo handling capability for AI helicopter groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
--
-- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module.
--
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
--
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
--
-- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
--
-- ---
--
-- # 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor.
--
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object.
--
-- ---
--
-- # 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
-- * Monitoring => Monitor => Monitoring
-- * Monitoring => Stop => Idle
--
-- * Monitoring => Pickup => Monitoring
-- * Monitoring => Load => Monitoring
-- * Monitoring => Loading => Monitoring
-- * Monitoring => Loaded => Monitoring
-- * Monitoring => PickedUp => Monitoring
-- * Monitoring => Deploy => Monitoring
-- * Monitoring => Unload => Monitoring
-- * Monitoring => Unloaded => Monitoring
-- * Monitoring => Deployed => Monitoring
-- * Monitoring => Home => Monitoring
--
--
-- ## 2.1) AI_CARGO_DISPATCHER States.
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_CARGO_DISPATCHER Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
-- * **Home**: A Carrier is going home.
--
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
--
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
--
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
--
-- ---
--
-- ## 3. Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo.
--
-- ---
--
-- ## 4. Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo.
--
-- ---
--
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER_HELICOPTER.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_HELICOPTER
AI_CARGO_DISPATCHER_HELICOPTER = {
ClassName = "AI_CARGO_DISPATCHER_HELICOPTER",
}
--- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object.
-- @param #AI_CARGO_DISPATCHER_HELICOPTER self
-- @param Core.Set#SET_GROUP HelicopterSet The set of @{Wrapper.Group#GROUP} objects of helicopters that will transport the cargo.
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the Helicopters.
-- @return #AI_CARGO_DISPATCHER_HELICOPTER
-- @usage
--
-- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones.
--
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
--
-- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones )
-- AICargoDispatcherHelicopter:Start()
--
function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER
self:SetPickupSpeed( 350, 150 )
self:SetDeploySpeed( 350, 150 )
self:SetPickupRadius( 40, 12 )
self:SetDeployRadius( 40, 12 )
self:SetPickupHeight( 500, 200 )
self:SetDeployHeight( 500, 200 )
return self
end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
dispatcher:SetLandingSpeedAndHeight(27, 6)
return dispatcher
end
@@ -1,200 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo using Ships.
--
-- ## Features:
--
-- * Transport cargo to various deploy zones using naval vehicles.
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
-- * Define a deploy zone of various types to determine the destination of the cargo.
-- * Ships will follow shipping lanes as defined in the Mission Editor.
-- * Multiple ships can transport multiple cargo as a single group.
--
-- ===
--
-- ## Test Missions:
--
-- NEED TO DO
--
-- ===
--
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Ship
-- @image AI_Cargo_Dispatcher.JPG
-- @type AI_CARGO_DISPATCHER_SHIP
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo transportation capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
--
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
--
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
--
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
--
--
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
--
-- * @{#AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
--
-- ---
--
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
--
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
-- * Monitoring => Monitor => Monitoring
-- * Monitoring => Stop => Idle
--
-- * Monitoring => Pickup => Monitoring
-- * Monitoring => Load => Monitoring
-- * Monitoring => Loading => Monitoring
-- * Monitoring => Loaded => Monitoring
-- * Monitoring => PickedUp => Monitoring
-- * Monitoring => Deploy => Monitoring
-- * Monitoring => Unload => Monitoring
-- * Monitoring => Unloaded => Monitoring
-- * Monitoring => Deployed => Monitoring
-- * Monitoring => Home => Monitoring
--
--
-- ## 2.1) AI_CARGO_DISPATCHER States.
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_CARGO_DISPATCHER Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
-- * **Home**: A Carrier is going home.
--
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
--
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
--
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
--
-- ---
--
-- # 3) Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- # 4) Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- # 5) Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_SHIP
AI_CARGO_DISPATCHER_SHIP = {
ClassName = "AI_CARGO_DISPATCHER_SHIP"
}
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
-- @param #AI_CARGO_DISPATCHER_SHIP self
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
-- @return #AI_CARGO_DISPATCHER_SHIP
-- @usage
--
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
--
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
-- local ShippingLane = SET_GROUP:New():FilterPrefixes( "ShippingLane" ):FilterOnce():GetSetObjects()
--
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
-- AICargoDispatcherShip:Start()
--
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
self:SetPickupSpeed( 60, 10 )
self:SetDeploySpeed( 60, 10 )
self:SetPickupRadius( 500, 6000 )
self:SetDeployRadius( 500, 6000 )
self:SetPickupHeight( 0, 0 )
self:SetDeployHeight( 0, 0 )
self:SetShippingLane( ShippingLane )
self:SetMonitorTimeInterval( 600 )
return self
end
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
self.ShippingLane = ShippingLane
return self
end
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
end
@@ -1,663 +0,0 @@
--- **AI** - Models the intelligent transportation of cargo using helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Helicopter
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
-- @type AI_CARGO_HELICOPTER
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Brings a dynamic cargo handling capability for an AI helicopter group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
--
-- ## Infantry health.
--
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_CARGO_HELICOPTER
AI_CARGO_HELICOPTER = {
ClassName = "AI_CARGO_HELICOPTER",
Coordinate = nil, -- Core.Point#COORDINATE,
}
AI_CARGO_QUEUE = {}
--- Creates a new AI_CARGO_HELICOPTER object.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param Core.Set#SET_CARGO CargoSet
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" )
-- Boarding
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
-- Unboarding
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Destroyed", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
-- RTB
self:AddTransition( "*", "Home" , "*" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
--- Pickup Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #number Delay Delay in seconds.
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Deploy Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Deploy Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
-- @param #number Delay Delay in seconds.
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Home Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Home
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height (optional) Height the Helicopter should be flying at.
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
-- @param #number Delay Delay in seconds.
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height (optional) Height the Helicopter should be flying at.
-- We need to capture the Crash events for the helicopters.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
-- So, we need to unlock this when the helo is not anymore ...
Helicopter:HandleEvent( EVENTS.Crash,
function( Helicopter, EventData )
AI_CARGO_QUEUE[Helicopter] = nil
end
)
-- We need to capture the Land events for the helicopters.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
-- So, we need to unlock this when the helo has landed, which can be anywhere ...
-- But only free the landing coordinate after 1 minute, to ensure that all helos have left.
Helicopter:HandleEvent( EVENTS.Land,
function( Helicopter, EventData )
self:ScheduleOnce( 60,
function( Helicopter )
AI_CARGO_QUEUE[Helicopter] = nil
end, Helicopter
)
end
)
self:SetCarrier( Helicopter )
self.landingspeed = 15 -- kph
self.landingheight = 5.5 -- meter
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
local AICargo = self
self.Helicopter = Helicopter -- Wrapper.Group#GROUP
self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self )
self.RoutePickup = false
self.RouteDeploy = false
Helicopter:HandleEvent( EVENTS.Dead )
Helicopter:HandleEvent( EVENTS.Hit )
Helicopter:HandleEvent( EVENTS.Land )
function Helicopter:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Helicopter.
AICargoTroops:Destroyed()
end
end
end
function Helicopter:OnEventLand( EventData )
AICargo:Landed()
end
self.Coalition = self.Helicopter:GetCoalition()
self:SetControllable( Helicopter )
return self
end
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
-- @param #AI_CARGO_HELICOPTER self
-- @param #number speed Landing speed in kph(!), e.g. 15
-- @param #number height Landing height in meters(!), e.g. 5.5
-- @return #AI_CARGO_HELICOPTER self
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
--
-- BASE:TraceOn()
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
--
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
-- Adjust if necessary.
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
local _speed = speed or 15
local _height = height or 5.5
self.landingheight = _height
self.landingspeed = _speed
return self
end
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
self:F({From, Event, To})
Helicopter:F( { Name = Helicopter:GetName() } )
if Helicopter and Helicopter:IsAlive() then
-- S_EVENT_LAND is directly called in two situations:
-- 1 - When the helo lands normally on the ground.
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
-- For point 2, this is an issue, the infantry may not unload in this case!
-- So we check if the helo is on the ground, and velocity< 15.
-- Only then the infantry can unload (and load too, for consistency)!
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
if self.RoutePickup == true then
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
--self:Load( Helicopter:GetPointVec2() )
self:Load( self.PickupZone )
self.RoutePickup = false
end
end
if self.RouteDeploy == true then
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
self:Unload( self.DeployZone )
self.RouteDeploy = false
end
end
end
end
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
self:F({From, Event, To, Coordinate, Speed, DeployZone})
local HelicopterInZone = false
if Helicopter and Helicopter:IsAlive() == true then
local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() )
if Distance > 2000 then
self:__Queue( -10, Coordinate, Speed, DeployZone )
else
local ZoneFree = true
for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do
local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS
if ZoneQueue:IsCoordinateInZone( Coordinate ) then
ZoneFree = false
end
end
self:F({ZoneFree=ZoneFree})
if ZoneFree == true then
local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 )
AI_CARGO_QUEUE[Helicopter] = ZoneQueue
local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate()
-- local WaypointFrom = CoordinateFrom:WaypointAir(
-- "RADIO",
-- COORDINATE.WaypointType.TurningPoint,
-- COORDINATE.WaypointAction.TurningPoint,
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
-- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event.
self.DeployZone = DeployZone
else
self:__Queue( -10, Coordinate, Speed, DeployZone )
end
end
else
AI_CARGO_QUEUE[Helicopter] = nil
end
end
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
self:F({From, Event, To, Coordinate})
if Helicopter and Helicopter:IsAlive() then
local Route = {}
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 50, true)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route(Route, 0)
end
end
--- On after Deployed event.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
-- @param #boolean Deployed Cargo is deployed.
-- @return #boolean True if all cargo has been unloaded.
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
self:F( { From, Event, To, DeployZone = DeployZone } )
self:Orbit( Helicopter:GetCoordinate(), 50 )
-- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first.
self:ScheduleOnce( 30,
function( Helicopter )
AI_CARGO_QUEUE[Helicopter] = nil
end, Helicopter
)
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
end
--- On after Pickup event.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Pickup place.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
self:F({Coordinate, Speed, Height, PickupZone })
if Helicopter and Helicopter:IsAlive() ~= nil then
Helicopter:Activate()
self.RoutePickup = true
Coordinate.y = Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local Route = {}
--- Calculate the target route point.
local CoordinateFrom = Helicopter:GetCoordinate()
--- Create a route point of type air.
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true)
--- Create a route point of type air.
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint,_speed, true)
Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 1 )
self.PickupZone = PickupZone
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterPickup( self, Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
end
end
--- Depoloy function and queue.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP AICargoHelicopter
-- @param Core.Point#COORDINATE Coordinate Coordinate
function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone )
AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone )
end
--- On after Deploy event.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter Transport helicopter.
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the deploy coordinate.
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteDeploy = true
local Route = {}
--- Calculate the target route point.
Coordinate.y = Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
--- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true)
Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointFrom
--- Create a route point of type air.
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true)
Route[#Route+1] = WaypointTo
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
-- The _Deploy function does not exist.
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeploy( self, Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
end
end
--- On after Home event.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Home place.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
self:F({From, Event, To, Coordinate, Speed, Height})
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteHome = true
local Route = {}
--- Calculate the target route point.
--Coordinate.y = Height
Height = Height or 50
Speed = Speed or Helicopter:GetSpeedMax()*0.5
--- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true)
Route[#Route+1] = WaypointFrom
--- Create a route point of type air.
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true)
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route(Route, 0)
end
end
@@ -1,404 +0,0 @@
--- **AI** - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
--
-- ===
--
-- @module AI.AI_Cargo_Ship
-- @image AI_Cargo_Dispatcher.JPG
-- @type AI_CARGO_SHIP
-- @extends AI.AI_Cargo#AI_CARGO
--- Brings a dynamic cargo handling capability for an AI naval group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
-- cargo and the specified boarding radius.
--
-- ## Defending the Ship when enemies are nearby
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
--
-- ## Infantry or cargo **health**.
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
-- between warehouses.
--
-- ## Control the Ships on the map.
--
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #AI_CARGO_SHIP
AI_CARGO_SHIP = {
ClassName = "AI_CARGO_SHIP",
Coordinate = nil -- Core.Point#COORDINATE
}
--- Creates a new AI_CARGO_SHIP object.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
self:AddTransition( "*", "Home", "*" )
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
self:SetShippingLane ( ShippingLane )
self:SetCarrier( Ship )
return self
end
--- Set the Carrier
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
function CargoCarrier:OnEventDead( EventData )
self:F({"dead"})
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- Better hope they can swim!
AICargoTroops:Destroyed()
end
end
end
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
self.Coalition = self.CargoCarrier:GetCoalition()
self:SetControllable( CargoCarrier )
return self
end
--- FInd a free Carrier within a radius
-- @param #AI_CARGO_SHIP self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttributes( "Trucks" ) then
return NearUnit:GetGroup()
end
end
end
return nil
end
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
self.ShippingLane = ShippingLane
return self
end
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
self.CombatRadius = CombatRadius or 0
return self
end
--- Follow Infantry to the Carrier
-- @param #AI_CARGO_SHIP self
-- @param #AI_CARGO_SHIP Me
-- @param Wrapper.Unit#UNIT ShipUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
if ShipUnit:IsAlive() then
-- Check if the Cargo is near the CargoCarrier
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
-- Cargo does not need to navigate to Carrier
Me:Guard()
else
self:F( { InfantryGroup = InfantryGroup:GetName() } )
if InfantryGroup:IsAlive() then
self:F( { InfantryGroup = InfantryGroup:GetName() } )
local Waypoints = {}
-- Calculate new route
local FromCoord = InfantryGroup:GetCoordinate()
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
self:F({Waypoints=Waypoints})
local Waypoint = Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
end
end
end
end
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
if self.CombatRadius > 0 then
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
-- This is to avoid Unloading the Ship in the middle of the sea.
if Ship and Ship:IsAlive() then
if self.CarrierCoordinate then
if self:IsTransporting() == true then
local Coordinate = Ship:GetCoordinate()
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) then
-- There are no enemies within combat radius. Reload the CargoCarrier.
self:Reload()
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat radius. Unload the CargoCarrier.
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
else
if self:Is( "Unloaded" ) then
--self:Follow()
end
self:F( "I am here" .. self:GetCurrentState() )
if self:Is( "Following" ) then
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
if Cargo:IsAlive() then
if not Cargo:IsNear( ShipUnit, 40 ) then
ShipUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( ShipUnit, 25 ) then
ShipUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = Ship:GetCoordinate()
end
self:__Monitor( -5 )
end
end
--- Check if cargo ship is alive and trigger Load event
-- @param Wrapper.Group#Group Ship
-- @param #AI_CARGO_SHIP self
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
if Ship:IsAlive() then
self:Load( PickupZone )
end
end
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
-- @param Wrapper.Group#GROUP Ship
-- @param #AI_CARGO_SHIP self
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
if Ship:IsAlive() then
self:Unload( DeployZone )
end
end
--- on after Pickup event.
-- @param AI_CARGO_SHIP Ship
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate of the pickup point
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
if Ship and Ship:IsAlive() then
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
end
end
--- On after Deploy event.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP SHIP
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
if Ship and Ship:IsAlive() then
Speed = Speed or Ship:GetSpeedMax()*0.8
local lane = self.ShippingLane
if lane then
local Waypoints = {}
for i=1, #lane do
local coord = lane[i]
local Waypoint = coord:WaypointGround(_speed)
table.insert(Waypoints, Waypoint)
end
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
local Waypoint = Waypoints[#Waypoints]
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
Ship:Route(Waypoints, 1)
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end
--- On after Unload event.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP Ship
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
local UnboardInterval = 5
local UnboardDelay = 5
if Ship and Ship:IsAlive() then
for _, ShipUnit in pairs( Ship:GetUnits() ) do
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
Ship:RouteStop()
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
if Cargo:IsLoaded() then
local unboardCoord = DeployZone:GetRandomPointVec2()
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
if not Defend == true then
Cargo:SetDeployed( true )
end
end
end
end
end
end
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
if Ship and Ship:IsAlive() then
self.RouteHome = true
Speed = Speed or Ship:GetSpeedMax()*0.8
local lane = self.ShippingLane
if lane then
local Waypoints = {}
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
for i=#lane, 1, -1 do
local coord = lane[i]
local Waypoint = coord:WaypointGround(_speed)
table.insert(Waypoints, Waypoint)
end
local Waypoint = Waypoints[#Waypoints]
Ship:Route(Waypoints, 1)
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end
File diff suppressed because it is too large Load Diff
@@ -1,192 +0,0 @@
--- **AI** - Models the automatic assignment of AI escorts to player flights.
--
-- ## Features:
-- --
-- * Provides the facilities to trigger escorts when players join flight slots.
-- *
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Escort_Dispatcher
-- @image MOOSE.JPG
-- @type AI_ESCORT_DISPATCHER
-- @extends Core.Fsm#FSM
--- Models the automatic assignment of AI escorts to player flights.
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_ESCORT_DISPATCHER
AI_ESCORT_DISPATCHER = {
ClassName = "AI_ESCORT_DISPATCHER",
}
-- @field #list
AI_ESCORT_DISPATCHER.AI_Escorts = {}
--- Creates a new AI_ESCORT_DISPATCHER object.
-- @param #AI_ESCORT_DISPATCHER self
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in.
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
-- @return #AI_ESCORT_DISPATCHER
-- @usage
--
-- -- Create a new escort when a player joins an SU-25T plane.
-- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined.
-- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart()
--
-- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot.
-- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 )
--
-- -- Create an airbase object, where the escorts will be spawned.
-- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya )
--
-- -- Park the airplanes at the airbase, visible before start.
-- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig )
--
-- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase.
-- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group.
-- -- And a briefing to appear when the player joins the player slot.
-- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." )
--
-- -- The dispatcher needs to be started using the :Start() method.
-- Red_SU25T_EscortDispatcher:Start()
function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER
self.CarrierSet = CarrierSet
self.EscortSpawn = EscortSpawn
self.EscortAirbase = EscortAirbase
self.EscortName = EscortName
self.EscortBriefing = EscortBriefing
self:SetStartState( "Idle" )
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
self:AddTransition( "Idle", "Start", "Monitoring" )
self:AddTransition( "Monitoring", "Stop", "Idle" )
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
self:F( { Carrier = Carrier:GetName() } )
end
return self
end
function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To )
self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
end
-- @param #AI_ESCORT_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
local PlayerGroupName = EventData.IniGroupName
local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit
self:T({EscortAirbase= self.EscortAirbase } )
self:T({PlayerGroupName = PlayerGroupName } )
self:T({PlayerGroup = PlayerGroup})
self:T({FirstGroup = self.CarrierSet:GetFirst()})
self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then
if self.AI_Escorts[PlayerGroupName] then
self.AI_Escorts[PlayerGroupName]:Stop()
self.AI_Escorts[PlayerGroupName] = nil
end
end
end
-- @param #AI_ESCORT_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
local PlayerGroupName = EventData.IniGroupName
local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit
self:T({EscortAirbase= self.EscortAirbase } )
self:T({PlayerGroupName = PlayerGroupName } )
self:T({PlayerGroup = PlayerGroup})
self:T({FirstGroup = self.CarrierSet:GetFirst()})
self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then
if not self.AI_Escorts[PlayerGroupName] then
local LeaderUnit = PlayerUnit
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
self:T({EscortGroup = EscortGroup})
self:ScheduleOnce( 1,
function( EscortGroup )
local EscortSet = SET_GROUP:New()
EscortSet:AddGroup( EscortGroup )
self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing )
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
if EscortGroup:IsHelicopter() then
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
else
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
end
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
end, EscortGroup
)
end
end
end
--- Start Trigger for AI_ESCORT_DISPATCHER
-- @function [parent=#AI_ESCORT_DISPATCHER] Start
-- @param #AI_ESCORT_DISPATCHER self
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER
-- @function [parent=#AI_ESCORT_DISPATCHER] __Start
-- @param #AI_ESCORT_DISPATCHER self
-- @param #number Delay
--- Stop Trigger for AI_ESCORT_DISPATCHER
-- @function [parent=#AI_ESCORT_DISPATCHER] Stop
-- @param #AI_ESCORT_DISPATCHER self
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER
-- @function [parent=#AI_ESCORT_DISPATCHER] __Stop
-- @param #AI_ESCORT_DISPATCHER self
-- @param #number Delay
@@ -1,153 +0,0 @@
--- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu.
--
-- ## Features:
--
-- * Provides the facilities to trigger escorts when players join flight units.
-- * Provide a menu for which escorts can be requested.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Escort_Dispatcher_Request
-- @image MOOSE.JPG
-- @type AI_ESCORT_DISPATCHER_REQUEST
-- @extends Core.Fsm#FSM
--- Models the assignment of AI escorts to player flights upon request using the radio menu.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_ESCORT_DISPATCHER_REQUEST
AI_ESCORT_DISPATCHER_REQUEST = {
ClassName = "AI_ESCORT_DISPATCHER_REQUEST",
}
-- @field #list
AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {}
--- Creates a new AI_ESCORT_DISPATCHER_REQUEST object.
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested.
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
-- @return #AI_ESCORT_DISPATCHER_REQUEST
function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST
self.CarrierSet = CarrierSet
self.EscortSpawn = EscortSpawn
self.EscortAirbase = EscortAirbase
self.EscortName = EscortName
self.EscortBriefing = EscortBriefing
self:SetStartState( "Idle" )
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
self:AddTransition( "Idle", "Start", "Monitoring" )
self:AddTransition( "Monitoring", "Stop", "Idle" )
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
self:F( { Carrier = Carrier:GetName() } )
end
return self
end
function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To )
self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
end
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
-- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData )
local PlayerGroupName = EventData.IniGroupName
local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit
if self.CarrierSet:FindGroup( PlayerGroupName ) then
if self.AI_Escorts[PlayerGroupName] then
self.AI_Escorts[PlayerGroupName]:Stop()
self.AI_Escorts[PlayerGroupName] = nil
end
end
end
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
-- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData )
local PlayerGroupName = EventData.IniGroupName
local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit
if self.CarrierSet:FindGroup( PlayerGroupName ) then
if not self.AI_Escorts[PlayerGroupName] then
local LeaderUnit = PlayerUnit
self:ScheduleOnce( 0.1,
function()
self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing )
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
if PlayerGroup:IsHelicopter() then
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
else
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
end
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
end
)
end
end
end
--- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
-- @param #number Delay
--- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
-- @param #number Delay
@@ -1,323 +0,0 @@
--- **AI** - Taking the lead of AI escorting your flight or of other AI, upon request using the menu.
--
-- ===
--
-- ## Features:
--
-- * Escort navigation commands.
-- * Escort hold at position commands.
-- * Escorts reporting detected targets.
-- * Escorts scanning targets in advance.
-- * Escorts attacking specific targets.
-- * Request assistance from other groups for attack.
-- * Manage rule of engagement of escorts.
-- * Manage the allowed evasion techniques of escorts.
-- * Make escort to execute a defined mission or path.
-- * Escort tactical situation reporting.
--
-- ===
--
-- ## Missions:
--
-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort)
--
-- ===
--
-- Allows you to interact with escorting AI on your flight and take the lead.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
--
-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval.
-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets.
-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered.
--
-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available.
-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target.
--
-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location.
-- In this way, you can spread out the escorts over the battle field before a coordinated attack.
--
-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight.
--
-- ## Leading the flight
--
-- When leading the flight, you are expected to guide the escorts towards the target areas,
-- and carefully coordinate the attack based on the threat levels reported, and the available weapons
-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack.
--
-- ## Following the flight
--
-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead.
-- You as a player, are following the escorts, and are commanding them to progress the mission while
-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets
-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target
-- type. Once the attack is finished, the escort will resume the mission it was assigned.
-- In other words, you can use the escorts for reconnaissance, and for guiding the attack.
-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are
-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack
-- will commence and from where. You can control where the escorts are holding position and which targets
-- are attacked first. You are in control how the ka-50s will follow their mission path.
--
-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control.
-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the
-- attack is well coordinated.
--
-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism
-- it brings in your DCS world simulations.
--
-- # RADIO MENUs that can be created:
--
-- Find a summary below of the current available commands:
--
-- ## Navigation ...:
--
-- Escort group navigation functions:
--
-- * **"Join-Up":** The escort group fill follow you in the assigned formation.
-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
--
-- ## Hold position ...:
--
-- Escort group navigation functions:
--
-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter.
-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter.
--
-- ## Report targets ...:
--
-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below).
--
-- * **"Report now":** Will report the current detected targets.
-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list.
-- * **"Report targets off":** Will stop detecting targets.
--
-- ## Attack targets ...:
--
-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
-- This menu will be available in Flight menu or in each Escort menu.
--
-- ## Scan targets ...:
--
-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation.
--
-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
--
-- ## Request assistance from ...:
--
-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**.
-- This menu item allows to request attack support from other ground based escorts supporting the current escort.
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
--
-- ## ROE ...:
--
-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
--
-- * **"Hold Fire":** The escort group will hold fire.
-- * **"Return Fire":** The escort group will return fire.
-- * **"Open Fire":** The escort group will open fire on designated targets.
-- * **"Weapon Free":** The escort group will engage with any target.
--
-- ## Evasion ...:
--
-- Will define the evasion techniques that the escort group will perform during flight or combat.
--
-- * **"Fight until death":** The escort group will have no reaction to threats.
-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
--
-- ## Resume Mission ...:
--
-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- ### Authors: **FlightControl**
--
-- ===
--
-- @module AI.AI_Escort_Request
-- @image Escorting.JPG
-- @type AI_ESCORT_REQUEST
-- @extends AI.AI_Escort#AI_ESCORT
--- AI_ESCORT_REQUEST class
--
-- # AI_ESCORT_REQUEST construction methods.
--
-- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method:
--
-- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text.
--
-- @usage
-- -- Declare a new EscortPlanes object as follows:
--
-- -- First find the GROUP object and the CLIENT object.
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
--
-- -- Now use these 2 objects to construct the new EscortPlanes object.
-- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
--
-- @field #AI_ESCORT_REQUEST
AI_ESCORT_REQUEST = {
ClassName = "AI_ESCORT_REQUEST",
}
--- AI_ESCORT_REQUEST.Mode class
-- @type AI_ESCORT_REQUEST.MODE
-- @field #number FOLLOW
-- @field #number MISSION
--- MENUPARAM type
-- @type MENUPARAM
-- @field #AI_ESCORT_REQUEST ParamSelf
-- @field #Distance ParamDistance
-- @field #function ParamFunction
-- @field #string ParamMessage
--- AI_ESCORT_REQUEST class constructor for an AI group
-- @param #AI_ESCORT_REQUEST self
-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup.
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit.
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested.
-- @param #string EscortName Name of the escort.
-- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
-- @return #AI_ESCORT_REQUEST
-- @usage
-- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 )
-- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig )
--
-- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" )
--
-- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" )
-- Escort:FormationTrail( 50, 100, 100 )
-- Escort:Menus()
-- Escort:__Start( 5 )
function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes()
local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST
self.EscortGroupSet = EscortGroupSet
self.EscortSpawn = EscortSpawn
self.EscortAirbase = EscortAirbase
self.LeaderGroup = self.PlayerUnit:GetGroup()
self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 )
self.Detection:__Start( 30 )
self.SpawnMode = self.__Enum.Mode.Mission
return self
end
-- @param #AI_ESCORT_REQUEST self
function AI_ESCORT_REQUEST:SpawnEscort()
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
self:ScheduleOnce( 0.1,
function( EscortGroup )
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEHoldFire()
self.EscortGroupSet:AddGroup( EscortGroup )
local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP
local Report = REPORT:New()
Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) )
LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit )
self:SetFlightModeFormation( EscortGroup )
self:FormationTrail()
self:_InitFlightMenus()
self:_InitEscortMenus( EscortGroup )
self:_InitEscortRoute( EscortGroup )
-- @param #AI_ESCORT self
-- @param Core.Event#EVENTDATA EventData
function EscortGroup:OnEventDeadOrCrash( EventData )
self:F( { "EventDead", EventData } )
self.EscortMenu:Remove()
end
EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash )
EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash )
end, EscortGroup
)
end
-- @param #AI_ESCORT_REQUEST self
-- @param Core.Set#SET_GROUP EscortGroupSet
function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet )
self:F()
if not self.MenuRequestEscort then
self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName )
self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu,
function()
self:SpawnEscort()
end
)
end
self:GetParent( self ).onafterStart( self, EscortGroupSet )
self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash )
end
-- @param #AI_ESCORT_REQUEST self
-- @param Core.Set#SET_GROUP EscortGroupSet
function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet )
self:F()
EscortGroupSet:ForEachGroup(
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
EscortGroup:WayPointInitialize()
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEOpenFire()
end
)
self.Detection:Stop()
self.MainMenu:Remove()
end
--- Set the spawn mode to be mission execution.
-- @param #AI_ESCORT_REQUEST self
function AI_ESCORT_REQUEST:SetEscortSpawnMission()
self.SpawnMode = self.__Enum.Mode.Mission
end
-950
View File
@@ -1,950 +0,0 @@
--- **AI** - Perform Air Patrolling for airplanes.
--
-- **Features:**
--
-- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel threshold to RTB on time.
--
-- ===
--
-- AI PATROL classes makes AI Controllables execute an Patrol.
--
-- There are the following types of PATROL classes defined:
--
-- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Patrol)
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
-- * **Pikey**: Testing and API concept review.
--
-- ===
--
-- @module AI.AI_Patrol
-- @image AI_Air_Patrolling.JPG
--- AI_PATROL_ZONE class
-- @type AI_PATROL_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @field DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @field DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @field DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @field DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @field Core.Spawn#SPAWN CoordTest
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_PATROL\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_PATROL\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia9.JPG)
--
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
-- use derived AI_ classes to model AI offensive or defensive behaviour.
--
-- ![Process](..\Presentations\AI_PATROL\Dia10.JPG)
--
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_PATROL\Dia11.JPG)
--
-- ## 1. AI_PATROL_ZONE constructor
--
-- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object.
--
-- ## 2. AI_PATROL_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG)
--
-- ### 2.1. AI_PATROL_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Returning** ( Group ): The AI is returning to Base.
-- * **Stopped** ( Group ): The process is stopped.
-- * **Crashed** ( Group ): The AI has crashed or is dead.
--
-- ### 2.2. AI_PATROL_ZONE Events
--
-- * **Start** ( Group ): Start the process.
-- * **Stop** ( Group ): Stop the process.
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
-- * **RTB** ( Group ): Route the AI to the home base.
-- * **Detect** ( Group ): The AI is detecting targets.
-- * **Detected** ( Group ): The AI has detected new targets.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
--
-- ## 3. Set or Get the AI controllable
--
-- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable.
-- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable.
--
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
--
-- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
-- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
--
-- ## 5. Manage the detection process of the AI controllable
--
-- The detection process of the AI controllable can be manipulated.
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
--
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
--
-- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
--
-- The detection can be filtered to potential targets in a specific zone.
-- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected.
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
-- according the weather conditions.
--
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
--
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
-- while a new AI is targeted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base.
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this process in place.
--
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
--
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this process in place.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @field #AI_PATROL_ZONE
AI_PATROL_ZONE = {
ClassName = "AI_PATROL_ZONE",
}
--- Creates a new AI_PATROL_ZONE object
-- @param #AI_PATROL_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_PATROL_ZONE self
-- @usage
-- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
-- PatrolZone = ZONE:New( 'PatrolZone' )
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
-- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 )
function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
-- defafult PatrolAltType to "BARO" if not specified
self.PatrolAltType = PatrolAltType or "BARO"
self:SetRefreshTimeInterval( 30 )
self.CheckStatus = true
self:ManageFuel( .2, 60 )
self:ManageDamage( 1 )
self.DetectedUnits = {} -- This table contains the targets detected during patrol.
self:SetStartState( "None" )
self:AddTransition( "*", "Stop", "Stopped" )
--- OnLeave Transition Handler for State Stopped.
-- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Stopped.
-- @function [parent=#AI_PATROL_ZONE] OnEnterStopped
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- OnBefore Transition Handler for Event Stop.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStop
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Stop.
-- @function [parent=#AI_PATROL_ZONE] OnAfterStop
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Stop.
-- @function [parent=#AI_PATROL_ZONE] Stop
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Stop.
-- @function [parent=#AI_PATROL_ZONE] __Stop
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "None", "Start", "Patrolling" )
--- OnBefore Transition Handler for Event Start.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStart
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Start.
-- @function [parent=#AI_PATROL_ZONE] OnAfterStart
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Start.
-- @function [parent=#AI_PATROL_ZONE] Start
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Start.
-- @function [parent=#AI_PATROL_ZONE] __Start
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
--- OnBefore Transition Handler for Event Route.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Route.
-- @function [parent=#AI_PATROL_ZONE] OnAfterRoute
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Route.
-- @function [parent=#AI_PATROL_ZONE] Route
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Route.
-- @function [parent=#AI_PATROL_ZONE] __Route
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
--- OnBefore Transition Handler for Event Status.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Status.
-- @function [parent=#AI_PATROL_ZONE] OnAfterStatus
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Status.
-- @function [parent=#AI_PATROL_ZONE] Status
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Status.
-- @function [parent=#AI_PATROL_ZONE] __Status
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
--- OnBefore Transition Handler for Event Detect.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Detect.
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetect
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Detect.
-- @function [parent=#AI_PATROL_ZONE] Detect
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Detect.
-- @function [parent=#AI_PATROL_ZONE] __Detect
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
--- OnBefore Transition Handler for Event Detected.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Detected.
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetected
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Detected.
-- @function [parent=#AI_PATROL_ZONE] Detected
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event Detected.
-- @function [parent=#AI_PATROL_ZONE] __Detected
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
--- OnBefore Transition Handler for Event RTB.
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event RTB.
-- @function [parent=#AI_PATROL_ZONE] OnAfterRTB
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event RTB.
-- @function [parent=#AI_PATROL_ZONE] RTB
-- @param #AI_PATROL_ZONE self
--- Asynchronous Event Trigger for Event RTB.
-- @function [parent=#AI_PATROL_ZONE] __RTB
-- @param #AI_PATROL_ZONE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Returning.
-- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Returning.
-- @function [parent=#AI_PATROL_ZONE] OnEnterReturning
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
self:AddTransition( "*", "Eject", "*" )
self:AddTransition( "*", "Crash", "Crashed" )
self:AddTransition( "*", "PilotDead", "*" )
return self
end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_PATROL_ZONE self
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_PATROL_ZONE self
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
end
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
--- Set the detection on. The AI will detect for targets.
-- @param #AI_PATROL_ZONE self
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionOn()
self:F2()
self.DetectOn = true
end
--- Set the detection off. The AI will NOT detect for targets.
-- However, the list of already detected targets will be kept and can be enquired!
-- @param #AI_PATROL_ZONE self
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionOff()
self:F2()
self.DetectOn = false
end
--- Set the status checking off.
-- @param #AI_PATROL_ZONE self
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetStatusOff()
self:F2()
self.CheckStatus = false
end
--- Activate the detection. The AI will detect for targets if the Detection is switched On.
-- @param #AI_PATROL_ZONE self
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionActivated()
self:F2()
self:ClearDetectedUnits()
self.DetectActivated = true
self:__Detect( -self.DetectInterval )
end
--- Deactivate the detection. The AI will NOT detect for targets.
-- @param #AI_PATROL_ZONE self
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionDeactivated()
self:F2()
self:ClearDetectedUnits()
self.DetectActivated = false
end
--- Set the interval in seconds between each detection executed by the AI.
-- The list of already detected targets will be kept and updated.
-- Newly detected targets will be added, but already detected targets that were
-- not detected in this cycle, will NOT be removed!
-- The default interval is 30 seconds.
-- @param #AI_PATROL_ZONE self
-- @param #number Seconds The interval in seconds.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds )
self:F2()
if Seconds then
self.DetectInterval = Seconds
else
self.DetectInterval = 30
end
end
--- Set the detection zone where the AI is detecting targets.
-- @param #AI_PATROL_ZONE self
-- @param Core.Zone#ZONE DetectionZone The zone where to detect targets.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionZone( DetectionZone )
self:F2()
if DetectionZone then
self.DetectZone = DetectionZone
else
self.DetectZone = nil
end
end
--- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
-- No filtering is applied, so, ANY detected UNIT can be in this list.
-- It is up to the mission designer to use the @{Wrapper.Unit} class and methods to filter the targets.
-- @param #AI_PATROL_ZONE self
-- @return #table The list of @{Wrapper.Unit#UNIT}s
function AI_PATROL_ZONE:GetDetectedUnits()
self:F2()
return self.DetectedUnits
end
--- Clears the list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ClearDetectedUnits()
self:F2()
self.DetectedUnits = {}
end
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
return self
end
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on.
-- Therefore, when the damage threshold is reached,
-- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
--- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
self:F2()
self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live.
self:__Status( 60 ) -- Check status status every 30 seconds.
self:SetDetectionActivated()
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
self:HandleEvent( EVENTS.Crash, self.OnCrash )
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
self.Controllable:OnReSpawn(
function( PatrolGroup )
self:T( "ReSpawn" )
self:__Reset( 1 )
self:__Route( 5 )
end
)
self:SetDetectionOn()
end
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable+
function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To )
return self.DetectOn and self.DetectActivated
end
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
local Detected = false
local DetectedTargets = Controllable:GetDetectedTargets()
for TargetID, Target in pairs( DetectedTargets or {} ) do
local TargetObject = Target.object
if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then
local TargetUnit = UNIT:Find( TargetObject )
-- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234
if TargetUnit and TargetUnit:IsAlive() then
local TargetUnitName = TargetUnit:GetName()
if self.DetectionZone then
if TargetUnit:IsInZone( self.DetectionZone ) then
self:T( {"Detected ", TargetUnit } )
if self.DetectedUnits[TargetUnit] == nil then
self.DetectedUnits[TargetUnit] = true
end
Detected = true
end
else
if self.DetectedUnits[TargetUnit] == nil then
self.DetectedUnits[TargetUnit] = true
end
Detected = true
end
end
end
end
self:__Detect( -self.DetectInterval )
if Detected == true then
self:__Detected( 1.5 )
end
end
-- @param Wrapper.Controllable#CONTROLLABLE AIControllable
-- This static method is called from the route path within the last task at the last waypoint of the Controllable.
-- Note that this method is required, as triggers the next route when patrolling for the Controllable.
function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable )
local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE
PatrolZone:__Route( 1 )
end
--- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings.
-- @param #AI_PATROL_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
self:F2()
-- When RTB, don't allow anymore the routing.
if From == "RTB" then
return
end
local life = self.Controllable:GetLife() or 0
if self.Controllable:IsAlive() and life > 1 then
-- Determine if the AIControllable is within the PatrolZone.
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
local PatrolRoute = {}
-- Calculate the current route point of the controllable as the start point of the route.
-- However, when the controllable is not in the air,
-- the controllable current waypoint is probably the airbase...
-- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies
-- immediately back to the airbase, and this is not correct.
-- Therefore, when on a runway, get as the current route point a random point within the PatrolZone.
-- This will make the plane fly immediately to the patrol zone.
if self.Controllable:InAir() == false then
self:T( "Not in the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return end
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TakeOffParking,
COORDINATE.WaypointAction.FromParkingArea,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
else
self:T( "In the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
end
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
--- Find a random 2D point in PatrolZone.
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
self:T2( ToTargetVec2 )
--- Define Speed and Altitude.
local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
--- Obtain a 3D @{Point} from the 2D point + altitude.
local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
ToTargetSpeed,
true
)
--self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() )
--ToTargetPointVec3:SmokeRed()
PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
self.Controllable:WayPointInitialize( PatrolRoute )
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ...
self.Controllable:SetState( self.Controllable, "PatrolZone", self )
self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" )
--- NOW ROUTE THE GROUP!
self.Controllable:WayPointExecute( 1, 2 )
end
end
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onbeforeStatus()
return self.CheckStatus
end
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterStatus()
self:F2()
if self.Controllable and self.Controllable:IsAlive() then
local RTB = false
local Fuel = self.Controllable:GetFuelMin()
if Fuel < self.PatrolFuelThresholdPercentage then
self:T( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
RTB = true
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageThreshold then
self:T( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true
end
if self:IsInstanceOf("AI_CAS") or self:IsInstanceOf("AI_BAI") then
local atotal,shells,rockets,bombs,missiles = self.Controllable:GetAmmunition()
local arelevant = rockets+bombs
if arelevant == 0 or missiles == 0 then
RTB = true
self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles})
self:T( self.Controllable:GetName() .. " is out of ammo, RTB!" )
end
end
if RTB == true then
self:RTB()
else
self:__Status( 60 ) -- Execute the Patrol event after 30 seconds.
end
end
end
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterRTB()
self:F2()
if self.Controllable and self.Controllable:IsAlive() then
self:SetDetectionOff()
self.CheckStatus = false
local PatrolRoute = {}
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
COORDINATE.WaypointType.TurningPoint,
COORDINATE.WaypointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
self.Controllable:WayPointInitialize( PatrolRoute )
--- NOW ROUTE THE GROUP!
self.Controllable:WayPointExecute( 1, 1 )
end
end
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterDead()
self:SetDetectionOff()
self:SetStatusOff()
end
-- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData )
end
end
end
-- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnEjection( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__Eject( 1, EventData )
end
end
-- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnPilotDead( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__PilotDead( 1, EventData )
end
end
@@ -1,312 +0,0 @@
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ===
--
-- @module Actions.Act_Account
-- @image MOOSE.JPG
do -- ACT_ACCOUNT
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ## ACT_ACCOUNT state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
-- but will have **different implementation behaviour** upon each event or state transition.
--
-- ### ACT_ACCOUNT States
--
-- * **Assigned**: The player is assigned.
-- * **Waiting**: Waiting for an event.
-- * **Report**: Reporting.
-- * **Account**: Account for an event.
-- * **Accounted**: All events have been accounted for, end of the process.
-- * **Failed**: Failed the process.
--
-- ### ACT_ACCOUNT Events
--
-- * **Start**: Start the process.
-- * **Wait**: Wait for an event.
-- * **Report**: Report the status of the accounting.
-- * **Event**: An event happened, process the event.
-- * **More**: More targets.
-- * **NoMore (*)**: No more targets.
-- * **Fail (*)**: The action process has failed.
--
-- (*) End states of the process.
--
-- ### ACT_ACCOUNT state transition methods:
--
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
-- There are 2 moments when state transition methods will be called by the state machine:
--
-- * **Before** the state transition.
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
-- If the state transition method returns false, then the processing of the state transition will not be done!
-- If you want to change the behaviour of the AIControllable at this event, return false,
-- but then you'll need to specify your own logic using the AIControllable!
--
-- * **After** the state transition.
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @type ACT_ACCOUNT
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Core.Fsm#FSM_PROCESS
ACT_ACCOUNT = {
ClassName = "ACT_ACCOUNT",
TargetSetUnit = nil,
}
--- Creates a new DESTROY process.
-- @param #ACT_ACCOUNT self
-- @return #ACT_ACCOUNT
function ACT_ACCOUNT:New()
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS
self:AddTransition( "Assigned", "Start", "Waiting" )
self:AddTransition( "*", "Wait", "Waiting" )
self:AddTransition( "*", "Report", "Report" )
self:AddTransition( "*", "Event", "Account" )
self:AddTransition( "Account", "Player", "AccountForPlayer" )
self:AddTransition( "Account", "Other", "AccountForOther" )
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" )
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddEndState( "Failed" )
self:SetStartState( "Assigned" )
return self
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To )
self:HandleEvent( EVENTS.Dead, self.onfuncEventDead )
self:HandleEvent( EVENTS.Crash, self.onfuncEventCrash )
self:HandleEvent( EVENTS.Hit )
self:__Wait( 1 )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To )
if self.DisplayCount >= self.DisplayInterval then
self:Report()
self.DisplayCount = 1
else
self.DisplayCount = self.DisplayCount + 1
end
return true -- Process always the event.
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To )
self:__NoMore( 1 )
end
end -- ACT_ACCOUNT
do -- ACT_ACCOUNT_DEADS
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{#ACT_ACCOUNT}
--
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
-- The process is given a @{Core.Set} of units that will be tracked upon successful destruction.
-- The process will end after each target has been successfully destroyed.
-- Each successful dead will trigger an Account state transition that can be scored, modified or administered.
--
--
-- ## ACT_ACCOUNT_DEADS constructor:
--
-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object.
--
-- @type ACT_ACCOUNT_DEADS
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends #ACT_ACCOUNT
ACT_ACCOUNT_DEADS = {
ClassName = "ACT_ACCOUNT_DEADS",
}
--- Creates a new DESTROY process.
-- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskName
function ACT_ACCOUNT_DEADS:New()
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
self.DisplayCategory = "HQ" -- Targets is the default display category
return self
end
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
self.Task = self:GetTask()
self.TaskName = self.Task:GetName()
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessUnit:GetPlayerName()
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
if PlayerHit == PlayerName then
self:Player( EventData )
else
self:Other( EventData )
end
end
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
local PlayerName = ProcessUnit:GetPlayerName()
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
if Task.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )
end
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
if Task.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )
end
end
--- DCS Events
-- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:OnEventHit( EventData )
self:T( { "EventDead", EventData } )
if EventData.IniPlayerName and EventData.TgtDCSUnitName then
self.PlayerHits = self.PlayerHits or {}
self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName
end
end
-- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
self:T( { "EventDead", EventData } )
if EventData.IniDCSUnit then
self:Event( EventData )
end
end
--- DCS Events
-- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
self:T( { "EventDead", EventData } )
if EventData.IniDCSUnit then
self:Event( EventData )
end
end
end -- ACT_ACCOUNT DEADS
@@ -1,294 +0,0 @@
--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments.
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ## ACT_ASSIGN state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
-- but will have **different implementation behaviour** upon each event or state transition.
--
-- ### ACT_ASSIGN **Events**:
--
-- These are the events defined in this class:
--
-- * **Start**: Start the tasking acceptance process.
-- * **Assign**: Assign the task.
-- * **Reject**: Reject the task..
--
-- ### ACT_ASSIGN **Event methods**:
--
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
--
-- * **Immediate**: The event method has exactly the name of the event.
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
--
-- ### ACT_ASSIGN **States**:
--
-- * **UnAssigned**: The player has not accepted the task.
-- * **Assigned (*)**: The player has accepted the task.
-- * **Rejected (*)**: The player has not accepted the task.
-- * **Waiting**: The process is awaiting player feedback.
-- * **Failed (*)**: The process has failed.
--
-- (*) End states of the process.
--
-- ### ACT_ASSIGN state transition methods:
--
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
-- There are 2 moments when state transition methods will be called by the state machine:
--
-- * **Before** the state transition.
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
-- If the state transition method returns false, then the processing of the state transition will not be done!
-- If you want to change the behaviour of the AIControllable at this event, return false,
-- but then you'll need to specify your own logic using the AIControllable!
--
-- * **After** the state transition.
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN}
--
-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.
--
-- ## 1.1) ACT_ASSIGN_ACCEPT constructor:
--
-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object.
--
-- ===
--
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN}
--
-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option.
-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task.
-- The assignment type also allows to reject the task.
--
-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor:
-- -----------------------------------------
--
-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object.
--
-- ===
--
-- @module Actions.Act_Assign
-- @image MOOSE.JPG
do -- ACT_ASSIGN
--- ACT_ASSIGN class
-- @type ACT_ASSIGN
-- @field Tasking.Task#TASK Task
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends Core.Fsm#FSM_PROCESS
ACT_ASSIGN = {
ClassName = "ACT_ASSIGN",
}
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
-- @param #ACT_ASSIGN self
-- @return #ACT_ASSIGN The task acceptance process.
function ACT_ASSIGN:New()
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS
self:AddTransition( "UnAssigned", "Start", "Waiting" )
self:AddTransition( "Waiting", "Assign", "Assigned" )
self:AddTransition( "Waiting", "Reject", "Rejected" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddEndState( "Assigned" )
self:AddEndState( "Rejected" )
self:AddEndState( "Failed" )
self:SetStartState( "UnAssigned" )
return self
end
end -- ACT_ASSIGN
do -- ACT_ASSIGN_ACCEPT
--- ACT_ASSIGN_ACCEPT class
-- @type ACT_ASSIGN_ACCEPT
-- @field Tasking.Task#TASK Task
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIGN
ACT_ASSIGN_ACCEPT = {
ClassName = "ACT_ASSIGN_ACCEPT",
}
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
-- @param #ACT_ASSIGN_ACCEPT self
-- @param #string TaskBriefing
function ACT_ASSIGN_ACCEPT:New( TaskBriefing )
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT
self.TaskBriefing = TaskBriefing
return self
end
function ACT_ASSIGN_ACCEPT:Init( FsmAssign )
self.TaskBriefing = FsmAssign.TaskBriefing
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
self:__Assign( 1 )
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
end -- ACT_ASSIGN_ACCEPT
do -- ACT_ASSIGN_MENU_ACCEPT
--- ACT_ASSIGN_MENU_ACCEPT class
-- @type ACT_ASSIGN_MENU_ACCEPT
-- @field Tasking.Task#TASK Task
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIGN
ACT_ASSIGN_MENU_ACCEPT = {
ClassName = "ACT_ASSIGN_MENU_ACCEPT",
}
--- Init.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing )
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
self.TaskBriefing = TaskBriefing
return self
end
--- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
self.TaskBriefing = TaskBriefing
return self
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 )
local TaskGroup = ProcessUnit:GetGroup()
self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" )
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup )
self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup )
self:__Reject( 120, TaskGroup )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup )
self:__Assign( -1, TaskGroup )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
self:__Reject( -1, TaskGroup )
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
self.Menu:Remove()
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup )
self:F( { TaskGroup = TaskGroup } )
self.Menu:Remove()
--TODO: need to resolve this problem ... it has to do with the events ...
--self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event
self.Task:RejectGroup( TaskGroup )
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
--self.Task:AssignToGroup( TaskGroup )
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
end -- ACT_ASSIGN_MENU_ACCEPT
@@ -1,220 +0,0 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
-- ![Banner Image](..\Images\deprecated.png)
-- ## ACT_ASSIST state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
-- but will have **different implementation behaviour** upon each event or state transition.
--
-- ### ACT_ASSIST **Events**:
--
-- These are the events defined in this class:
--
-- * **Start**: The process is started.
-- * **Next**: The process is smoking the targets in the given zone.
--
-- ### ACT_ASSIST **Event methods**:
--
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
--
-- * **Immediate**: The event method has exactly the name of the event.
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
--
-- ### ACT_ASSIST **States**:
--
-- * **None**: The controllable did not receive route commands.
-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone.
-- * **Smoking (*)**: The process is smoking the targets in the zone.
-- * **Failed (*)**: The process has failed.
--
-- (*) End states of the process.
--
-- ### ACT_ASSIST state transition methods:
--
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
-- There are 2 moments when state transition methods will be called by the state machine:
--
-- * **Before** the state transition.
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
-- If the state transition method returns false, then the processing of the state transition will not be done!
-- If you want to change the behaviour of the AIControllable at this event, return false,
-- but then you'll need to specify your own logic using the AIControllable!
--
-- * **After** the state transition.
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- ===
--
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{#ACT_ASSIST}
--
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Core.Zone}.
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
-- At random intervals, a new target is smoked.
--
-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor:
--
-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @module Actions.Act_Assist
-- @image MOOSE.JPG
do -- ACT_ASSIST
--- ACT_ASSIST class
-- @type ACT_ASSIST
-- @extends Core.Fsm#FSM_PROCESS
ACT_ASSIST = {
ClassName = "ACT_ASSIST",
}
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIST self
-- @return #ACT_ASSIST
function ACT_ASSIST:New()
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS
self:AddTransition( "None", "Start", "AwaitSmoke" )
self:AddTransition( "AwaitSmoke", "Next", "Smoking" )
self:AddTransition( "Smoking", "Next", "AwaitSmoke" )
self:AddTransition( "*", "Stop", "Success" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddEndState( "Failed" )
self:AddEndState( "Success" )
self:SetStartState( "None" )
return self
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ASSIST self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To )
local ProcessGroup = ProcessUnit:GetGroup()
local MissionMenu = self:GetMission():GetMenu( ProcessGroup )
local function MenuSmoke( MenuParam )
local self = MenuParam.self
local SmokeColor = MenuParam.SmokeColor
self.SmokeColor = SmokeColor
self:__Next( 1 )
end
self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu )
self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } )
self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } )
self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } )
self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } )
self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } )
end
--- StateMachine callback function
-- @param #ACT_ASSIST self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To )
self.Menu:Remove() -- When stopped, remove the menus
end
end
do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
--- ACT_ASSIST_SMOKE_TARGETS_ZONE class
-- @type ACT_ASSIST_SMOKE_TARGETS_ZONE
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIST
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE",
}
-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor()
-- self:E("_Destructor")
--
-- self.Menu:Remove()
-- self:EventRemoveAll()
-- end
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param Core.Zone#ZONE_BASE TargetZone
function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone )
local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST
self.TargetSetUnit = TargetSetUnit
self.TargetZone = TargetZone
return self
end
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke )
self.TargetSetUnit = FsmSmoke.TargetSetUnit
self.TargetZone = FsmSmoke.TargetZone
end
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param Core.Zone#ZONE_BASE TargetZone
-- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone )
self.TargetSetUnit = TargetSetUnit
self.TargetZone = TargetZone
return self
end
--- StateMachine callback function
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To )
self.TargetSetUnit:ForEachUnit(
-- @param Wrapper.Unit#UNIT SmokeUnit
function( SmokeUnit )
if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then
SCHEDULER:New( self,
function()
if SmokeUnit:IsAlive() then
SmokeUnit:Smoke( self.SmokeColor, 150 )
end
end, {}, math.random( 10, 60 )
)
end
end
)
end
end
@@ -1,485 +0,0 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ## ACT_ROUTE state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
-- but will have **different implementation behaviour** upon each event or state transition.
--
-- ### ACT_ROUTE **Events**:
--
-- These are the events defined in this class:
--
-- * **Start**: The process is started. The process will go into the Report state.
-- * **Report**: The process is reporting to the player the route to be followed.
-- * **Route**: The process is routing the controllable.
-- * **Pause**: The process is pausing the route of the controllable.
-- * **Arrive**: The controllable has arrived at a route point.
-- * **More**: There are more route points that need to be followed. The process will go back into the Report state.
-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state.
--
-- ### ACT_ROUTE **Event methods**:
--
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
--
-- * **Immediate**: The event method has exactly the name of the event.
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
--
-- ### ACT_ROUTE **States**:
--
-- * **None**: The controllable did not receive route commands.
-- * **Arrived (*)**: The controllable has arrived at a route point.
-- * **Aborted (*)**: The controllable has aborted the route path.
-- * **Routing**: The controllable is understay to the route point.
-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around.
-- * **Success (*)**: All route points were reached.
-- * **Failed (*)**: The process has failed.
--
-- (*) End states of the process.
--
-- ### ACT_ROUTE state transition methods:
--
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
-- There are 2 moments when state transition methods will be called by the state machine:
--
-- * **Before** the state transition.
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
-- If the state transition method returns false, then the processing of the state transition will not be done!
-- If you want to change the behaviour of the AIControllable at this event, return false,
-- but then you'll need to specify your own logic using the AIControllable!
--
-- * **After** the state transition.
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- ===
--
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{#ACT_ROUTE}
--
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Core.Zone}.
-- The player receives on perioding times messages with the coordinates of the route to follow.
-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended.
--
-- # 1.1) ACT_ROUTE_ZONE constructor:
--
-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- @module Actions.Act_Route
-- @image MOOSE.JPG
do -- ACT_ROUTE
--- ACT_ROUTE class
-- @type ACT_ROUTE
-- @field Tasking.Task#TASK TASK
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE Zone
-- @field Core.Point#COORDINATE Coordinate
-- @extends Core.Fsm#FSM_PROCESS
ACT_ROUTE = {
ClassName = "ACT_ROUTE",
}
--- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE.
-- @param #ACT_ROUTE self
-- @return #ACT_ROUTE self
function ACT_ROUTE:New()
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS
self:AddTransition( "*", "Reset", "None" )
self:AddTransition( "None", "Start", "Routing" )
self:AddTransition( "*", "Report", "*" )
self:AddTransition( "Routing", "Route", "Routing" )
self:AddTransition( "Routing", "Pause", "Pausing" )
self:AddTransition( "Routing", "Arrive", "Arrived" )
self:AddTransition( "*", "Cancel", "Cancelled" )
self:AddTransition( "Arrived", "Success", "Success" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddTransition( "", "", "" )
self:AddTransition( "", "", "" )
self:AddEndState( "Arrived" )
self:AddEndState( "Failed" )
self:AddEndState( "Cancelled" )
self:SetStartState( "None" )
self:SetRouteMode( "C" )
return self
end
--- Set a Cancel Menu item.
-- @param #ACT_ROUTE self
-- @return #ACT_ROUTE
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
MenuGroup,
MenuText,
ParentMenu,
self.MenuCancel,
self
):SetTime( MenuTime ):SetTag( MenuTag )
ParentMenu:SetTime( MenuTime )
ParentMenu:Remove( MenuTime, MenuTag )
return self
end
--- Set the route mode.
-- There are 2 route modes supported:
--
-- * SetRouteMode( "B" ): Route mode is Bearing and Range.
-- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup.
--
-- @param #ACT_ROUTE self
-- @return #ACT_ROUTE
function ACT_ROUTE:SetRouteMode( RouteMode )
self.RouteMode = RouteMode
return self
end
--- Get the routing text to be displayed.
-- The route mode determines the text displayed.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT Controllable
-- @return #string
function ACT_ROUTE:GetRouteText( Controllable )
local RouteText = ""
local Coordinate = nil -- Core.Point#COORDINATE
if self.Coordinate then
Coordinate = self.Coordinate
end
if self.Zone then
Coordinate = self.Zone:GetPointVec3( self.Altitude )
Coordinate:SetHeading( self.Heading )
end
local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G).
local CC = self:GetTask():GetMission():GetCommandCenter()
if CC then
if CC:IsModeWWII() then
-- Find closest reference point to the target.
local ShortestDistance = 0
local ShortestReferencePoint = nil
local ShortestReferenceName = ""
self:F( { CC.ReferencePoints } )
for ZoneName, Zone in pairs( CC.ReferencePoints ) do
self:F( { ZoneName = ZoneName } )
local Zone = Zone -- Core.Zone#ZONE
local ZoneCoord = Zone:GetCoordinate()
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
self:F( { ShortestDistance, ShortestReferenceName } )
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
ShortestDistance = ZoneDistance
ShortestReferencePoint = ZoneCoord
ShortestReferenceName = CC.ReferenceNames[ZoneName]
end
end
if ShortestReferencePoint then
RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable )
end
else
RouteText = Coordinate:ToString( Controllable, nil, Task )
end
end
return RouteText
end
function ACT_ROUTE:MenuCancel()
self:F("Cancelled")
self.CancelMenuGroupCommand:Remove()
self:__Cancel( 1 )
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To )
self:__Route( 1 )
end
--- Check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
return false
end
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
if ProcessUnit:IsAlive() then
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
if self.DisplayCount >= self.DisplayInterval then
self:T( { HasArrived = HasArrived } )
if not HasArrived then
self:Report()
end
self.DisplayCount = 1
else
self.DisplayCount = self.DisplayCount + 1
end
if HasArrived then
self:__Arrive( 1 )
else
self:__Route( 1 )
end
return HasArrived -- if false, then the event will not be executed...
end
return false
end
end -- ACT_ROUTE
do -- ACT_ROUTE_POINT
--- ACT_ROUTE_POINT class
-- @type ACT_ROUTE_POINT
-- @field Tasking.Task#TASK TASK
-- @extends #ACT_ROUTE
ACT_ROUTE_POINT = {
ClassName = "ACT_ROUTE_POINT",
}
--- Creates a new routing state machine.
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
-- @param #ACT_ROUTE_POINT self
-- @param Core.Point#COORDINATE The Coordinate to Target.
-- @param #number Range The Distance to Target.
-- @param Core.Zone#ZONE_BASE Zone
function ACT_ROUTE_POINT:New( Coordinate, Range )
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT
self.Coordinate = Coordinate
self.Range = Range or 0
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
return self
end
--- Creates a new routing state machine.
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
-- @param #ACT_ROUTE_POINT self
function ACT_ROUTE_POINT:Init( FsmRoute )
self.Coordinate = FsmRoute.Coordinate
self.Range = FsmRoute.Range or 0
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
self:SetStartState("None")
end
--- Set Coordinate
-- @param #ACT_ROUTE_POINT self
-- @param Core.Point#COORDINATE Coordinate The Coordinate to route to.
function ACT_ROUTE_POINT:SetCoordinate( Coordinate )
self:F2( { Coordinate } )
self.Coordinate = Coordinate
end
--- Get Coordinate
-- @param #ACT_ROUTE_POINT self
-- @return Core.Point#COORDINATE Coordinate The Coordinate to route to.
function ACT_ROUTE_POINT:GetCoordinate()
self:F2( { self.Coordinate } )
return self.Coordinate
end
--- Set Range around Coordinate
-- @param #ACT_ROUTE_POINT self
-- @param #number Range The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:SetRange( Range )
self:F2( { Range } )
self.Range = Range or 10000
end
--- Get Range around Coordinate
-- @param #ACT_ROUTE_POINT self
-- @return #number The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:GetRange()
self:F2( { self.Range } )
return self.Range
end
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
if ProcessUnit:IsAlive() then
local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() )
if Distance <= self.Range then
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived."
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
return true
end
end
return false
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_POINT
do -- ACT_ROUTE_ZONE
--- ACT_ROUTE_ZONE class
-- @type ACT_ROUTE_ZONE
-- @field Tasking.Task#TASK TASK
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE Zone
-- @extends #ACT_ROUTE
ACT_ROUTE_ZONE = {
ClassName = "ACT_ROUTE_ZONE",
}
--- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE.
-- @param #ACT_ROUTE_ZONE self
-- @param Core.Zone#ZONE_BASE Zone
function ACT_ROUTE_ZONE:New( Zone )
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE
self.Zone = Zone
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
return self
end
function ACT_ROUTE_ZONE:Init( FsmRoute )
self.Zone = FsmRoute.Zone
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
end
--- Set Zone
-- @param #ACT_ROUTE_ZONE self
-- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to.
-- @param #number Altitude
-- @param #number Heading
function ACT_ROUTE_ZONE:SetZone( Zone, Altitude, Heading ) -- R2.2 Added altitude and heading
self.Zone = Zone
self.Altitude = Altitude
self.Heading = Heading
end
--- Get Zone
-- @param #ACT_ROUTE_ZONE self
-- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to.
function ACT_ROUTE_ZONE:GetZone()
return self.Zone
end
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
if ProcessUnit:IsInZone( self.Zone ) then
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived within the zone."
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
return ProcessUnit:IsInZone( self.Zone )
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE_ZONE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To )
self:F( { ProcessUnit = ProcessUnit } )
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_ZONE
+1 -1
View File
@@ -372,7 +372,7 @@ CARGOS = {}
do -- CARGO
-- @type CARGO
--- @type CARGO
-- @extends Core.Fsm#FSM_PROCESS
-- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers.
-- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo.
+1 -1
View File
@@ -1820,7 +1820,7 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
if Event.IniDCSUnit then
-- 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()
if IsPlayer then
+20 -38
View File
@@ -184,6 +184,7 @@ EVENT = {
ClassName = "EVENT",
ClassID = 0,
MissionEnd = false,
CreateMarkCoordinateOnEvent = false,
}
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 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 #string ZoneName The name of the zone.
--
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
-- @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
--- 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.
-- @param #EVENT self
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
@@ -1361,7 +1336,8 @@ function EVENT:onEvent( Event )
Event.IniDynamicCargoName = Event.IniUnitName
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
else
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
--Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
end
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
@@ -1374,7 +1350,9 @@ function EVENT:onEvent( Event )
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.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.IniTypeName = (Event.initiator and Event.initiator.isExist
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
@@ -1475,10 +1453,12 @@ function EVENT:onEvent( Event )
-- SCENERY
---
Event.TgtDCSUnit = Event.target
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
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.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
@@ -1519,7 +1499,9 @@ function EVENT:onEvent( Event )
if Event.idx then
Event.MarkID=Event.idx
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.MarkCoalition=Event.coalition
Event.IniCoalition=Event.coalition
+1
View File
@@ -79,6 +79,7 @@
do -- FSM
--- FSM class
-- @type FSM
-- @field #string ClassName Name of the class.
-- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler.
+26 -13
View File
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.1.4",
version = "0.1.5",
debug = false,
Casesensitive = true,
}
@@ -59,9 +59,8 @@ MARKEROPS_BASE = {
-- @param #MARKEROPS_BASE self
-- @param #string Tagname Name to identify us from the event text.
-- @param #table Keywords Table of keywords recognized from the event text.
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
-- @return #MARKEROPS_BASE self
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
function MARKEROPS_BASE:New(Tagname,Keywords)
-- Inherit FSM
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.debug = false
self.Casesensitive = true
if Casesensitive and Casesensitive == false then
self.Casesensitive = false
end
-----------------------
--- FSM Transitions ---
-----------------------
@@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
end
--- (internal) Handle events.
--- (Internal) Handle events.
-- @param #MARKEROPS_BASE self
-- @param Core.Event#EVENTDATA Event
function MARKEROPS_BASE:OnEventMark(Event)
@@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event)
end
end
--- (internal) Match tag.
--- (Internal) Match tag.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #boolean
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches = false
if not self.Casesensitive then
--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
if string.find(string.lower(Eventtext),type) then
if string.find(string.lower(Eventtext),type,1,true) then
matches = true --event text contains tag
end
else
@@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext)
return matches
end
--- (internal) Match keywords table.
--- (Internal) Match keywords table.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #table
@@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:UnHandleEvent(EVENTS.MarkRemoved)
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.
--------------------------------------------------------------------------
+79 -4
View File
@@ -49,7 +49,7 @@
--
-- # Mark on F10 map
--
-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
-- The points of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
-- water depth.
--
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
@@ -69,11 +69,12 @@ PATHLINE = {
-- @field #number landHeight Land height in meters.
-- @field #number depth Water depth in meters.
-- @field #number markerID Marker ID.
-- @field #number lineID Line marker ID.
--- PATHLINE class version.
-- @field #string version
PATHLINE.version="0.1.1"
PATHLINE.version="0.2.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -301,18 +302,42 @@ function PATHLINE:GetPoint2DFromIndex(n)
return nil
end
--- Calculate the length of the line.
-- @param #PATHLINE self
-- @param #boolean Project2D Calculate 2D distance between points.
-- @return #number Length in meters.
function PATHLINE:GetLength(Project2D)
local l=0
local np=#self.points
for i=1,np-1 do
local p1=self.points[i] --#PATHLINE.Point
local p2=self.points[i+1] --#PATHLINE.Point
if Project2D then
l=l+UTILS.VecDist2D(p1.vec2, p2.vec2)
else
l=l+UTILS.VecDist3D(p1.vec3, p2.vec3)
end
end
return l
end
--- Mark points on F10 map.
-- @param #PATHLINE self
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
-- @return <DCS#Vec3> List of DCS#Vec3 points.
-- @return #PATHLINE self
function PATHLINE:MarkPoints(Switch)
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
if Switch==false then
if point.markerID then
UTILS.RemoveMark(point.markerID, Delay)
UTILS.RemoveMark(point.markerID)
end
else
@@ -329,6 +354,56 @@ function PATHLINE:MarkPoints(Switch)
end
end
return self
end
--- Draw line on F10 map.
-- @param #PATHLINE self
-- @param #number Recipient Recipent of the line: -1=All.
-- @param #table Color Color as RGB table plus alpha value. Default {1, 0, 0, 1.0}.
-- @param #number LineType Line type: 1=Solid (default).
-- @return #PATHLINE self
function PATHLINE:DrawLine(Recipient, Color, LineType)
-- Input
Recipient= Recipient or -1
Color= Color or {1,0,0, 1.0}
LineType=LineType or 1
local ReadOnly=false
local np=#self.points
for i=1,np-1 do
local p1=self.points[i] --#PATHLINE.Point
local p2=self.points[i+1] --#PATHLINE.Point
p1.lineID = UTILS.GetMarkID()
trigger.action.lineToAll(Recipient, p1.lineID, p1.vec3, p2.vec3, Color, LineType, ReadOnly, "")
end
return self
end
--- Remove line on F10 map.
-- @param #PATHLINE self
-- @param #number Delay Delay in seconds before line is removed.
-- @return #PATHLINE self
function PATHLINE:UnDrawLine(Delay)
local np=#self.points
for _,_point in pairs(self.points) do
local p=_point --#PATHLINE.Point
if p.lineID then
UTILS.RemoveMark(p.lineID, Delay)
end
end
return self
end
+48 -171
View File
@@ -3,8 +3,6 @@
-- ## Features:
--
-- * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it.
-- * Provides a POINT\_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a Lat/Lon and Altitude perspective.
-- * Provides a POINT\_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a X, Z and Y vector perspective.
--
-- ===
--
@@ -2060,6 +2058,40 @@ do -- COORDINATE
return Path, Way, GotPath
end
--- Returns a table of coordinates to a destination using only roads or railroads.
-- The first point is the closest point on road of the given coordinate.
-- By default, the last point is the closest point on road of the ToCoord. Hence, the coordinate itself and the final ToCoord are not necessarily included in the path.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoord Coordinate of destination.
-- @param #boolean IncludeEndpoints (Optional) Include the coordinate itself and the ToCoordinate in the path.
-- @param #boolean Railroad (Optional) If true, path on railroad is returned. Default false.
-- @return Core.Pathline#PATHLINE Pathline containing the points on road. If no path on road can be found, nil is returned or just the endpoints.
function COORDINATE:GetPathlineOnRoad(ToCoord, IncludeEndpoints, Railroad)
-- Set road type.
local RoadType="roads"
if Railroad==true then
RoadType="railroads"
end
-- DCS API function returning a table of vec2.
local path = land.findPathOnRoads(RoadType, self.x, self.z, ToCoord.x, ToCoord.z)
if IncludeEndpoints then
path=path or {}
table.insert(path, 1, self:GetVec2())
table.insert(path, ToCoord:GetVec2())
end
local pathline=nil
if path then
pathline=PATHLINE:NewFromVec2Array(RoadType, path)
end
return pathline
end
--- Gets the surface type at the coordinate.
-- @param #COORDINATE self
@@ -3249,7 +3281,6 @@ do -- COORDINATE
return delta/60
end
--- Return a BR string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
@@ -3263,6 +3294,20 @@ do -- COORDINATE
local Distance = self:Get2DDistance( FromCoordinate )
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
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.
-- @param #COORDINATE self
@@ -3841,171 +3886,3 @@ do -- COORDINATE
end
end
do
--- The POINT_VEC3 class
-- @type POINT_VEC3
-- @field #number x The x coordinate in 3D space.
-- @field #number y The y coordinate in 3D space.
-- @field #number z The z COORDINATE in 3D space.
-- @field Utilities.Utils#SMOKECOLOR SmokeColor
-- @field Utilities.Utils#FLARECOLOR FlareColor
-- @field #POINT_VEC3.RoutePointAltType RoutePointAltType
-- @field #POINT_VEC3.RoutePointType RoutePointType
-- @field #POINT_VEC3.RoutePointAction RoutePointAction
-- @extends #COORDINATE
--- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space.
--
-- **DEPRECATED - PLEASE USE COORDINATE!**
--
-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts.
-- In order to keep the credibility of the the author,
-- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors,
-- who you can find on the Eagle Dynamics Forums.
--
--
-- ## POINT_VEC3 constructor
--
-- A new POINT_VEC3 object can be created with:
--
-- * @{#POINT_VEC3.New}(): a 3D point.
-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCS#Vec3}.
--
--
-- ## Manupulate the X, Y, Z coordinates of the POINT_VEC3
--
-- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate.
-- Methods exist to manupulate these coordinates.
--
-- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively.
-- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value.
-- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example:
--
-- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3()
--
--
-- ## 3D calculation methods
--
-- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:
--
--
-- ## Point Randomization
--
-- Various methods exist to calculate random locations around a given 3D point.
--
-- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band.
--
--
-- @field #POINT_VEC3
POINT_VEC3 = {
ClassName = "POINT_VEC3",
Metric = true,
RoutePointAltType = {
BARO = "BARO",
},
RoutePointType = {
TakeOffParking = "TakeOffParking",
TurningPoint = "Turning Point",
},
RoutePointAction = {
FromParkingArea = "From Parking Area",
TurningPoint = "Turning Point",
},
}
--- RoutePoint AltTypes
-- @type POINT_VEC3.RoutePointAltType
-- @field BARO "BARO"
--- RoutePoint Types
-- @type POINT_VEC3.RoutePointType
-- @field TakeOffParking "TakeOffParking"
-- @field TurningPoint "Turning Point"
--- RoutePoint Actions
-- @type POINT_VEC3.RoutePointAction
-- @field FromParkingArea "From Parking Area"
-- @field TurningPoint "Turning Point"
-- Constructor.
--- Create a new POINT_VEC3 object.
-- @param #POINT_VEC3 self
-- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North.
-- @param DCS#Distance y The y coordinate of the Vec3 point, pointing Upwards.
-- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right.
-- @return Core.Point#POINT_VEC3
function POINT_VEC3:New( x, y, z )
local self = BASE:Inherit( self, COORDINATE:New( x, y, z ) ) -- Core.Point#POINT_VEC3
self:F2( self )
return self
end
end
do
--- @type POINT_VEC2
-- @field DCS#Distance x The x coordinate in meters.
-- @field DCS#Distance y the y coordinate in meters.
-- @extends Core.Point#COORDINATE
--- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified.
--
-- **DEPRECATED - PLEASE USE COORDINATE!**
--
-- ## POINT_VEC2 constructor
--
-- A new POINT_VEC2 instance can be created with:
--
-- * @{Core.Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter.
-- * @{Core.Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCS#Vec2}.
--
-- ## Manupulate the X, Altitude, Y coordinates of the 2D point
--
-- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate.
-- Methods exist to manupulate these coordinates.
--
-- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively.
-- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value.
-- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively.
-- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example:
--
-- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2()
--
-- @field #POINT_VEC2
POINT_VEC2 = {
ClassName = "POINT_VEC2",
}
--- POINT_VEC2 constructor.
-- @param #POINT_VEC2 self
-- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North.
-- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right.
-- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height.
-- @return Core.Point#POINT_VEC2
function POINT_VEC2:New( x, y, LandHeightAdd )
local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } )
LandHeightAdd = LandHeightAdd or 0
LandHeight = LandHeight + LandHeightAdd
local self = BASE:Inherit( self, COORDINATE:New( x, LandHeight, y ) ) -- Core.Point#POINT_VEC2
self:F2( self )
return self
end
end
File diff suppressed because it is too large Load Diff
+8
View File
@@ -1667,6 +1667,9 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
-- If RandomizeUnits, then Randomize the formation at the start point.
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
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
if (SpawnZone) then
@@ -3518,6 +3521,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
max = 18
ctable = CALLSIGN.F15E
end
if SpawnTemplate.units[1].type == "A6E" then
min = 4
max = 18
ctable = CALLSIGN.Intruder
end
local callsignnr = math.random(min,max)
local callsignname = "Enfield"
for name, value in pairs(ctable) do
@@ -334,6 +334,31 @@ function SPAWNSTATIC:InitCargo(IsCargo)
return self
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.
-- @param #SPAWNSTATIC self
-- @param #boolean IsDead If true, this static is dead.
@@ -533,6 +558,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.mass=self.InitStaticCargoMass
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
Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true
File diff suppressed because it is too large Load Diff
+31 -15
View File
@@ -783,7 +783,7 @@ end
-- @param #ZONE_BASE self
-- @param #string From
-- @param #string Event
-- @param #string to
-- @param #string To
-- @return #ZONE_BASE self
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
if self:GetState() ~= "TriggerStopped" then
@@ -1158,17 +1158,17 @@ end
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {}
self.ScanData.Coalitions = {}
self.ScanData.Scenery = {}
self.ScanData.SceneryTable = {}
self.ScanData.Units = {}
local ZoneCoord = self:GetCoordinate()
local ZoneCoord = self:GetCoordinate():SetAlt()
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 = {
id = world.VolumeType.SPHERE,
@@ -1180,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
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 and self:IsVec3InZone(ZoneObject:getPoint()) then
-- Get object category.
@@ -1265,38 +1266,48 @@ end
--- Get a set of scanned units.
-- @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.
function ZONE_RADIUS:GetScannedSetUnit()
local SetUnit = SET_UNIT:New()
function ZONE_RADIUS:GetScannedSetUnit(Coalition)
self.SetUnit = self.SetUnit or SET_UNIT:New()
self.SetUnit:Clear(false)
self.SetUnit.Set={}
if self.ScanData then
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
local UnitObject = UnitObject -- DCS#Unit
if UnitObject:isExist() then
local FoundUnit = UNIT:FindByName( UnitObject:getName() )
if FoundUnit then
SetUnit:AddUnit( FoundUnit )
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
self.SetUnit:AddUnit( FoundUnit )
else
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
if FoundStatic then
SetUnit:AddUnit( FoundStatic )
self.SetUnit:AddUnit( FoundStatic )
end
end
end
end
end
return SetUnit
return self.SetUnit
end
--- Get a set of scanned groups.
-- @param #ZONE_RADIUS self
-- @param #number Coalition (optional) Filter for this coalition only.
-- @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:Clear(false)
self.ScanSetGroup.Set={}
if self.ScanData then
@@ -1305,7 +1316,12 @@ function ZONE_RADIUS:GetScannedSetGroup()
if UnitObject:isExist() then
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()
self.ScanSetGroup:AddGroup(group)
end
@@ -3380,7 +3396,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
--self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
end
end
@@ -27,7 +27,7 @@
--
-- ### Author: **funkyfranky**
--
-- ### Contributions: FlightControl
-- ### Contributions: FlightControl, Applevangelist
--
-- ====
-- @module Functional.Artillery
@@ -780,7 +780,7 @@ ARTY.db={
--- Arty script 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
-- 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.
_cr:Smoke(self.smokeColor)
+12 -13
View File
@@ -693,7 +693,7 @@ end
function AUTOLASE:GetLosFromUnit(Unit)
local lasedistance = self.LaseDistance
local unitheight = Unit:GetHeight()
local coord = Unit:GetCoordinate()
local coord = Unit:GetCoord()
local landheight = coord:GetLandHeight()
local asl = unitheight - landheight
if asl > 100 then
@@ -845,7 +845,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
locationstring = entry.coordinate:ToStringLLDDM(settings)
elseif settings:IsA2G_BR() then
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate()
local startcoordinate = Unit:GetCoord() or Group:GetCoord()
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
end
end
@@ -963,8 +963,8 @@ function AUTOLASE:CanLase(Recce,Unit)
end
end
-- calculate LOS
local reccecoord = Recce:GetCoordinate()
local unitcoord = Unit:GetCoordinate()
local reccecoord = Recce:GetCoord()
local unitcoord = Unit:GetCoord()
local islos = reccecoord:IsLOS(unitcoord,2.5)
-- calculate distance
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())
for _,_static in pairs(Statics) do -- DCS static object here
local static = STATIC:Find(_static)
if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then
local IsLOS = position:IsLOS(static:GetCoordinate())
if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
local IsLOS = position:IsLOS(static:GetCoord())
if IsLOS then
unit:KnowUnit(static,true,true)
end
@@ -1081,7 +1081,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local threat = contact.threatlevel or 0
local reccegrp = UNIT:FindByName(reccename)
if reccegrp then
local reccecoord = reccegrp:GetCoordinate()
local reccecoord = reccegrp:GetCoord()
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)
report:Add(text)
@@ -1120,7 +1120,6 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local threat = unit:GetThreatLevel()
local coord = unit:GetCoordinate()
if threat >= self.minthreatlevel then
local unitname = unit:GetName()
-- prefer radar units
@@ -1176,16 +1175,16 @@ function AUTOLASE:onafterMonitor(From, Event, To)
local code = self:GetLaserCode(reccename)
local spot = SPOT:New(recce)
spot:LaseOn(unit,code,self.LaseDuration)
local locationstring = unit:GetCoordinate():ToStringLLDDM()
local locationstring = unit:GetCoord():ToStringLLDDM()
if _SETTINGS:IsA2G_MGRS() then
local precision = _SETTINGS:GetMGRS_Accuracy()
local settings = {}
settings.MGRS_Accuracy = precision
locationstring = unit:GetCoordinate():ToStringMGRS(settings)
locationstring = unit:GetCoord():ToStringMGRS(settings)
elseif _SETTINGS:IsA2G_LL_DMS() then
locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS)
locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
elseif _SETTINGS:IsA2G_BR() then
locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS)
locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
end
local laserspot = { -- #AUTOLASE.LaserSpot
@@ -1198,7 +1197,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
unitname = unitname,
reccename = reccename,
unittype = unit:GetTypeName(),
coordinate = unit:GetCoordinate(),
coordinate = unit:GetCoord(),
}
if self.smoketargets then
local coord = unit:GetCoordinate()
+519 -85
View File
@@ -22,7 +22,7 @@
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Last Update: August 2025
-- Last Update: January 2026
-------------------------------------------------------------------------
--- **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 #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects.
-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range.
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White.
-- @field #number checkcounter Counter for SAM Table refreshes.
-- @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
@@ -113,7 +116,7 @@
-- * TOR M2
-- * C-RAM
-- * 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 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
@@ -280,7 +283,7 @@
MANTIS = {
ClassName = "MANTIS",
name = "mymantis",
version = "0.9.34",
version = "0.9.44",
SAM_Templates_Prefix = "",
SAM_Group = nil,
EWR_Templates_Prefix = "",
@@ -331,6 +334,11 @@ MANTIS = {
checkcounter = 1,
DLinkCacheTime = 120,
logsamstatus = false,
DetectAccoustic = false,
DetectAccousticRadius = 2000,
DetectAccousticCategories = {Unit.Category.HELICOPTER},
ARMWeaponSeen = {},
InboundARMs = {},
}
--- 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 Radar Radar typename on unit level (used as key)
-- @field #string Point Point defense capable
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
MANTIS.SamData = {
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B
["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" },
["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-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-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" },
["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" },
["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-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" },
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="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" },
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
["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" },
["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
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" },
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
["SA-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", ARMCapacity=4 },
["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", 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" },
["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" },
["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" },
-- CH Added to DCS core 2.9.19.x
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" },
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" },
["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", ARMCapacity=4 },
["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
@@ -419,13 +432,13 @@ MANTIS.SamDataHDS = {
-- group name MUST contain HDS to ID launcher type correctly!
["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-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " },
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"},
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"},
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" },
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" },
["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", ARMCapacity=3}, -- V55RUD
["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", ARMCapacity=12},
["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", ARMCapacity=14 },
["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" },
["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
@@ -469,21 +482,21 @@ MANTIS.SamDataCH = {
-- https://www.currenthill.com/
-- group name MUST contain CHM to ID launcher type correctly!
["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" },
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
["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", ARMCapacity=4 },
["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" },
["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" },
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" },
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", 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", ARMCapacity=4 },
["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-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
["PGZ-09 CHM"] = { Range=4, Blindspot=0.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-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
["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", ARMCapacity=20 },
["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" },
["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" },
["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" },
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" },
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" },
["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", ARMCapacity=20 },
["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" },
["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" },
["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" },
@@ -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 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 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 #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)
@@ -548,7 +561,7 @@ do
-- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- 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.
@@ -561,12 +574,14 @@ do
-- DONE: Treat Awacs separately, since they might be >80km off site
-- DONE: Allow tables of prefixes for the setup
-- 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.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
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_Long = {}
self.SAM_Table_Medium = {}
@@ -613,6 +628,8 @@ do
self.autoshorad = true
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
self.FilterZones = Zones
self.LastThreatEval = {}
self.InboundARMs = {}
self.SkateZones = nil
self.SkateNumber = 3
@@ -716,6 +733,8 @@ do
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
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.
------------------------
@@ -826,6 +845,24 @@ do
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object
-- @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
end
@@ -833,6 +870,138 @@ do
-- 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
-- @param #MANTIS self
-- @return #table table
@@ -903,13 +1072,111 @@ do
return self
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)
-- @param #MANTIS self
-- @param #number radius Radius of the EWR detection zone
function MANTIS:SetEWRRange(radius)
self:T(self.lid .. "SetEWRRange")
--local radius = radius or 80000
-- self.acceptrange = radius
return self
end
@@ -1325,11 +1592,12 @@ do
return inzone
end
--- [Internal] Function to prefilter height based
--- [Internal] Function to prefilter height based and check for Helo activity.
-- @param #MANTIS self
-- @param #number height
-- @param Core.Point#COORDINATE SamCoordinate
-- @return #table set
function MANTIS:_PreFilterHeight(height)
function MANTIS:_PreFilterHeight(height,SamCoordinate)
self:T(self.lid.."_PreFilterHeight")
local set = {}
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
@@ -1338,8 +1606,22 @@ do
local contact = _contact -- Ops.Intel#INTEL.Contact
local grp = contact.group -- Wrapper.Group#GROUP
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)
end
end
@@ -1363,7 +1645,7 @@ do
local set = dectset
if dlink then
-- DEBUG
set = self:_PreFilterHeight(height)
set = self:_PreFilterHeight(height,samcoordinate)
end
--self.friendlyset -- Core.Set#SET_GROUP
if self.checkforfriendlies == true and self.friendlyset == nil then
@@ -1444,14 +1726,30 @@ do
self.intelset = {}
local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
--IntelOne:SetClusterAnalysis(true,true)
--IntelOne:SetClusterRadius(5000)
local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne")
IntelOne.DetectAccoustic = self.DetectAccoustic
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()
local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
--IntelTwo:SetClusterAnalysis(true,true)
--IntelTwo:SetClusterRadius(5000)
local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo")
IntelTwo.DetectAccoustic = self.DetectAccoustic
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()
local CacheTime = self.DLinkCacheTime or 120
@@ -1511,6 +1809,7 @@ do
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
local units = group:GetUnits()
local SAMData = self.SamData
local ARMCapacity
if mod then
SAMData = self.SamDataHDS
elseif sma then
@@ -1518,22 +1817,23 @@ do
elseif chm then
SAMData = self.SamDataCH
end
--self:I("Looking to auto-match for "..grpname)
self:T("Looking to auto-match for "..grpname)
for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT
local type = string.lower(unit:GetTypeName())
--self:I(string.format("Matching typename: %s",type))
local typename = string.lower(unit:GetTypeName())
self:T(string.format("Matching typename: %s",typename))
for idx,entry in pairs(SAMData) do
local _entry = entry -- #MANTIS.SamData
local _radar = string.lower(_entry.Radar)
--self:I(string.format("Trying typename: %s",_radar))
if string.find(type,_radar,1,true) then
self:T(string.format("Trying typename: %s",_radar))
if string.find(typename,_radar,1,true) then
type = _entry.Type
radiusscale = self.radiusscale[type]
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
height = _entry.Height * 1000 -- max firing height
blind = _entry.Blindspot * 100 -- blind spot range
--self:I(string.format("Match: %s - %s",_radar,type))
blind = _entry.Blindspot * 100 -- blind spot range
ARMCapacity = _entry.ARMCapacity
self:T(string.format("Match: %s - %s",_radar,type))
found = true
break
end
@@ -1554,7 +1854,7 @@ do
if not found then
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
return range, height, type, blind
return range, height, type, blind, ARMCapacity
end
--- [Internal] Function to get SAM firing data
@@ -1575,6 +1875,7 @@ do
local HDSmod = false
local SMAMod = false
local CHMod = false
local ARMCapacity = 0
if string.find(grpname,"HDS",1,true) then
HDSmod = true
elseif string.find(grpname,"SMA",1,true) then
@@ -1592,6 +1893,7 @@ do
range = _entry.Range * 1000 * radiusscale -- max firing range
height = _entry.Height * 1000 -- max firing height
blind = _entry.Blindspot
ARMCapacity = _entry.ARMCapacity or 0
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
found = true
break
@@ -1611,14 +1913,14 @@ do
end
--- Tertiary filter if not found
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
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
if found and string.find(grpname,"SHORAD",1,true) then
type = MANTIS.SamType.POINT -- force short on match
end
return range, height, type, blind
return range, height, type, blind, ARMCapacity
end
--- [Internal] Function to set the SAM start state
@@ -1653,8 +1955,9 @@ do
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
local grpname = group:GetName()
local grpcoord = group:GetCoordinate()
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname)
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type})
local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname)
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 )
if type == MANTIS.SamType.LONG then
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
@@ -1718,12 +2021,15 @@ do
if group:IsGround() and group:IsAlive() then
local grpname = group:GetName()
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
--local radaralive = group:IsSAM()
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
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( SEAD_Grps, grpname )
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here
if type ~= MANTIS.SamType.POINT then
table.insert( SEAD_Grps, grpname )
end
if type == MANTIS.SamType.LONG and radaralive then
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
self:T({grpname,grprange, grpheight})
@@ -1780,10 +2086,122 @@ do
self.ShoradLink = false
return self
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
-----------------------------------------------------------------------
--- [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
-- @param #MANTIS self
@@ -1807,7 +2225,7 @@ do
local radius = _data[3]
local height = _data[4]
local blind = _data[5] * 1.25 + 1
local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false
local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false
if not shortsam then
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
end
@@ -1818,6 +2236,7 @@ do
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
activeshorad = true
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 samgroup:IsAlive() then
-- switch on SAM
@@ -1833,19 +2252,13 @@ do
switch = true
end
if self.SamStateTracker[name] ~= "RED" and switch then
self:__RedState(1,samgroup)
self.SamStateTracker[name] = "RED"
self:__RedState(1,samgroup)
end
-- TODO doesn't work
if shortsam == true and self.SmokeDecoy == true then
-- DONE Restrict on Distance
if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then
self:T("Smoking")
local units = samgroup:GetUnits() or {}
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
for _,unit in pairs(units) do
if unit and unit:IsAlive() then
unit:GetCoordinate():Smoke(smoke)
end
end
self:_SmokeUnits(samgroup)
end
-- link in to SHORAD if available
-- DONE: Test integration fully
@@ -1872,8 +2285,8 @@ do
samgroup:OptionAlarmStateGreen()
end
if self.SamStateTracker[name] ~= "GREEN" then
self:__GreenState(1,samgroup)
self.SamStateTracker[name] = "GREEN"
self:__GreenState(1,samgroup)
end
if self.debug or self.verbose then
local text = string.format("SAM %s in alarm state GREEN!", name)
@@ -1883,12 +2296,13 @@ do
end --end alive
end --end check
end --for loop
--[[
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
instatusred=instatusred+1
end
end
if self.Shorad then
@@ -1897,6 +2311,8 @@ do
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
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 instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
else
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
end
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)
statusreport:Add("+-----------------------------+")
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
@@ -2030,21 +2462,22 @@ do
else
self.Detection = self:StartIntelDetection()
end
--[[
if self.advAwacs and not self.automode then
self.AWACS_Detection = self:StartAwacsDetection()
end
--]]
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.ShoradLink = true
self.Shorad.Groupset=self.ShoradGroupSet
self.Shorad.debug = self.debug
self.Shorad:AddCallBack(self)
end
if self.shootandscoot and self.SkateZones and self.Shorad then
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
end
self:HandleEvent(EVENTS.Hit,self._EventHandler)
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
self:__Status(-math.random(1,10))
return self
end
@@ -2242,6 +2675,7 @@ do
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
self:T({From, Event, To, Name})
self.SuppressedGroups[Name] = false
self.InboundARMs[Name] = 0
return self
end
@@ -1,736 +0,0 @@
--- **Functional** - Train missile defence and deflection.
--
-- ===
--
-- ## Features:
--
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
-- * Provide alerts when a missile would have killed your aircraft.
-- * Provide alerts when the missile self destructs.
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
--
-- ===
--
-- ## Missions:
--
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Functional/MissileTrainer)
--
-- ===
--
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
--
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
--
-- * **Messages**: Menu to configure all messages.
-- * **Messages On**: Show all messages.
-- * **Messages Off**: Disable all messages.
-- * **Tracking**: Menu to configure missile tracking messages.
-- * **To All**: Shows missile tracking messages to all players.
-- * **To Target**: Shows missile tracking messages only to the player where the missile is targeted at.
-- * **Tracking On**: Show missile tracking messages.
-- * **Tracking Off**: Disable missile tracking messages.
-- * **Frequency Increase**: Increases the missile tracking message frequency with one second.
-- * **Frequency Decrease**: Decreases the missile tracking message frequency with one second.
-- * **Alerts**: Menu to configure alert messages.
-- * **To All**: Shows alert messages to all players.
-- * **To Target**: Shows alert messages only to the player where the missile is (was) targeted at.
-- * **Hits On**: Show missile hit alert messages.
-- * **Hits Off**: Disable missile hit alert messages.
-- * **Launches On**: Show missile launch messages.
-- * **Launches Off**: Disable missile launch messages.
-- * **Details**: Menu to configure message details.
-- * **Range On**: Shows range information when a missile is fired to a target.
-- * **Range Off**: Disable range information when a missile is fired to a target.
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE.
-- Therefore, this class is considered to be deprecated and superseded by the [Functional.Fox](https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html) class, which provides the same functionality.
--
-- ===
--
-- ### Authors: **FlightControl**
--
-- ### Contributions:
--
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
-- * **132nd Squadron**: Testing and optimizing the logic.
--
-- ===
--
-- @module Functional.MissileTrainer
-- @image Missile_Trainer.JPG
---
-- @type MISSILETRAINER
-- @field Core.Set#SET_CLIENT DBClients
-- @extends Core.Base#BASE
---
--
-- # Constructor:
--
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
--
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
--
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
--
-- # Initialization:
--
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
--
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
-- * @{#MISSILETRAINER.InitTrackingToAll}: Sets by default the missile tracking report for all players or only for those missiles targeted to you.
-- * @{#MISSILETRAINER.InitTrackingOnOff}: Sets by default the display of missile tracking report to be ON or OFF.
-- * @{#MISSILETRAINER.InitTrackingFrequency}: Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
-- * @{#MISSILETRAINER.InitAlertsToAll}: Sets by default the display of alerts to be shown to all players or only to you.
-- * @{#MISSILETRAINER.InitAlertsHitsOnOff}: Sets by default the display of hit alerts ON or OFF.
-- * @{#MISSILETRAINER.InitAlertsLaunchesOnOff}: Sets by default the display of launch alerts ON or OFF.
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE.
-- Therefore, this class is considered to be deprecated and superseded by the [Functional.Fox](https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html) class, which provides the same functionality.
--
-- @field #MISSILETRAINER
MISSILETRAINER = {
ClassName = "MISSILETRAINER",
TrackingMissiles = {},
}
function MISSILETRAINER._Alive( Client, self )
if self.Briefing then
Client:Message( self.Briefing, 15, "Trainer" )
end
if self.MenusOnOff == true then
Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" )
Client.MainMenu = MENU_GROUP:New( Client:GetGroup(), "Missile Trainer", nil ) -- Menu#MENU_GROUP
Client.MenuMessages = MENU_GROUP:New( Client:GetGroup(), "Messages", Client.MainMenu )
Client.MenuOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } )
Client.MenuOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } )
Client.MenuTracking = MENU_GROUP:New( Client:GetGroup(), "Tracking", Client.MainMenu )
Client.MenuTrackingToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } )
Client.MenuTrackingToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } )
Client.MenuTrackOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } )
Client.MenuTrackOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } )
Client.MenuTrackIncrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } )
Client.MenuTrackDecrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } )
Client.MenuAlerts = MENU_GROUP:New( Client:GetGroup(), "Alerts", Client.MainMenu )
Client.MenuAlertsToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } )
Client.MenuAlertsToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } )
Client.MenuHitsOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } )
Client.MenuHitsOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } )
Client.MenuLaunchesOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } )
Client.MenuLaunchesOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } )
Client.MenuDetails = MENU_GROUP:New( Client:GetGroup(), "Details", Client.MainMenu )
Client.MenuDetailsDistanceOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } )
Client.MenuDetailsDistanceOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } )
Client.MenuDetailsBearingOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } )
Client.MenuDetailsBearingOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } )
Client.MenuDistance = MENU_GROUP:New( Client:GetGroup(), "Set distance to plane", Client.MainMenu )
Client.MenuDistance50 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } )
Client.MenuDistance100 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } )
Client.MenuDistance150 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } )
Client.MenuDistance200 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } )
else
if Client.MainMenu then
Client.MainMenu:Remove()
end
end
local ClientID = Client:GetID()
self:T( ClientID )
if not self.TrackingMissiles[ClientID] then
self.TrackingMissiles[ClientID] = {}
end
self.TrackingMissiles[ClientID].Client = Client
if not self.TrackingMissiles[ClientID].MissileData then
self.TrackingMissiles[ClientID].MissileData = {}
end
end
--- Creates the main object which is handling missile tracking.
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
-- @param #MISSILETRAINER self
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @return #MISSILETRAINER
function MISSILETRAINER:New( Distance, Briefing )
local self = BASE:Inherit( self, BASE:New() )
self:F( Distance )
if Briefing then
self.Briefing = Briefing
end
self.Schedulers = {}
self.SchedulerID = 0
self.MessageInterval = 2
self.MessageLastTime = timer.getTime()
self.Distance = Distance / 1000
self:HandleEvent( EVENTS.Shot )
self.DBClients = SET_CLIENT:New():FilterStart()
-- for ClientID, Client in pairs( self.DBClients.Database ) do
-- self:F( "ForEach:" .. Client.UnitName )
-- Client:Alive( self._Alive, self )
-- end
--
self.DBClients:ForEachClient(
function( Client )
self:F( "ForEach:" .. Client.UnitName )
Client:Alive( self._Alive, self )
end
)
-- self.DB:ForEachClient(
-- -- @param Wrapper.Client#CLIENT Client
-- function( Client )
--
-- ... actions ...
--
-- end
-- )
self.MessagesOnOff = true
self.TrackingToAll = false
self.TrackingOnOff = true
self.TrackingFrequency = 3
self.AlertsToAll = true
self.AlertsHitsOnOff = true
self.AlertsLaunchesOnOff = true
self.DetailsRangeOnOff = true
self.DetailsBearingOnOff = true
self.MenusOnOff = true
self.TrackingMissiles = {}
self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 )
return self
end
-- Initialization methods.
--- Sets by default the display of any message to be ON or OFF.
-- @param #MISSILETRAINER self
-- @param #boolean MessagesOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitMessagesOnOff( MessagesOnOff )
self:F( MessagesOnOff )
self.MessagesOnOff = MessagesOnOff
if self.MessagesOnOff == true then
MESSAGE:New( "Messages ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Messages OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the missile tracking report for all players or only for those missiles targeted to you.
-- @param #MISSILETRAINER self
-- @param #boolean TrackingToAll true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingToAll( TrackingToAll )
self:F( TrackingToAll )
self.TrackingToAll = TrackingToAll
if self.TrackingToAll == true then
MESSAGE:New( "Missile tracking to all players ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Missile tracking to all players OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of missile tracking report to be ON or OFF.
-- @param #MISSILETRAINER self
-- @param #boolean TrackingOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingOnOff( TrackingOnOff )
self:F( TrackingOnOff )
self.TrackingOnOff = TrackingOnOff
if self.TrackingOnOff == true then
MESSAGE:New( "Missile tracking ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Missile tracking OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
-- @param #MISSILETRAINER self
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
self:F( TrackingFrequency )
self.TrackingFrequency = self.TrackingFrequency + TrackingFrequency
if self.TrackingFrequency < 0.5 then
self.TrackingFrequency = 0.5
end
if self.TrackingFrequency then
MESSAGE:New( "Missile tracking frequency is " .. self.TrackingFrequency .. " seconds.", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of alerts to be shown to all players or only to you.
-- @param #MISSILETRAINER self
-- @param #boolean AlertsToAll true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitAlertsToAll( AlertsToAll )
self:F( AlertsToAll )
self.AlertsToAll = AlertsToAll
if self.AlertsToAll == true then
MESSAGE:New( "Alerts to all players ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Alerts to all players OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of hit alerts ON or OFF.
-- @param #MISSILETRAINER self
-- @param #boolean AlertsHitsOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitAlertsHitsOnOff( AlertsHitsOnOff )
self:F( AlertsHitsOnOff )
self.AlertsHitsOnOff = AlertsHitsOnOff
if self.AlertsHitsOnOff == true then
MESSAGE:New( "Alerts Hits ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Alerts Hits OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of launch alerts ON or OFF.
-- @param #MISSILETRAINER self
-- @param #boolean AlertsLaunchesOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitAlertsLaunchesOnOff( AlertsLaunchesOnOff )
self:F( AlertsLaunchesOnOff )
self.AlertsLaunchesOnOff = AlertsLaunchesOnOff
if self.AlertsLaunchesOnOff == true then
MESSAGE:New( "Alerts Launches ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Alerts Launches OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of range information of missiles ON of OFF.
-- @param #MISSILETRAINER self
-- @param #boolean DetailsRangeOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitRangeOnOff( DetailsRangeOnOff )
self:F( DetailsRangeOnOff )
self.DetailsRangeOnOff = DetailsRangeOnOff
if self.DetailsRangeOnOff == true then
MESSAGE:New( "Range display ON", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Range display OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Sets by default the display of bearing information of missiles ON of OFF.
-- @param #MISSILETRAINER self
-- @param #boolean DetailsBearingOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitBearingOnOff( DetailsBearingOnOff )
self:F( DetailsBearingOnOff )
self.DetailsBearingOnOff = DetailsBearingOnOff
if self.DetailsBearingOnOff == true then
MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll()
end
return self
end
--- Enables / Disables the menus.
-- @param #MISSILETRAINER self
-- @param #boolean MenusOnOff true or false
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitMenusOnOff( MenusOnOff )
self:F( MenusOnOff )
self.MenusOnOff = MenusOnOff
if self.MenusOnOff == true then
MESSAGE:New( "Menus are ENABLED (only when a player rejoins a slot)", 15, "Menu" ):ToAll()
else
MESSAGE:New( "Menus are DISABLED", 15, "Menu" ):ToAll()
end
return self
end
-- Menu functions
function MISSILETRAINER._MenuMessages( MenuParameters )
local self = MenuParameters.MenuSelf
if MenuParameters.MessagesOnOff ~= nil then
self:InitMessagesOnOff( MenuParameters.MessagesOnOff )
end
if MenuParameters.TrackingToAll ~= nil then
self:InitTrackingToAll( MenuParameters.TrackingToAll )
end
if MenuParameters.TrackingOnOff ~= nil then
self:InitTrackingOnOff( MenuParameters.TrackingOnOff )
end
if MenuParameters.TrackingFrequency ~= nil then
self:InitTrackingFrequency( MenuParameters.TrackingFrequency )
end
if MenuParameters.AlertsToAll ~= nil then
self:InitAlertsToAll( MenuParameters.AlertsToAll )
end
if MenuParameters.AlertsHitsOnOff ~= nil then
self:InitAlertsHitsOnOff( MenuParameters.AlertsHitsOnOff )
end
if MenuParameters.AlertsLaunchesOnOff ~= nil then
self:InitAlertsLaunchesOnOff( MenuParameters.AlertsLaunchesOnOff )
end
if MenuParameters.DetailsRangeOnOff ~= nil then
self:InitRangeOnOff( MenuParameters.DetailsRangeOnOff )
end
if MenuParameters.DetailsBearingOnOff ~= nil then
self:InitBearingOnOff( MenuParameters.DetailsBearingOnOff )
end
if MenuParameters.Distance ~= nil then
self.Distance = MenuParameters.Distance
MESSAGE:New( "Hit detection distance set to " .. ( self.Distance * 1000 ) .. " meters", 15, "Menu" ):ToAll()
end
end
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
-- @param #MISSILETRAINER self
-- @param Core.Event#EVENTDATA EventData
function MISSILETRAINER:OnEventShot( EVentData )
self:F( { EVentData } )
local TrainerSourceDCSUnit = EVentData.IniDCSUnit
local TrainerSourceDCSUnitName = EVentData.IniDCSUnitName
local TrainerWeapon = EVentData.Weapon -- Identify the weapon fired
local TrainerWeaponName = EVentData.WeaponName -- return weapon type
self:T( "Missile Launched = " .. TrainerWeaponName )
local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target
if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName )
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
if Client then
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
local Message = MESSAGE:New(
string.format( "%s launched a %s",
TrainerSourceUnit:GetTypeName(),
TrainerWeaponName
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
if self.AlertsToAll then
Message:ToAll()
else
Message:ToClient( Client )
end
end
local ClientID = Client:GetID()
self:T( ClientID )
local MissileData = {}
MissileData.TrainerSourceUnit = TrainerSourceUnit
MissileData.TrainerWeapon = TrainerWeapon
MissileData.TrainerTargetUnit = TrainerTargetUnit
MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName()
MissileData.TrainerWeaponLaunched = true
table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData )
--self:T( self.TrackingMissiles )
end
else
-- TODO: some weapons don't know the target unit... Need to develop a workaround for this.
if ( TrainerWeapon:getTypeName() == "9M311" ) then
SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 1 )
else
end
end
end
function MISSILETRAINER:_AddRange( Client, TrainerWeapon )
local RangeText = ""
if self.DetailsRangeOnOff then
local PositionMissile = TrainerWeapon:getPoint()
local TargetVec3 = Client:GetVec3()
local Range = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
RangeText = string.format( ", at %4.2fkm", Range )
end
return RangeText
end
function MISSILETRAINER:_AddBearing( Client, TrainerWeapon )
local BearingText = ""
if self.DetailsBearingOnOff then
local PositionMissile = TrainerWeapon:getPoint()
local TargetVec3 = Client:GetVec3()
self:T2( { TargetVec3, PositionMissile })
local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z }
local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x )
if DirectionRadians < 0 then
DirectionRadians = DirectionRadians + 2 * math.pi
end
local DirectionDegrees = DirectionRadians * 180 / math.pi
BearingText = string.format( ", %d degrees", DirectionDegrees )
end
return BearingText
end
function MISSILETRAINER:_TrackMissiles()
self:F2()
local ShowMessages = false
if self.MessagesOnOff and self.MessageLastTime + self.TrackingFrequency <= timer.getTime() then
self.MessageLastTime = timer.getTime()
ShowMessages = true
end
-- ALERTS PART
-- Loop for all Player Clients to check the alerts and deletion of missiles.
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
if Client and Client:IsAlive() then
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
if Distance <= self.Distance then
-- Hit alert
TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" )
local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData)
end
end
else
if not ( TrainerWeapon and TrainerWeapon:isExist() ) then
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
-- Weapon does not exist anymore. Delete from Table
local Message = MESSAGE:New(
string.format( "%s launched by %s self destructed!",
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
), 5, "Tracking" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T( ClientData.MissileData )
end
end
end
else
self.TrackingMissiles[ClientDataID] = nil
end
end
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
-- TRACKING PART
-- For the current client, the missile range and bearing details are displayed To the Player Client.
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- Main Player Client loop
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
--self:T2( { Client:GetName() } )
ClientData.MessageToClient = ""
ClientData.MessageToAll = ""
-- Other Players Client loop
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
--self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
if ShowMessages == true then
local TrackingTo
TrackingTo = string.format( " -> %s",
TrainerWeaponTypeName
)
if ClientDataID == TrackingDataID then
if ClientData.MessageToClient == "" then
ClientData.MessageToClient = "Missiles to You:\n"
end
ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. "\n"
else
if self.TrackingToAll == true then
if ClientData.MessageToAll == "" then
ClientData.MessageToAll = "Missiles to other Players:\n"
end
ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n"
end
end
end
end
end
end
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
end
end
end
return true
end
@@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
--- PSEUDOATC 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
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)
if self.radios then
missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name)
end
-- Debug message.
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
--- 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 #number GID Group id of player unit.
-- @param #number UID Unit id of player.
@@ -979,3 +1005,22 @@ function PSEUDOATC:_myname(unitname)
return string.format("%s (%s)", csign, pname)
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
+48 -21
View File
@@ -1805,6 +1805,17 @@ function RAT:SetMaxCruiseSpeed(speed)
return self
end
--- Set the minimum cruise speed of the aircraft.
-- @param #RAT self
-- @param #number speed Speed in km/h.
-- @return #RAT RAT self object.
function RAT:SetMinCruiseSpeed(speed)
self:F2(speed)
-- Convert to m/s.
self.Vcruisemin=speed/3.6
return self
end
--- Set the climb rate. This automatically sets the climb angle.
-- @param #RAT self
-- @param #number rate Climb rate in ft/min. Default is 1500 ft/min. Minimum is 100 ft/min. Maximum is 15,000 ft/min.
@@ -2135,10 +2146,18 @@ function RAT:_InitAircraft(DCSgroup)
self.aircraft.length=16
self.aircraft.height=5
self.aircraft.width=9
elseif DCStype == "Saab340" then -- <- These lines added
self.aircraft.length=19.73 -- <- These lines added
self.aircraft.height=6.97 -- <- These lines added
self.aircraft.width=21.44 -- <- These lines added
elseif DCStype == "Saab340" then
self.aircraft.length=19.73
self.aircraft.height=6.97
self.aircraft.width=21.44
elseif DCStype == "vwv_l-1049" then
self.aircraft.length=35.41
self.aircraft.height=7.54
self.aircraft.width=38.47
elseif DCStype == "uh2b" then
self.aircraft.length=11.48
self.aircraft.height=4.11
self.aircraft.width=13.41
end
self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width)
@@ -2813,9 +2832,15 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
-- Max cruise speed 90% of Vmax or 900 km/h whichever is lower.
VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
end
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
local VxCruiseMin = math.min(VxCruiseMax*0.70, 166)
-- Min cruise speed.
local VxCruiseMin
if self.Vcruisemin then
VxCruiseMin = self.Vcruisemin
else
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
VxCruiseMin = math.min(VxCruiseMax*0.70, 166)
end
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
@@ -5584,7 +5609,7 @@ end
-- @param #string dest Name of the destination airport.
function RAT:_ATCAddFlight(name, dest)
-- 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
local flight={} --#RAT.AtcFlight
@@ -5603,7 +5628,7 @@ end
function RAT._ATCDelFlight(t,entry)
for k,_ in pairs(t) do
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
end
end
@@ -5614,7 +5639,7 @@ end
-- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered.
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].holding=0
end
@@ -5649,15 +5674,16 @@ function RAT._ATCStatus()
-- Aircraft is holding.
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
BASE:I(RAT.id..text)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then
-- 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)
BASE:I(RAT.id..text)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then
@@ -5711,13 +5737,13 @@ function RAT._ATCCheck()
-- Debug message.
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",
airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60)
BASE:I(RAT.id..text)
BASE:T(RAT.id..text)
else
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",
airportname, flightname, flight.holding/60, flight.holding%60)
BASE:I(RAT.id..text)
BASE:T(RAT.id..text)
-- Clear flight for landing.
RAT._ATCClearForLanding(airportname, flightname)
@@ -5772,7 +5798,7 @@ function RAT._ATCClearForLanding(airportname, flightname)
-- 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
flightname = string.match(flightname,"^(.+)#")
@@ -5799,8 +5825,9 @@ function RAT._ATCFlightLanded(name)
-- Times for holding and final approach.
local Tnow=timer.getTime()
local Tfinal=Tnow-flight.Tonfinal
local Thold=flight.Tonfinal-flight.Tarrive
local Tonfinal = flight.Tonfinal or timer.getTime()-1
local Tfinal=Tnow-Tonfinal
local Thold=Tonfinal-flight.Tarrive
local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport
@@ -5823,9 +5850,9 @@ function RAT._ATCFlightLanded(name)
local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600
-- 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:I(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: 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: Number of flights still on final %d.", dest, airport.Nonfinal))
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
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 #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft.
-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false.
-- @extends Core.Fsm#FSM
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
@@ -1271,6 +1270,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC")
self.controlmsrs:SetVolume(Volume or 1.0)
if self.rangezone then
self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate())
end
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
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:SetLabel("RANGEI")
self.instructmsrs:SetVolume(Volume or 1.0)
if self.rangezone then
self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate())
end
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
@@ -1316,8 +1321,13 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
self.rangecontrol = true
if relayunitname then
local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate()
self.rangecontrolrelayname = relayunitname
if unit then
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
return self
end
@@ -1345,9 +1355,13 @@ function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gen
self.instructor = true
if relayunitname then
local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate()
self.instructmsrs:SetCoordinate(Coordinate)
self.instructorrelayname = relayunitname
if unit then
local Coordinate = unit:GetCoordinate()
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
return self
end
+350 -88
View File
@@ -90,7 +90,8 @@
-- @image Scoring.JPG
--- @type SCORING
-- @field Players A collection of the current players that have joined the game.
-- @field #table Players A collection of the current players that have joined the game.
-- @field Core.Set#SET_SCENERY ScoringScenery
-- @extends Core.Base#BASE
--- SCORING class
@@ -229,7 +230,10 @@ SCORING = {
ClassID = 0,
Players = {},
AutoSave = true,
version = "1.18.4"
version = "1.18.5",
ScoringScenery = nil, -- Core.Set#SET_SCENERY
SceneryHitsInZone = false,
LoadSave = false,
}
local _SCORINGCoalition = {
@@ -248,15 +252,16 @@ local _SCORINGCategory = {
--- Creates a new SCORING object to administer the scoring achieved by players.
-- @param #SCORING self
-- @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 #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
-- @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. 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
-- @usage
--
-- -- Define a new scoring object for the mission Gori Valley.
-- ScoringObject = SCORING:New( "Gori Valley" )
--
function SCORING:New( GameName, SavePath, AutoSave )
function SCORING:New( GameName, SavePath, AutoSave, LoadSave )
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
@@ -264,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
if GameName then
self.GameName = GameName
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
-- Additional Object scores
@@ -299,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave )
self.penaltyoncoalitionchange = true
self:SetDisplayMessagePrefix()
self.SceneryHitsInZone = false
if LoadSave then
self.LoadSave = LoadSave
end
-- Event handlers
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
@@ -325,10 +336,62 @@ function SCORING:New( GameName, SavePath, AutoSave )
self:OpenCSV( GameName )
end
self:I("SCORING "..tostring(GameName).." started! v"..self.version)
if LoadSave == true then
self:_LoadPlayerSummaryScore()
end
return self
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.
-- @param #SCORING self
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
@@ -377,7 +440,7 @@ function SCORING:AddUnitScore( ScoreUnit, Score )
return self
end
--- Removes a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed.
--- Removes a @{Wrapper.Unit} for scoring when the @{Wrapper.Unit} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given.
-- @return #SCORING
@@ -394,10 +457,26 @@ end
-- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name,
-- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}.
-- @param #SCORING self
-- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddStaticScore( ScoreStatic, Score )
return self:AddScoreStatic( ScoreStatic, Score )
end
--- Add a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed.
-- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name,
-- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}.
-- @param #SCORING self
-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreStatic( ScoreStatic, Score )
if ScoreStatic == nil then
BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!")
return self
end
local StaticName = ScoreStatic:GetName()
@@ -406,7 +485,61 @@ function SCORING:AddStaticScore( ScoreStatic, Score )
return self
end
--- Removes a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed.
--- Add a @{Wrapper.Scenery} for additional scoring when the @{Wrapper.Scenery} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Scenery#SCENERY ScoreScenery The @{Wrapper.Scenery} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreScenery( ScoreScenery, Score )
if ScoreScenery == nil then
self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!")
return self
end
if not self.ScoringScenery then
self.ScoringScenery = SET_SCENERY:New() -- Core.Set#SET_SCENERY
end
local StaticName = ScoreScenery:GetName()
self:T("Scenery name = ".. StaticName)
self.ScoringScenery:AddScenery(ScoreScenery)
return self
end
--- Removes a @{Wrapper.Scenery} for scoring when the @{Wrapper.Scenery} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Scenery#SCENERY ScoreStatic The @{Wrapper.Scenery} for which the Score needs to be given.
-- @return #SCORING
function SCORING:RemoveSceneryScore( ScoreScenery )
local StaticName = ScoreScenery:GetName()
self.ScoringObjects[StaticName] = nil
return self
end
--- Specify a special additional score for a @{Core.Set#SET_SCENERY}.
-- @param #SCORING self
-- @param Core.Set#SET_SCENERY Set The @{Core.Set#SET_SCENERY} for which each @{Wrapper.Scenery} in the SET a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreSetScenery(Set, Score)
local set = Set.Set
for _,_static in pairs (set) do
if _static ~= nil then
self:AddScoreScenery(_static,Score)
end
end
return self
end
--- Removes a @{Wrapper.Static} for scoring when the @{Wrapper.Static} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
-- @return #SCORING
@@ -461,6 +594,31 @@ function SCORING:AddScoreSetGroup(Set, Score)
return self
end
--- Specify a special additional score for a @{Core.Set#SET_STATIC}.
-- @param #SCORING self
-- @param Core.Set#SET_STATIC Set The @{Core.Set#SET_STATIC} for which each @{Wrapper.Static} in the SET a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreSetStatic(Set, Score)
local set = Set:GetSetObjects()
for _,_static in pairs (set) do
if _static and _static:IsAlive() then
self:AddStaticScore(_static,Score)
end
end
local function AddScore(static)
self:AddStaticScore(static,Score)
end
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
AddScore(Object)
end
return self
end
--- Add a @{Core.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!
-- This allows for a dynamic destruction zone evolution within your mission.
@@ -480,7 +638,40 @@ function SCORING:AddZoneScore( ScoreZone, Score )
return self
end
--- Remove a @{Core.Zone} for additional scoring.
--- 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.
-- 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.
-- @param #SCORING self
-- @param Core.Set#SET_ZONE ScoreZoneSet The @{Core.Set#SET_ZONE} which defines the destruction score perimeters.
-- Note that a zone can be a polygon or a moving zone.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddZoneScoreSet( ScoreZoneSet, Score )
for _,_zone in pairs(ScoreZoneSet.Set or {}) do
self:AddZoneScore(_zone,Score)
end
return self
end
--- Remove a @{Core.Zone} for scoring.
-- The scoring will search if any @{Core.Zone} is added with the given name, and will remove that zone from the scoring.
-- This allows for a dynamic destruction zone evolution within your mission.
-- @param #SCORING self
@@ -761,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.
if PlayerName then
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].Score = PlayerData.Goals[GoalTag].Score + Score
@@ -958,7 +1163,7 @@ end
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:_EventOnHit( Event )
self:F( { Event } )
--self:F( { Event } )
local InitUnit = nil
local InitUNIT = nil
@@ -988,6 +1193,7 @@ function SCORING:_EventOnHit( Event )
local TargetUnitCategory = nil
local TargetUnitType = nil
local TargetIsScenery = false
local TargetSceneryObject = nil
if Event.IniDCSUnit then
@@ -1009,7 +1215,7 @@ function SCORING:_EventOnHit( Event )
InitUnitCategory = _SCORINGCategory[InitCategory]
InitUnitType = InitType
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } )
--self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } )
end
if Event.TgtDCSUnit then
@@ -1027,18 +1233,23 @@ function SCORING:_EventOnHit( Event )
-- TargetCategory = TargetUnit:getDesc().category
TargetCategory = Event.TgtCategory
TargetType = Event.TgtTypeName
-- Scenery hit
if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then
if TargetUNIT ~= nil and TargetUNIT:IsInstanceOf("SCENERY") then
TargetCategory = Unit.Category.STRUCTURE
TargetIsScenery = true
TargetType = "Scenery"
TargetSceneryObject = TargetUNIT
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
--UTILS.PrintTableToLog(TargetSceneryObject)
end
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } )
--self:T( { TargetUnitName=TargetUnitName, TargetGroupName=TargetGroupName, TargetPlayerName=TargetPlayerName, TargetCoalition=TargetCoalition, TargetCategory=TargetCategory, TargetType=TargetType, TargetUnitCoalition=TargetUnitCoalition, TargetUnitCategory=TargetUnitCategory, TargetUnitType=TargetUnitType } )
end
if InitPlayerName ~= nil then -- It is a player that is hitting something
@@ -1051,7 +1262,7 @@ function SCORING:_EventOnHit( Event )
self:T( "Hitting Something" )
-- What is he hitting?
if TargetCategory then
if (TargetCategory ~=nil) and (TargetIsScenery == false) then
-- A target got hit, score it.
-- Player contains the score data from self.Players[InitPlayerName]
@@ -1071,7 +1282,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then
@@ -1157,21 +1368,21 @@ function SCORING:_EventOnHit( Event )
-- It is a weapon initiated by a player, that is hitting something
-- This seems to occur only with scenery and static objects.
if Event.WeaponPlayerName ~= nil then
self:_AddPlayerFromUnit( Event.WeaponUNIT )
if self.Players[Event.WeaponPlayerName] then -- This should normally not happen, but i'll test it anyway.
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
self:_AddPlayerFromUnit( TargetUNIT )
end
if Event.WeaponPlayerName ~= nil or TargetIsScenery == true then
local playername = Event.WeaponPlayerName or Event.IniPlayerName or "Ghost"
--self:_AddPlayerFromUnit( Event.WeaponUNIT )
if self.Players[playername] then -- This should normally not happen, but i'll test it anyway.
self:T( "Hitting Scenery" )
self:T( "Hitting Scenery or Static" )
-- What is he hitting?
if TargetCategory then
if Event.TgtObjectCategory then
-- A scenery or static got hit, score it.
-- Player contains the score data from self.Players[WeaponPlayerName]
local Player = self.Players[Event.WeaponPlayerName]
local Player = self.Players[playername]
-- Ensure there is a hit table per TargetCategory and TargetUnitName.
Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {}
@@ -1179,15 +1390,17 @@ function SCORING:_EventOnHit( Event )
-- PlayerHit contains the score counters and data per unit that was hit.
local PlayerHit = Player.Hit[TargetCategory][TargetUnitName]
-- Init player scores
PlayerHit.Score = PlayerHit.Score or 0
PlayerHit.Penalty = PlayerHit.Penalty or 0
PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then
@@ -1195,8 +1408,8 @@ function SCORING:_EventOnHit( Event )
PlayerHit.ThreatType = "Unknown"
end
else
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
PlayerHit.ThreatLevel = PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1
PlayerHit.ThreatType = PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or "Unknown"
end
-- Only grant hit scores if there was more than one second between the last hit.
@@ -1204,50 +1417,69 @@ function SCORING:_EventOnHit( Event )
PlayerHit.TimeStamp = timer.getTime()
local Score = 0
if InitCoalition then -- A coalition object was hit, probably a static.
if InitCoalition == TargetCoalition then
-- TODO: Penalty according scale
local Penalty = 10
Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
local TgtName = Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or "Unknown"
--if InitCoalition then -- A coalition object was hit, probably a static.
if TargetIsScenery == true and self.ScoringScenery:IsInSet(TargetSceneryObject) then
Player.Score = Player.Score + self.ScoreIncrementOnHit
PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit scenery target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
else -- A scenery object was hit.
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
elseif TargetIsScenery == false and Event.TgtObjectCategory == Object.Category.STATIC and self.ScoringObjects[TgtName] then
Player.Score = Player.Score + self.ScoreIncrementOnHit
PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit static target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, "", "HIT_SCORE", 1, 0, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, "", "Scenery", TargetUnitType )
end
self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")")
end
--end
end
end
end
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:F( { ScoringZone = ScoreZoneData } )
local hit=Event.TgtUnit
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if TargetUNIT and ScoreZone:IsVec2InZone( TargetUNIT:GetVec2() ) then
-- A scenery or static got hit, score it.
-- Player contains the score data from self.Players[WeaponPlayerName]
local PlayerName = Event.IniPlayerName or "Ghost"
local Player = self.Players[PlayerName]
if Player then
Player.Score = Player.Score + Score
Player.Score = Player.Score + self.ScoreIncrementOnHit
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
end
end
end
end
end
end
--- Track DEAD or CRASH events for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
@@ -1305,7 +1537,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
local Destroyed = false
-- What is the player destroying?
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and TargetUnit and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType
@@ -1425,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event )
end
else
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:F( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
: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
if self.SceneryHitsInZone == true then
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:F( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
: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
end
end
end
end
@@ -1784,9 +2018,12 @@ end
--- Report all players score
-- @param #SCORING self
-- @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 ReportTable = {}
self:T( { "Summary Score Report of All Players", Players = self.Players } )
@@ -1818,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
if JustScore~=true then
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
else
ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
end
end
end
return ReportTable
end
--- Opens a score CSV file to log the scores.
-- @param #SCORING self
-- @param #number sSeconds
-- @return #string ClockString
function SCORING:SecondsToClock( sSeconds )
local nSeconds = sSeconds
if nSeconds == 0 then
@@ -1959,6 +2204,23 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
self.CSVFile:write( "\n" )
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
--- Close CSV file
+16 -5
View File
@@ -19,7 +19,7 @@
--
-- ### 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("*", "CalculateHitZone", "*")
self:I("*** SEAD - Started Version 0.4.9")
self:I("*** SEAD - Started Version 0.4.11")
return self
end
@@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
local WeaponWrapper = WEAPON:New(SEADWeapon)
-- look at a couple of zones across the trajectory
for n=1,3 do
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
_targetskill = tgtgrp:GetUnit(1):GetSkill()
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
@@ -442,7 +442,18 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
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))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
+132 -59
View File
@@ -21,7 +21,7 @@
-- @image Functional.Shorad.jpg
--
-- Date: Nov 2021
-- Last Update: Jan 2025
-- Last Update: Jan 2026
-------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE
@@ -48,7 +48,9 @@
-- @field #number minscootdist Min 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 #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
@@ -114,7 +116,9 @@ SHORAD = {
SkateZones = nil,
minscootdist = 100,
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 #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 SmokeDecoy Throw smoke decoy when getting activated. Defaults to false.
-- @param #number SmokeDecoyColor SMOLECOLOR to use. Defaults to SMOLECOLOR.White
-- @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() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
@@ -171,6 +177,7 @@ do
self.name = Name or "MyShorad"
self.Prefixes = ShoradPrefix or "SAM SHORAD"
self.Radius = Radius or 20000
if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end
self.Coalition = Coalition or "blue"
self.Samset = Samset or GroupSet
self.ActiveTimer = ActiveTimer or 600
@@ -181,8 +188,15 @@ do
self.DefenseLowProb = 70 -- probability to detect a missile shot, low 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
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.
self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState()
@@ -451,6 +465,43 @@ do
return returnname
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
-- @param #SHORAD self
-- @return #boolean Returns true for a detection, else false
@@ -484,7 +535,56 @@ do
-- mymantis:Start()
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
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 targetgroup = TargetGroup
local targetvec2 = nil
@@ -501,69 +601,41 @@ do
local groupset = self.Groupset --Core.Set#SET_GROUP
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
local TDiff = 4
for _,_group in pairs (shoradset) do
local groupname = _group:GetName()
if groupname == TargetGroup and ShotAt==true then
-- Shot at a SHORAD group
if self.UseEmOnOff then
_group:EnableEmission(false)
local allow = false
if self.CallBack and self.UseCallBack == true then
allow = self.CallBack:SeadAllowSuppression(_group,groupname)
end
_group:OptionAlarmStateGreen()
self.ActiveGroups[groupname] = nil
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
--Shoot and Scoot
if self.shootandscoot then
self:__ShootAndScoot(1,_group)
end
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
-- shot at a group we protect
local text = string.format("Waking up SHORAD %s", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
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)
if allow == true then
if self.UseEmOnOff then
_group:EnableEmission(false)
end
_group:OptionAlarmStateGreen()
self.ActiveGroups[groupname] = nil
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
self:_SmokeUnits(_group)
--Shoot and Scoot
if self.shootandscoot then
self:__ShootAndScoot(TDiff,_group)
TDiff=TDiff+1
self:__ShootAndScoot(1,_group)
else
_group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
end
else
WakeUp(_group,groupname)
end
end
end
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
WakeUp(_group,groupname)
end -- end if
end -- end in pairs
return self
end
@@ -679,11 +751,12 @@ do
-- @param Core.Event#EVENTDATA EventData The event details table data set
-- @return #SHORAD self
function SHORAD:HandleEventShot( EventData )
self:T( { EventData } )
--self:T( { EventData.id } )
self:T(self.lid .. " HandleEventShot")
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
local ShootingWeaponName = EventData.WeaponName -- return weapon type
-- get firing coalition
if not EventData.IniGroup then return self end
local weaponcoalition = EventData.IniGroup:GetCoalition()
-- get detection probability
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
@@ -703,7 +776,7 @@ do
-- Is there target data?
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true) then
self:I("**** Tracking AGM-88 with no target data.")
self:T("**** Tracking AGM-88 with no target data.")
local pos0 = EventData.IniUnit:GetCoordinate()
local fheight = EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
+69 -223
View File
@@ -35,7 +35,6 @@
-- ===
--
-- ### Author: **funkyfranky**
-- ### Co-author: FlightControl (cargo dispatcher classes)
--
-- ===
--
@@ -132,14 +131,7 @@
-- a reasonable degree in DCS at the moment and hence cannot be used yet.
--
-- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modeled
-- in a realistic way by using the corresponding cargo dispatcher classes, i.e.
--
-- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}
-- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER}
-- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}
--
-- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. Howver, note that
-- the dispatchers as of yet cannot use user defined off road paths for example since they are classes of their own and use a different routing logic.
-- in a realistic way by using the @{Ops.OpsTransport#OPSTRANSPORT} class.
--
-- ===
--
@@ -230,18 +222,6 @@
-- of 630 kg. This is important as groups cannot be split between carrier units when transporting, i.e. the total weight of the whole group must be smaller than the
-- cargo bay of the transport carrier.
--
-- ### Setting the Load Radius
-- Boading and loading of cargo into a carrier is modeled in a realistic fashion in the AI\_CARGO\DISPATCHER classes, which are used inernally by the WAREHOUSE class.
-- Meaning that troops (cargo) will board, i.e. run or drive to the carrier, and only once they are in close proximity to the transporter they will be loaded (disappear).
--
-- Unfortunately, there are some situations where problems can occur. For example, in DCS tanks have the strong tentendcy not to drive around obstacles but rather to roll over them.
-- I have seen cases where an aircraft of the same coalition as the tank was in its way and the tank drove right through the plane waiting on a parking spot and destroying it.
--
-- As a workaround it is possible to set a larger load radius so that the cargo units are despawned further away from the carrier via the optional **loadradius** parameter:
--
-- warehouseBatumi:AddAsset("Leopard 2", nil, nil, nil, nil, 250)
--
-- Adding the asset like this will cause the units to be loaded into the carrier already at a distance of 250 meters.
--
-- ### Setting the AI Skill
--
@@ -486,7 +466,7 @@
-- and the road connection is less than 3 km.
--
-- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. This is only functional for self propelled assets at the moment
-- and not if using the AI dispatcher classes since these have a different logic to find the route.
-- and not if using the OPSTRANSPORT class since this has a different logic to find the route.
--
-- ## Off Road Connections
--
@@ -595,7 +575,7 @@
--
-- ## Cargo Bay and Weight Limitations
--
-- The transportation of cargo is handled by the AI\_Dispatcher classes. These take the cargo bay of a carrier and the weight of
-- The transportation of cargo is handled by the `OPSTRANSPORT` class. This takes the cargo bay of a carrier and the weight of
-- the cargo into account so that a carrier can only load a realistic amount of cargo.
--
-- However, if troops are supposed to be transported between warehouses, there is one important limitations one has to keep in mind.
@@ -1132,7 +1112,7 @@
-- specify an "Assignment". This can be later used to identify the request and take the right actions.
--
-- Once the request is processed, the @{#WAREHOUSE.OnAfterSelfRequest} function is called. This is where we hook in and postprocess the spawned assets.
-- In particular, we use the @{AI.AI_Formation#AI_FORMATION} class to make some nice escorts for our carrier.
-- In particular, we use the @{Functional.Formation#FORMATION} class to make some nice escorts for our carrier.
--
-- When the resue helo is spawned, we can check that this is the correct asset and make the helo go into formation with the carrier.
-- Once the helo runs out of fuel, it will automatically return to the ship and land. For the warehouse, this means that the "cargo", i.e. the helicopter
@@ -1175,7 +1155,7 @@
--
-- -- Define AI Formation object.
-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason!
-- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Left Formation with Carrier", "Escort Carrier.")
-- CarrierFormationLeft = FORMATION:New(Mother, groupset, "Left Formation with Carrier")
--
-- -- Formation parameters.
-- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50)
@@ -1190,7 +1170,7 @@
--
-- -- Define AI Formation object.
-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason!
-- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Right Formation with Carrier", "Escort Carrier.")
-- CarrierFormationRight = FORMATION:New(Mother, groupset, "Right Formation with Carrier")
--
-- -- Formation parameters.
-- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50)
@@ -1208,7 +1188,7 @@
-- group:StartUncontrolled()
--
-- -- Define AI Formation object.
-- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Fly Formation.")
-- CarrierFormationHelo = FORMATION:New(Mother, groupset, "Helo Formation with Carrier")
--
-- -- Formation parameters.
-- CarrierFormationHelo:FormationCenterWing(-150, 50, 20, 50, 100, 50)
@@ -1798,12 +1778,13 @@ _WAREHOUSEDB = {
--- Warehouse class version.
-- @field #string version
WAREHOUSE.version="1.0.2a"
WAREHOUSE.version="2.0.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DONE: Switch from AI Dispatchers to OPSTRANSPORT
-- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time?
-- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths.
-- NOGO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number?
@@ -3340,7 +3321,6 @@ function WAREHOUSE:FindAssetInDB(group)
if aid~=nil then
local asset=_WAREHOUSEDB.Assets[aid]
self:T2({asset=asset})
if asset==nil then
self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0)
end
@@ -3744,12 +3724,15 @@ function WAREHOUSE:_JobDone()
-- Check conditions for being back home.
local ishome=false
if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then
-- Units go back to the spawn zone, helicopters land and they should not move any more.
ishome=inspawnzone and onground and notmoving
if category==Group.Category.GROUND then
-- Ground units go back to the spawn zone and they should not move any more.
ishome=inspawnzone and notmoving
elseif category==Group.Category.AIRPLANE then
-- Planes need to be on ground at their home airbase and should not move any more.
ishome=athomebase and onground and notmoving
elseif category==Group.Category.HELICOPTER then
-- Helicopters go back to their airbase or spawn zone and should not move any more.
ishome=(athomebase or inspawnzone) and onground and notmoving
end
-- Debug text.
@@ -3918,7 +3901,7 @@ end
-- @param #string assignment A free to choose string specifying an assignment for the asset. This can be used with the @{#WAREHOUSE.OnAfterNewAsset} function.
-- @param #table other (Optional) Table of other useful data. Can be collected via WAREHOUSE.OnAfterNewAsset() function for example
function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute, forcecargobay, forceweight, loadradius, skill, liveries, assignment, other)
self:T({group=group, ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
--self:T({group=group:GetName(), ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
-- Set default.
local n=ngroups or 1
@@ -4113,10 +4096,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
-- Get name of template group.
local templategroupname=group:GetName()
local Descriptors=group:GetUnit(1):GetDesc()
local unit = group:GetUnit(1)
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
local Category=group:GetCategory()
local TypeName=group:GetTypeName()
local TypeName=group:GetTypeName() or "none"
local SpeedMax=group:GetSpeedMax()
local RangeMin=group:GetRange()
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
@@ -4446,7 +4429,6 @@ end
-- @param #WAREHOUSE.Queueitem Request Information table of the request.
-- @return #boolean If true, request is granted.
function WAREHOUSE:onbeforeRequest(From, Event, To, Request)
self:T3({warehouse=self.alias, request=Request})
-- Distance from warehouse to requesting warehouse.
local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate())
@@ -4699,136 +4681,48 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
------------------------------------------------------------------------------------------------------------------------------------
-- Prepare cargo groups for transport
------------------------------------------------------------------------------------------------------------------------------------
-- TODO: set asset.weight for cargos
-- Board radius, i.e. when the cargo will begin to board the carrier
local _boardradius=500
-------------------------
-- Create OPSTRANSPORT --
-------------------------
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
_boardradius=5000
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
--_loadradius=1000
--_boardradius=nil
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
--_boardradius=nil
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
_boardradius=6000
end
-- Empty cargo group set.
local CargoGroups=SET_CARGO:New()
-- Add cargo groups to set.
for _,_group in pairs(CargoGroupSet:GetSetObjects()) do
-- Find asset belonging to this group.
local asset=self:FindAssetInDB(_group)
-- New cargo group object.
local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius)
-- Set weight for this group.
cargogroup:SetWeight(asset.weight)
-- Add group to group set.
CargoGroups:AddCargo(cargogroup)
end
------------------------
-- Create Dispatchers --
------------------------
-- Cargo dispatcher.
local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
-- OPSTRANSPORT
local CargoTransport --Ops.OpsTransport#OPSTRANSPORT
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
-- Pickup and deploy zones.
local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName()))
local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName()))
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet)
-- Set home zone.
CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName()))
CargoTransport = OPSTRANSPORT:New(CargoGroupSet, ZONE_AIRBASE:New(self.airbase:GetName()), ZONE_AIRBASE:New(Request.airbase:GetName()))
CargoTransport:SetEmbarkZone(self.spawnzone)
CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
-- Pickup and deploy zones.
local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet)
-- Home zone.
CargoTransport:SetHomeZone(self.spawnzone)
CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
-- Pickup and deploy zones.
local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, 0)
-- Set home zone.
CargoTransport:SetHomeZone(self.spawnzone)
CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
-- Pickup and deploy zones.
local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone)
PickupZoneSet:AddZone(self.harborzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.harborzone)
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.portzone, Request.warehouse.portzone)
CargoTransport:SetEmbarkZone(self.spawnzone)
CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone)
-- Get the shipping lane to use and pass it to the Dispatcher
local remotename = Request.warehouse.warehouse:GetName()
local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, ShippingLane)
-- Set home zone
CargoTransport:SetHomeZone(self.portzone)
-- TODO: Add shipping lane
-- CargoTransport:AddPathTransport(PathGroup)
else
self:E(self.lid.."ERROR: Unknown transporttype!")
end
-- Set pickup and deploy radii.
-- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone.
local pickupouter = 200
local pickupinner = 0
local deployouter = 200
local deployinner = 0
if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
pickupouter=1000
pickupinner=20
deployouter=1000
deployinner=0
else
pickupouter=200
pickupinner=0
if self.spawnzone.Radius~=nil then
pickupouter=self.spawnzone.Radius
pickupinner=20
end
deployouter=200
deployinner=0
if self.spawnzone.Radius~=nil then
deployouter=Request.warehouse.spawnzone.Radius
deployinner=20
end
end
CargoTransport:SetPickupRadius(pickupouter, pickupinner)
CargoTransport:SetDeployRadius(deployouter, deployinner)
-- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight!
Request.carriercargo={}
@@ -4849,111 +4743,63 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
end
end
--------------------------------
-- Dispatcher Event Functions --
--------------------------------
----------------------------------
-- Opstransport Event Functions --
----------------------------------
--- Function called after carrier picked up something.
function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s picked up at pickup zone %s.", Carrier:GetName(), PickupZone:GetName())
warehouse:T(warehouse.lid..text)
end
--- Function called if something was deployed.
function CargoTransport:OnAfterDeployed(From, Event, To, Carrier, DeployZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
-- TODO: Depoloy zone is nil!
--local text=string.format("Carrier group %s deployed at deploy zone %s.", Carrier:GetName(), DeployZone:GetName())
--warehouse:T(warehouse.lid..text)
end
--- Function called if carrier group is going home.
function CargoTransport:OnAfterHome(From, Event, To, Carrier, Coordinate, Speed, Height, HomeZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s going home to zone %s.", Carrier:GetName(), HomeZone:GetName())
warehouse:T(warehouse.lid..text)
end
CargoTransport.warehouse = self
--- Function called when a carrier unit has loaded a cargo group.
function CargoTransport:OnAfterLoaded(From, Event, To, Carrier, Cargo, CarrierUnit, PickupZone)
function CargoTransport:OnAfterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s", Carrier:GetName(), Cargo:GetName(), CarrierUnit:GetName(), PickupZone:GetName())
warehouse:T(warehouse.lid..text)
local warehouse=CargoTransport.warehouse --#WAREHOUSE
-- Get cargo group object.
local group=Cargo:GetObject() --Wrapper.Group#GROUP
local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP
-- Get request.
local request=warehouse:_GetRequestOfGroup(group, warehouse.pending)
-- Add cargo group to this carrier.
table.insert(request.carriercargo[CarrierUnit:GetName()], warehouse:_GetNameWithOut(Cargo:GetName()))
table.insert(request.carriercargo[CarrierElement.name], warehouse:_GetNameWithOut(group:GetName()))
end
--- Function called when cargo has arrived and was unloaded.
function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone)
function CargoTransport:OnAfterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
local warehouse=CargoTransport.warehouse --Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Get group obejet.
local group=Cargo:GetObject() --Wrapper.Group#GROUP
local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP
-- Debug message.
local text=string.format("Cargo group %s was unloaded from carrier unit %s.", tostring(group:GetName()), tostring(CarrierUnit:GetName()))
local text=string.format("Cargo group %s was unloaded from carrier group %s.", tostring(group:GetName()), tostring(OpsGroupCarrier:GetName()))
warehouse:T(warehouse.lid..text)
-- Load the cargo in the warehouse.
--Cargo:Load(warehouse.warehouse)
-- Trigger Arrived event.
warehouse:Arrived(group)
end
-- TODO: Probably can also add some cargo/carrier dead functions here to simplify things at other places
--- On after BackHome event.
function CargoTransport:OnAfterBackHome(From, Event, To, Carrier)
-- Intellisense.
local carrier=Carrier --Wrapper.Group#GROUP
-- Get warehouse state.
local warehouse=carrier:GetState(carrier, "WAREHOUSE") --#WAREHOUSE
carrier:SmokeWhite()
-- Debug info.
local text=string.format("Carrier %s is back home at warehouse %s.", tostring(Carrier:GetName()), tostring(warehouse.warehouse:GetName()))
MESSAGE:New(text, 5):ToAllIf(warehouse.Debug)
warehouse:I(warehouse.lid..text)
-- Call arrived event for carrier.
warehouse:__Arrived(1, Carrier)
-- Assign cargo to carriers
for _,carriergroup in pairs(TransportGroupSet:GetSetObjects()) do
local opsgroup=nil
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE or Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
opsgroup=FLIGHTGROUP:New(carriergroup)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
opsgroup=ARMYGROUP:New(carriergroup)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
opsgroup=NAVYGROUP:New(carriergroup)
end
opsgroup:AddOpsTransport(CargoTransport)
end
-- Start dispatcher.
CargoTransport:__Start(5)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -6134,6 +5980,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
unit.onboard_num=asset.modex[i]
end
if asset.callsign then
--UTILS.PrintTableToLog(asset.callsign)
unit.callsign=asset.callsign[i]
end
@@ -6153,9 +6000,6 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
-- Uncontrolled spawning.
template.uncontrolled=uncontrolled
-- Debug info.
self:T2({airtemplate=template})
-- Spawn group.
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
@@ -8601,6 +8445,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
local item=self.stock[i] --#WAREHOUSE.Assetitem
if item.uid==stockitem.uid then
table.remove(self.stock,i)
-- 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
break
end
end
+7
View File
@@ -20,6 +20,13 @@ _DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
--- Function that writes to DCS log file
-- @param #string text Formatted text.
-- @param ... Format passed to string.format().
function printf(text, ...)
env.info(string.format(text, ...))
end
--- Check if os etc is available.
BASE:I("Checking de-sanitization of os, io and lfs:")
local __na = false
+7 -54
View File
@@ -32,6 +32,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/TextAndSound.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Pathline.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/ClientMenu.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Vector.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Object.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Identifiable.lua' )
@@ -60,7 +61,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/CleanUp.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Movement.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Sead.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Escort.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/MissileTrainer.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ATC_Ground.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Detection.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/DetectionZones.lua' )
@@ -84,6 +84,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ZoneGoalCargo.lua'
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
@@ -116,43 +117,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/Target.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.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_Patrol.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Engage.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Patrol.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Cap.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Gci.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_BAI.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_CAS.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_SEAD.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Patrol.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAP.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAS.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_BAI.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Formation.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Request.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher_Request.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_APC.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Helicopter.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Airplane.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Ship.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assign.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Route.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
@@ -169,21 +134,9 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioQueue.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioSpeech.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SRS.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/CommandCenter.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Mission.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/TaskInfo.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Manager.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/DetectionManager.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_CARGO.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Transport.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_CSAR.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Point.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' )
-180
View File
@@ -1,180 +0,0 @@
__Moose.Include( 'Utilities\\Enums.lua' )
__Moose.Include( 'Utilities\\Utils.lua' )
__Moose.Include( 'Utilities\\Profiler.lua' )
--__Moose.Include( 'Utilities\\STTS.lua' )
__Moose.Include( 'Utilities\\FiFo.lua' )
__Moose.Include( 'Utilities\\Socket.lua' )
__Moose.Include( 'Core\\Base.lua' )
__Moose.Include( 'Core\\Beacon.lua' )
__Moose.Include( 'Core\\UserFlag.lua' )
__Moose.Include( 'Core\\Report.lua' )
__Moose.Include( 'Core\\Scheduler.lua' )
__Moose.Include( 'Core\\ScheduleDispatcher.lua' )
__Moose.Include( 'Core\\Event.lua' )
__Moose.Include( 'Core\\Settings.lua' )
__Moose.Include( 'Core\\Menu.lua' )
__Moose.Include( 'Core\\Zone.lua' )
__Moose.Include( 'Core\\Velocity.lua' )
__Moose.Include( 'Core\\Database.lua' )
__Moose.Include( 'Core\\Set.lua' )
__Moose.Include( 'Core\\Point.lua' )
__Moose.Include( 'Core\\Pathline.lua' )
__Moose.Include( 'Core\\Message.lua' )
__Moose.Include( 'Core\\Fsm.lua' )
__Moose.Include( 'Core\\Spawn.lua' )
__Moose.Include( 'Core\\SpawnStatic.lua' )
__Moose.Include( 'Core\\Timer.lua' )
__Moose.Include( 'Core\\Goal.lua' )
__Moose.Include( 'Core\\Spot.lua' )
__Moose.Include( 'Core\\Astar.lua' )
__Moose.Include( 'Core\\MarkerOps_Base.lua' )
__Moose.Include( 'Core\\TextAndSound.lua' )
__Moose.Include( 'Core\\Condition.lua' )
__Moose.Include( 'Core\\ClientMenu.lua' )
__Moose.Include( 'Wrapper\\Object.lua' )
__Moose.Include( 'Wrapper\\Identifiable.lua' )
__Moose.Include( 'Wrapper\\Positionable.lua' )
__Moose.Include( 'Wrapper\\Controllable.lua' )
__Moose.Include( 'Wrapper\\Group.lua' )
__Moose.Include( 'Wrapper\\Unit.lua' )
__Moose.Include( 'Wrapper\\Client.lua' )
__Moose.Include( 'Wrapper\\Static.lua' )
__Moose.Include( 'Wrapper\\Airbase.lua' )
__Moose.Include( 'Wrapper\\Scenery.lua' )
__Moose.Include( 'Wrapper\\Marker.lua' )
__Moose.Include( 'Wrapper\\Net.lua' )
__Moose.Include( 'Wrapper\\Weapon.lua' )
__Moose.Include( 'Wrapper\\Storage.lua' )
__Moose.Include( 'Wrapper\\DynamicCargo.lua' )
__Moose.Include( 'Cargo\\Cargo.lua' )
__Moose.Include( 'Cargo\\CargoUnit.lua' )
__Moose.Include( 'Cargo\\CargoSlingload.lua' )
__Moose.Include( 'Cargo\\CargoCrate.lua' )
__Moose.Include( 'Cargo\\CargoGroup.lua' )
__Moose.Include( 'Functional\\Scoring.lua' )
__Moose.Include( 'Functional\\CleanUp.lua' )
__Moose.Include( 'Functional\\Movement.lua' )
__Moose.Include( 'Functional\\Sead.lua' )
__Moose.Include( 'Functional\\Escort.lua' )
__Moose.Include( 'Functional\\MissileTrainer.lua' )
__Moose.Include( 'Functional\\ATC_Ground.lua' )
__Moose.Include( 'Functional\\Detection.lua' )
__Moose.Include( 'Functional\\DetectionZones.lua' )
__Moose.Include( 'Functional\\Designate.lua' )
__Moose.Include( 'Functional\\RAT.lua' )
__Moose.Include( 'Functional\\Range.lua' )
__Moose.Include( 'Functional\\ZoneGoal.lua' )
__Moose.Include( 'Functional\\ZoneGoalCoalition.lua' )
__Moose.Include( 'Functional\\ZoneCaptureCoalition.lua' )
__Moose.Include( 'Functional\\Artillery.lua' )
__Moose.Include( 'Functional\\Suppression.lua' )
__Moose.Include( 'Functional\\PseudoATC.lua' )
__Moose.Include( 'Functional\\Warehouse.lua' )
__Moose.Include( 'Functional\\Fox.lua' )
__Moose.Include( 'Functional\\Mantis.lua' )
__Moose.Include( 'Functional\\Shorad.lua' )
__Moose.Include( 'Functional\\Autolase.lua' )
__Moose.Include( 'Functional\\AICSAR.lua' )
__Moose.Include( 'Functional\\AmmoTruck.lua' )
__Moose.Include( 'Functional\\Tiresias.lua' )
__Moose.Include( 'Functional\\Stratego.lua' )
__Moose.Include( 'Functional\\ClientWatch.lua' )
__Moose.Include( 'Ops\\Airboss.lua' )
__Moose.Include( 'Ops\\RecoveryTanker.lua' )
__Moose.Include( 'Ops\\RescueHelo.lua' )
__Moose.Include( 'Ops\\ATIS.lua' )
__Moose.Include( 'Ops\\Auftrag.lua' )
__Moose.Include( 'Ops\\Target.lua' )
__Moose.Include( 'Ops\\OpsGroup.lua' )
__Moose.Include( 'Ops\\FlightGroup.lua' )
__Moose.Include( 'Ops\\NavyGroup.lua' )
__Moose.Include( 'Ops\\ArmyGroup.lua' )
__Moose.Include( 'Ops\\Cohort.lua' )
__Moose.Include( 'Ops\\Squadron.lua' )
__Moose.Include( 'Ops\\Platoon.lua' )
__Moose.Include( 'Ops\\Legion.lua' )
__Moose.Include( 'Ops\\AirWing.lua' )
__Moose.Include( 'Ops\\Brigade.lua' )
__Moose.Include( 'Ops\\Intelligence.lua' )
__Moose.Include( 'Ops\\Commander.lua' )
__Moose.Include( 'Ops\\OpsTransport.lua' )
__Moose.Include( 'Ops\\CSAR.lua' )
__Moose.Include( 'Ops\\CTLD.lua' )
__Moose.Include( 'Ops\\OpsZone.lua' )
__Moose.Include( 'Ops\\Chief.lua' )
__Moose.Include( 'Ops\\Flotilla.lua' )
__Moose.Include( 'Ops\\Fleet.lua' )
__Moose.Include( 'Ops\\Awacs.lua' )
__Moose.Include( 'Ops\\PlayerTask.lua' )
__Moose.Include( 'Ops\\Operation.lua' )
__Moose.Include( 'Ops\\FlightControl.lua' )
__Moose.Include( 'Ops\\PlayerRecce.lua' )
__Moose.Include( 'Ops\\EasyGCICAP.lua' )
__Moose.Include( 'AI\\AI_Balancer.lua' )
__Moose.Include( 'AI\\AI_Air.lua' )
__Moose.Include( 'AI\\AI_Air_Patrol.lua' )
__Moose.Include( 'AI\\AI_Air_Engage.lua' )
__Moose.Include( 'AI\\AI_A2A_Patrol.lua' )
__Moose.Include( 'AI\\AI_A2A_Cap.lua' )
__Moose.Include( 'AI\\AI_A2A_Gci.lua' )
__Moose.Include( 'AI\\AI_A2A_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_A2G_BAI.lua' )
__Moose.Include( 'AI\\AI_A2G_CAS.lua' )
__Moose.Include( 'AI\\AI_A2G_SEAD.lua' )
__Moose.Include( 'AI\\AI_A2G_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Patrol.lua' )
__Moose.Include( 'AI\\AI_Cap.lua' )
__Moose.Include( 'AI\\AI_Cas.lua' )
__Moose.Include( 'AI\\AI_Bai.lua' )
__Moose.Include( 'AI\\AI_Formation.lua' )
__Moose.Include( 'AI\\AI_Escort.lua' )
__Moose.Include( 'AI\\AI_Escort_Request.lua' )
__Moose.Include( 'AI\\AI_Escort_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Escort_Dispatcher_Request.lua' )
__Moose.Include( 'AI\\AI_Cargo.lua' )
__Moose.Include( 'AI\\AI_Cargo_APC.lua' )
__Moose.Include( 'AI\\AI_Cargo_Helicopter.lua' )
__Moose.Include( 'AI\\AI_Cargo_Airplane.lua' )
__Moose.Include( 'AI\\AI_Cargo_Ship.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_APC.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Helicopter.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Ship.lua' )
__Moose.Include( 'Actions\\Act_Assign.lua' )
__Moose.Include( 'Actions\\Act_Route.lua' )
__Moose.Include( 'Actions\\Act_Account.lua' )
__Moose.Include( 'Actions\\Act_Assist.lua' )
__Moose.Include( 'Sound\\UserSound.lua' )
__Moose.Include( 'Sound\\SoundOutput.lua' )
__Moose.Include( 'Sound\\Radio.lua' )
__Moose.Include( 'Sound\\RadioQueue.lua' )
__Moose.Include( 'Sound\\RadioSpeech.lua' )
__Moose.Include( 'Sound\\SRS.lua' )
__Moose.Include( 'Tasking\\CommandCenter.lua' )
__Moose.Include( 'Tasking\\Mission.lua' )
__Moose.Include( 'Tasking\\Task.lua' )
__Moose.Include( 'Tasking\\TaskInfo.lua' )
__Moose.Include( 'Tasking\\Task_Manager.lua' )
__Moose.Include( 'Tasking\\DetectionManager.lua' )
__Moose.Include( 'Tasking\\Task_A2G_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_A2G.lua' )
__Moose.Include( 'Tasking\\Task_A2A_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_A2A.lua' )
__Moose.Include( 'Tasking\\Task_Cargo.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_Transport.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_CSAR.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_Capture_Zone.lua' )
__Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' )
__Moose.Include( 'Globals.lua' )
@@ -0,0 +1,434 @@
--- **NAVIGATION** - Beacons of the map/theatre.
--
-- **Main Features:**
--
-- * Access beacons of the map
-- * Find closest beacon
-- * Get frequencies and channels
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Beacons).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Beacons
-- @image MOOSE.JPG
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- BEACONS class.
-- @type BEACONS
--
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table beacons Beacons.
--
-- @extends Core.Base#BASE
--- *Hope is the beacon that guides lost ships back to the shore.*
--
-- ===
--
-- # The BEACONS Concept
--
-- This class is designed to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `beacons.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data. `Package` als needs to be available.
--
-- # Basic Setup
--
-- A new `BEACONS` object can be created with the @{#BEACONS.NewFromFile}(*beacons_lua_file*) function.
--
-- local beacons=BEACONS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\beacons.lua")
-- beacons:MarkerShow()
--
-- This will load the beacons 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
-- you provided is correct and all relevant beacons are present.
--
-- # User Functions
--
-- ## F10 Map Markers
--
-- ## Position
--
-- ## Get Closest Beacon
--
--
-- @field #BEACONS
BEACONS = {
ClassName = "BEACONS",
verbose = 1,
beacons = {},
}
--- Mission capability.
-- @type BEACONS.Beacon
-- @field #function display_name Function that returns the localized name.
-- @field #number type Beacon type.
-- @field #string beaconId Beacon ID.
-- @field #string callsign Call sign.
-- @field #number frequency Frequency in Hz.
-- @field #number channel TACAN, RSBN or PRMG channel depending on type.
-- @field #table position Position table.
-- @field #number direction Direction in degrees.
-- @field #table positionGeo Table with latitude and longitude.
-- @field #table sceneObjects Table with scenery objects, e.g. `{t:393396742}`.
-- @field #number chartOffsetX No idea what this offset is?!
-- @field DCS#Vec3 vec3 Position vector 3D.
-- @field #number markerID ID for the F10 marker.
-- @field #string typeName Name of becon type.
-- @field Wrapper.Scenery#SCENERY scenery The scenery object.
--- BEACONS class version.
-- @field #string version
BEACONS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- DONE: TACAN channel from frequency (was already in beacon.lua as channel)
-- DONE: Scenery object
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new BEACONS class instance from a given table.
-- @param #BEACONS self
-- @param #table BeaconTable Table with beacon info.
-- @return #BEACONS self
function BEACONS:NewFromTable(BeaconTable)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #BEACONS
for _,_beacon in pairs(BeaconTable) do
local beacon=_beacon --#BEACONS.Beacon
-- Get 3D vector
beacon.vec3={x=beacon.position[1], y=beacon.position[2], z=beacon.position[3]}
-- Get coordinate
beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3)
-- Get type name
beacon.typeName=self:_GetTypeName(beacon.type)
-- Find closest scenery object from scan
beacon.scenery=beacon.coordinate:FindClosestScenery(20)
-- Debug stuff for scenery object
if false then
if beacon.scenery then
env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s", beacon.callsign, beacon.beaconId, beacon.typeName, beacon.scenery:GetName(), beacon.scenery:GetTypeName() ))
UTILS.PrintTableToLog(beacon.scenery.SceneryObject)
UTILS.PrintTableToLog(beacon.sceneObjects)
else
env.info(string.format("FF NO scenery object %s %s %s ", beacon.callsign, beacon.beaconId, beacon.typeName))
end
end
-- Add to table
table.insert(self.beacons, beacon)
end
-- Debug output
self:I(string.format("Added %d beacons", #self.beacons))
if self.verbose > 0 then
local text="Beacon types:"
for typeName,typeID in pairs(BEACON.Type) do
local n=self:CountBeacons(typeID)
text=text..string.format("\n%s = %d", typeName, n)
end
self:I(text)
end
return self
end
--- Create a new BEACONS class instance from a given file.
-- @param #BEACONS self
-- @param #string FileName Full path to the file containing the map beacons.
-- @return #BEACONS self
function BEACONS:NewFromFile(FileName)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #BEACONS
local exists=UTILS.FileExists(FileName)
if exists==false then
self:E(string.format("ERROR: file with beacon info does not exist!"))
return nil
end
-- This will create a global table `beacons`
dofile(FileName)
-- Get beacons from table.
self=self:NewFromTable(beacons)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get 3D position vector of a specific beacon.
-- @param #BEACONS self
-- @param #BEACONS.Beacon beacon The beacon data structure.
-- @return DCS#Vec3 Position vector.
function BEACONS:GetVec3(beacon)
return beacon.vec3
end
--- Get COORDINATE of a specific beacon.
-- @param #BEACONS self
-- @param #BEACONS.Beacon beacon The beacon data structure.
-- @return Core.Point#COORDINATE The coordinate.
function BEACONS:GetCoordinate(beacon)
local coordinate=COORDINATE:NewFromVec3(beacon.vec3)
return coordinate
end
--- Find closest beacon to a given coordinate.
-- @param #BEACONS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number TypeID (Optional) Only search for specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @param #table ExcludeList (Optional) List of beacons to exclude.
-- @return #BEACONS.Beacon The closest beacon.
function BEACONS:GetClosestBeacon(Coordinate, TypeID, DistMax, ExcludeList)
local beacon=nil --#BEACONS.Beacon
local distmin=math.huge
ExcludeList=ExcludeList or {}
for _,_beacon in pairs(self.beacons) do
local bc=_beacon --#BEACONS.Beacon
if (TypeID==nil or TypeID==bc.type) and (not UTILS.IsInTable(ExcludeList, bc, "beaconId")) then
local dist=Coordinate:Get2DDistance(bc.vec3)
if dist<distmin and (DistMax==nil or dist<=DistMax) then
distmin=dist
beacon=bc
end
end
end
return beacon
end
--- Find closest beacons to a given coordinate.
-- @param #BEACONS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of beacons. Default 5.
-- @param #number TypeID (Optional) Only search for specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #BEACONS.Beacon closest beacons.
function BEACONS:GetClosestBeacons(Coordinate, Nmax, TypeID, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local beacon=self:GetClosestBeacon(Coordinate, TypeID, DistMax, closest)
if beacon then
table.insert(closest, beacon)
else
break
end
end
return closest
end
--- Get table of all beacons, optionally of a given type.
-- @param #BEACONS self
-- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`. Can be handed in as tanle of beacon types.
-- @return #table Table of beacons. Each element is of type #BEACON.Beacon.
function BEACONS:GetBeacons(TypeID)
local beacons={}
local keys = {}
if TypeID~=nil and type(TypeID) ~= "table" then
TypeID = {TypeID}
end
for _,_typeid in pairs(TypeID or {}) do
if _typeid ~= nil then
keys[_typeid] = _typeid
end
end
for _,_beacon in pairs(self.beacons) do
local bc=_beacon --#BEACONS.Beacon
if TypeID==nil or keys[bc.type] ~= nil then
table.insert(beacons, bc)
end
end
return beacons
end
--- Count beacons, optionally of a given type.
-- @param #BEACONS self
-- @param #number TypeID (Optional) Only count specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @return #number Number of beacons.
function BEACONS:CountBeacons(TypeID)
local n=0
if TypeID then
for _,_beacon in pairs(self.beacons) do
local bc=_beacon --#BEACONS.Beacon
if TypeID==bc.type then
n=n+1
end
end
else
n=#self.beacons
end
return n
end
--- Add markers for all beacons on the F10 map. Optionally, only a specific beacon or a certain beacon type can be marked.
-- @param #BEACONS self
-- @param #BEACONS.Beacon Beacon (Optional) Only this specifc beacon.
-- @param #number TypeID (Optional) Only show specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @return #BEACONS self
function BEACONS:MarkerShow(Beacon, TypeID)
for _,_beacon in pairs(self.beacons) do
local beacon=_beacon --#BEACONS.Beacon
if Beacon==nil or Beacon.beaconId==beacon.beaconId then
if TypeID==nil or beacon.type==TypeID then
local text=self:_GetMarkerText(beacon)
local coord=COORDINATE:NewFromVec3(beacon.vec3)
if beacon.markerID then
UTILS.RemoveMark(beacon.markerID)
end
beacon.markerID=coord:MarkToAll(text)
end
end
end
return self
end
--- Remove markers of all beacons from the F10 map. Optionally, remove only marker of a specific beacon or a certain beacon type.
-- @param #BEACONS self
-- @param #BEACONS.Beacon Beacon (Optional) Only this specifc beacon.
-- @param #number TypeID (Optional) Only show specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @return #BEACONS self
function BEACONS:MarkerRemove(Beacon, TypeID)
for _,_beacon in pairs(self.beacons) do
local beacon=_beacon --#BEACONS.Beacon
if Beacon==nil or Beacon.beaconId==beacon.beaconId then
if TypeID==nil or beacon.type==TypeID then
if beacon.markerID then
UTILS.RemoveMark(beacon.markerID)
beacon.markerID=nil
end
end
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get text displayed in the F10 marker.
-- @param #BEACONS self
-- @param #BEACONS.Beacon beacon The beacon data structure.
-- @return #string Marker text.
function BEACONS:_GetMarkerText(beacon)
local frequency, funit=self:_GetFrequency(beacon.frequency)
local direction=beacon.direction~=nil and beacon.direction or -1
local text=string.format("Beacon %s [ID=%s]", tostring(beacon.typeName), tostring(beacon.beaconId))
text=text..string.format("\nCallsign: %s", tostring(beacon.callsign))
if UTILS.IsInTable({BEACON.Type.TACAN, BEACON.Type.RSBN, BEACON.Type.PRMG_GLIDESLOPE, BEACON.Type.PRMG_LOCALIZER}, beacon.type) then
text=text..string.format("\nChannel: %s", tostring(beacon.channel))
end
text=text..string.format("\nFrequency: %.3f %s", frequency, funit)
text=text..string.format("\nDirection: %.1f°", direction)
return text
end
--- Get converted frequency.
-- @param #BEACONS self
-- @param #number freq Frequency in Hz.
-- @return #number Frequency in better unit.
-- @return #string Unit ("Hz", "kHz", "MHz").
function BEACONS:_GetFrequency(freq)
freq=freq or 0
local unit="Hz"
if freq>=1e6 then
freq=freq/1e6
unit="MHz"
elseif freq>=1e3 then
freq=freq/1e3
unit="kHz"
end
return freq, unit
end
--- Get name of beacon type.
-- @param #BEACONS self
-- @param #number typeID Beacon type number.
-- @return #string Type name.
function BEACONS:_GetTypeName(typeID)
if typeID~=nil then
for typeName,_typeID in pairs(BEACON.Type) do
if _typeID==typeID then
return typeName
end
end
end
return "Unknown"
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,602 @@
--- **NAVIGATION** - Navigation Airspace Points, Fixes and Aids.
--
-- **Main Features:**
--
-- * Navigation Fixes
-- * Navigation Aids
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20NavFix).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Point
-- @image MOOSE.JPG
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- NAVFIX class.
-- @type NAVFIX
--
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #string name Name of the point.
-- @field #string typePoint Type of the point, *e.g. "Intersection", "VOR", "Airport".
-- @field Core.Vector#VECTOR vector Position vector of the fix.
-- @field Wrapper.Marker#MARKER marker Marker on F10 map.
-- @field #number altMin Minimum altitude in meters.
-- @field #number altMax Maximum altitude in meters.
-- @field #number speedMin Minimum speed in knots.
-- @field #number speedMax Maximum speed in knots.
--
-- @field #boolean isCompulsory Is this a compulsory fix.
-- @field #boolean isFlyover Is this a flyover fix (`true`) or turning point otherwise.
-- @field #boolean isFAF Is this a final approach fix.
-- @field #boolean isIAF Is this an initial approach fix.
-- @field #boolean isIF Is this an initial fix.
-- @field #boolean isMAF Is this an initial fix.
--
-- @extends Core.Base#BASE
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The NAVFIX Concept
--
-- The NAVFIX class has a great concept!
--
-- A NAVFIX describes a geo position and can, *e.g.*, be part of a FLIGHTPLAN. It has a unique name and is of a certain type, *e.g.* "Intersection", "VOR", "Airbase" etc.
-- It can also have further properties as min/max altitudes and speeds that aircraft need to obey when they pass the point.
--
-- # Basic Setup
--
-- A new `NAVFIX` object can be created with the @{#NAVFIX.New}() function.
--
-- myNavPoint=NAVFIX:New()
-- myTemplate:SetXYZ(X, Y, Z)
--
-- This is how it works.
--
-- @field #NAVFIX
NAVFIX = {
ClassName = "NAVFIX",
verbose = 0,
}
--- Type of point.
-- @type NAVFIX.Type
-- @field #string POINT Waypoint.
-- @field #string INTERSECTION Intersection of airway.
-- @field #string AIRPORT Airport.
-- @field #string VOR Very High Frequency Omnidirectional Range Station.
-- @field #string DME Distance Measuring Equipment.
-- @field #string NDB Non-Directional Beacon.
-- @field #string VORDME Combined VHF omnidirectional range (VOR) with a distance-measuring equipment (DME).
-- @field #string LOC Localizer.
-- @field #string ILS Instrument Landing System.
-- @field #string TACAN TACtical Air Navigation System (TACAN).
NAVFIX.Type={
POINT="Point",
INTERSECTION="Intersection",
AIRPORT="Airport",
NDB="NDB",
VOR="VOR",
DME="DME",
VORDME="VOR/DME",
LOC="Localizer",
ILS="ILS",
TACAN="TACAN"
}
--- NAVFIX class version.
-- @field #string version
NAVFIX.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new NAVFIX class instance from a given VECTOR.
-- @param #NAVFIX self
-- @param #string Name Name/ident of the point. Should be unique!
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
-- @param Core.Vector#VECTOR Vector Position vector of the navpoint.
-- @return #NAVFIX self
function NAVFIX:NewFromVector(Name, Type, Vector)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #NAVFIX
-- Vector of point.
self.vector=Vector
-- Name of point.
self.name=Name
-- Type of the point.
self.typePoint=Type or NAVFIX.Type.POINT
local coord=COORDINATE:NewFromVec3(self.vector)
-- Marker on F10.
self.marker=MARKER:New(coord, self:_GetMarkerText())
-- Log ID string.
self.lid=string.format("NAVFIX %s [%s] | ", tostring(self.name), tostring(self.typePoint))
-- Debug info.
self:I(self.lid..string.format("Created NAVFIX"))
return self
end
--- Create a new NAVFIX class instance from a given COORDINATE.
-- @param #NAVFIX self
-- @param #string Name Name of the fix. Should be unique!
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
-- @param Core.Point#COORDINATE Coordinate Coordinate of the point.
-- @return #NAVFIX self
function NAVFIX:NewFromCoordinate(Name, Type, Coordinate)
-- Create a VECTOR from the coordinate.
local Vector=VECTOR:NewFromVec(Coordinate)
-- Create NAVFIX.
self=NAVFIX:NewFromVector(Name, Type, Vector)
return self
end
--- Create a new NAVFIX instance from given latitude and longitude in degrees, minutes and seconds (DMS).
-- @param #NAVFIX self
-- @param #string Name Name of the fix. Should be unique!
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
-- @param #string Latitude Latitude in DMS as string.
-- @param #string Longitude Longitude in DMS as string.
-- @return #NAVFIX self
function NAVFIX:NewFromLLDMS(Name, Type, Latitude, Longitude)
-- Create a VECTOR from the coordinate.
local Vector=VECTOR:NewFromLLDMS(Latitude, Longitude)
-- Create NAVFIX.
self=NAVFIX:NewFromVector(Name, Type, Vector)
return self
end
--- Create a new NAVFIX instance from given latitude and longitude in decimal degrees (DD).
-- @param #NAVFIX self
-- @param #string Name Name of the fix. Should be unique!
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
-- @param #number Latitude Latitude in DD.
-- @param #number Longitude Longitude in DD.
-- @return #NAVFIX self
function NAVFIX:NewFromLLDD(Name, Type, Latitude, Longitude)
-- Create a VECTOR from the coordinate.
local Vector=VECTOR:NewFromLLDD(Latitude, Longitude)
-- Create NAVFIX.
self=NAVFIX:NewFromVector(Name, Type, Vector)
return self
end
--- Create a new NAVFIX class instance relative to a given other NAVFIX.
-- You have to specify the distance and bearing from the new point to the given point. *E.g.*, for a distance of 5 NM and a bearing of 090° (West), the
-- new nav point is created 5 NM East of the given nav point. The reason is that this corresponts to convention used in most maps.
-- You can, however, use the `Reciprocal` switch to create the new point in the direction you specify.
-- @param #NAVFIX self
-- @param #string Name Name of the fix. Should be unique!
-- @param #string Type Type of navfix.
-- @param #NAVFIX NavFix The given/existing navigation fix relative to which the new fix is created.
-- @param #number Distance Distance from the given to the new point in nautical miles.
-- @param #number Bearing Bearing [Deg] from the new point to the given one.
-- @param #boolean Reciprocal If `true` the reciprocal `Bearing` is taken so it specifies the direction from the given point to the new one.
-- @return #NAVFIX self
function NAVFIX:NewFromNavFix(Name, Type, NavFix, Distance, Bearing, Reciprocal)
-- Convert magnetic to true bearing by adding magnetic declination, e.g. mag. bearing 10°M ==> true bearing 16°M (for 6° variation on Caucasus map)
Bearing=Bearing+UTILS.GetMagneticDeclination()
if Reciprocal then
Bearing=Bearing-180
end
-- Translate.
local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance), Bearing, true)
self=NAVFIX:NewFromVector(Name, Type, Vector)
return self
end
--- Create a new NAVFIX class instance from BEACONS.Beacon data.
-- @param #NAVFIX self
-- @param Navigation.Beacons#BEACONS.Beacon Beacon The beacon data.
-- @return #NAVFIX self
function NAVFIX:NewFromBeacon(Beacon)
local frequency, unit = BEACONS:_GetFrequency(Beacon.frequency)
frequency = string.format("%.3f",frequency)
if Beacon.typeName == "TACAN" then
frequency = Beacon.channel
unit = "X"
end
self = NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set whether this is the intermediate fix (IF).
-- @param #NAVFIX self
-- @return #NAVFIX self
function NAVFIX:SetIntermediateFix(IntermediateFix)
self.isIF=IntermediateFix
return self
end
--- Set whether this is an initial approach fix (IAF).
-- The IAF is the point where the initial approach segment of an instrument approach begins.
-- It is usually a designated intersection, VHF omidirectional range (VOR) non-directional beacon (NDB)
-- or distance measuring equipment (DME) fix.
-- The IAF may be collocated with the intermediate fix (IF) of the instrument apprach an in such case they designate the
-- beginning of the intermediate segment of the approach. When the IAF and the IF are combined, there is no inital approach segment.
-- @param #NAVFIX self
-- @param #boolean IntermediateFix If `true`, this is an intermediate fix.
-- @return #NAVFIX self
function NAVFIX:SetInitialApproachFix(IntermediateFix)
self.isIAF=IntermediateFix
return self
end
--- Set whether this is the final approach fix (FAF).
-- @param #NAVFIX self
-- @param #boolean FinalApproachFix If `true`, this is a final approach fix.
-- @return #NAVFIX self
function NAVFIX:SetFinalApproachFix(FinalApproachFix)
self.isFAF=FinalApproachFix
return self
end
--- Set whether this is the final approach fix (FAF).
-- @param #NAVFIX self
-- @param #boolean FinalApproachFix If `true`, this is a final approach fix.
-- @return #NAVFIX self
function NAVFIX:SetMissedApproachFix(MissedApproachFix)
self.isMAF=MissedApproachFix
return self
end
--- Set minimum altitude.
-- @param #NAVFIX self
-- @param #number Altitude Min altitude in feet.
-- @return #NAVFIX self
function NAVFIX:SetAltMin(Altitude)
self.altMin=Altitude
return self
end
--- Set maximum altitude.
-- @param #NAVFIX self
-- @param #number Altitude Max altitude in feet.
-- @return #NAVFIX self
function NAVFIX:SetAltMax(Altitude)
self.altMax=Altitude
return self
end
--- Set mandatory altitude (min alt = max alt).
-- @param #NAVFIX self
-- @param #number Altitude Altitude in feet.
-- @return #NAVFIX self
function NAVFIX:SetAltMandatory(Altitude)
self.altMin=Altitude
self.altMax=Altitude
return self
end
--- Set minimum allowed speed at this fix.
-- @param #NAVFIX self
-- @param #number Speed Min speed in knots.
-- @return #NAVFIX self
function NAVFIX:SetSpeedMin(Speed)
self.speedMin=Speed
return self
end
--- Set maximum allowed speed at this fix.
-- @param #NAVFIX self
-- @param #number Speed Max speed in knots.
-- @return #NAVFIX self
function NAVFIX:SetSpeedMax(Speed)
self.speedMax=Speed
return self
end
--- Set mandatory speed (min speed = max speed) at this fix.
-- @param #NAVFIX self
-- @param #number Speed Mandatory speed in knots.
-- @return #NAVFIX self
function NAVFIX:SetSpeedMandatory(Speed)
self.speedMin=Speed
self.speedMax=Speed
return self
end
--- Set whether this fix is compulsory.
-- @param #NAVFIX self
-- @param #boolean Compulsory If `true`, this is a compulsory fix. If `false` or nil, it is non-compulsory.
-- @return #NAVFIX self
function NAVFIX:SetCompulsory(Compulsory)
self.isCompulsory=Compulsory
return self
end
--- Set whether this is a fly-over fix fix.
-- @param #NAVFIX self
-- @param #boolean FlyOver If `true`, this is a fly over fix. If `false` or nil, it is not.
-- @return #NAVFIX self
function NAVFIX:SetFlyOver(FlyOver)
self.isFlyover=FlyOver
return self
end
--- Get the altitude in feet MSL. If min and max altitudes are set, it will return a random altitude between min and max.
-- @param #NAVFIX self
-- @return #number Altitude in feet MSL. Can be `nil`, if neither min nor max altitudes have beeen set.
function NAVFIX:GetAltitude()
local alt=nil
if self.altMin and self.altMax and self.altMin~=self.altMax then
alt=math.random(self.altMin, self.altMax)
elseif self.altMin then
alt=self.altMin
elseif self.altMax then
alt=self.altMax
end
return alt
end
--- Get the speed. If min and max speeds are set, it will return a random speed between min and max.
-- @param #NAVFIX self
-- @return #number Speed in knots. Can be `nil`, if neither min nor max speeds have beeen set.
function NAVFIX:GetSpeed()
local speed=nil
if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then
speed=math.random(self.speedMin, self.speedMax)
elseif self.speedMin then
speed=self.speedMin
elseif self.speedMax then
speed=self.speedMax
end
return speed
end
--- Add marker the NAVFIX on the F10 map.
-- @param #NAVFIX self
-- @return #NAVFIX self
function NAVFIX:MarkerShow()
self.marker:ToAll()
return self
end
--- Remove marker of the NAVFIX from the F10 map.
-- @param #NAVFIX self
-- @return #NAVFIX self
function NAVFIX:MarkerRemove()
self.marker:Remove()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get text displayed in the F10 marker.
-- @param #NAVFIX self
-- @return #string Marker text.
function NAVFIX:_GetMarkerText()
local altmin=self.altMin and tostring(self.altMin) or ""
local altmax=self.altMax and tostring(self.altMax) or ""
local speedmin=self.speedMin and tostring(self.speedMin) or ""
local speedmax=self.speedMax and tostring(self.speedMax) or ""
local text=string.format("NAVFIX %s", self.name)
if self.isIAF then
text=text..string.format(" (IAF)")
end
if self.isIF then
text=text..string.format(" (IF)")
end
text=text..string.format("\nAltitude [ft]: %s - %s", altmin, altmax)
text=text..string.format("\nSpeed [knots]: %s - %s", speedmin, speedmax)
text=text..string.format("\nCompulsory: %s", tostring(self.isCompulsory))
text=text..string.format("\nFly Over: %s", tostring(self.isFlyover))
return text
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- NAVAID class.
-- @type NAVAID
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @extends Navigation.Point#NAVFIX
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The NAVAID Concept
--
-- A NAVAID consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
--
-- # Basic Setup
--
-- A new `NAVAID` object can be created with the @{#NAVAID.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
--
-- myFleet=NAVAID:New("myWarehouseName", "1st Fleet")
-- myFleet:SetPortZone(ZonePort1stFleet)
-- myFleet:Start()
--
-- A fleet needs a *port zone*, which is set via the @{#NAVAID.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
--
-- Finally, the fleet needs to be started using the @{#NAVAID.Start}() function. If the fleet is not started, it will not process any requests.
--
-- @field #NAVAID
NAVAID = {
ClassName = "NAVAID",
verbose = 0,
}
--- NAVAID class version.
-- @field #string version
NAVAID.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add frequencies. Which unit MHz, kHz, Hz?
-- TODO: Add radial function
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new NAVAID class instance.
-- @param #NAVAID self
-- @param #string Name Name/ident of this navaid.
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
-- @param #string ZoneName Name of the zone to scan the scenery.
-- @param #string SceneryName Name of the scenery object.
-- @return #NAVAID self
function NAVAID:NewFromScenery(Name, Type, ZoneName, SceneryName)
-- Get the zone.
local zone=ZONE:FindByName(ZoneName)
-- Get coordinate.
local Coordinate=zone:GetCoordinate()
-- Inherit everything from NAVFIX class.
self=BASE:Inherit(self, NAVFIX:NewFromCoordinate(Name, Type, Coordinate)) -- #NAVAID
-- Set zone.
self.zone=ZONE:FindByName(ZoneName)
-- Try to get the scenery object. Note not all can be found unfortunately.
if SceneryName then
self.scenery=SCENERY:FindByNameInZone(SceneryName, ZoneName)
if not self.scenery then
self:E(string.format("ERROR: Could not find scenery object %s in zone %s", SceneryName, ZoneName))
end
end
-- Alias.
self.alias=string.format("%s %s %s", tostring(ZoneName), tostring(SceneryName), tostring(Type))
-- Set some string id for output to DCS.log file.
self.lid=string.format("NAVAID %s | ", self.alias)
-- Debug info.
self:I(self.lid..string.format("Created NAVAID!"))
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set frequency the beacon transmits on.
-- @param #NAVAID self
-- @param #number Frequency Frequency in Hz.
-- @return #NAVAID self
function NAVAID:SetFrequency(Frequency)
self.frequency=Frequency
return self
end
--- Set channel of, *e.g.*, TACAN beacons.
-- @param #NAVAID self
-- @param #number Channel The channel.
-- @param #string Band The band either `"X"` (default) or `"Y"`.
-- @return #NAVAID self
function NAVAID:SetChannel(Channel, Band)
self.channel=Channel
self.band=Band or "X"
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Add private CLASS functions here.
-- No private NAVAID functions yet.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,423 @@
--- **NAVIGATION** - Airbase radios.
--
-- **Main Features:**
--
-- * Get radio frequencies of airbases
-- * Find closest airbase radios
-- * Mark radio frequencies on F10 map
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Radios).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Radios
-- @image MOOSE.JPG
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- RADIOS class.
-- @type RADIOS
--
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table radios Radios.
--
-- @extends Core.Base#BASE
--- *It's not true I had nothing on, I had the radio on.* -- *Marilyn Monroe*
--
-- ===
--
-- # The RADIOS Concept
--
-- This class is designed to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `radio.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data.
--
-- # Basic Setup
--
-- 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")
-- 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
-- you provided is correct and all relevant radios are present.
--
-- # User Functions
--
-- ## F10 Map Markers
--
-- ## Position
--
-- ## Closest Radio
--
--
-- @field #RADIOS
RADIOS = {
ClassName = "RADIOS",
verbose = 0,
radios = {},
}
--- Radio item data structure.
-- @type RADIOS.Radio
-- @field #string radioId Radio ID.
-- @field #table role Roles of the radio (usually {"ground", "tower", "approach"}).
-- @field #table callsign Callsigns of the radio (usually the airbase name).
-- @field #table frequency Frequencies of the radios.
-- @field #table position Position table.
-- @field #table sceneObjects Scenery objects.
-- @field #string name Name of the airbase.
-- @field Wrapper.Airbase#AIRBASE airbase Airbase.
-- @field Core.Point#COORDINATE coordinate The COORDINATE of the radio.
-- @field DCS#Vec3 vec3 3D vector.
-- @field #number markerID Marker ID.
--- Radio item data structure.
-- @type RADIOS.Frequency
-- @field #number modu Modulation type.
-- @field #number freq Frequency in Hz.
--- RADIOS class version.
-- @field #string version
RADIOS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new RADIOS class instance from a given table.
-- @param #RADIOS self
-- @param #table RadioTable Table with radios info.
-- @return #RADIOS self
function RADIOS:NewFromTable(RadioTable)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #RADIOS
--local airbasenames=AIRBASE.GetAllAirbaseNames()
-- Get all airdromes
local airdromes=AIRBASE.GetAllAirbases(nil, Airbase.Category.AIRDROME)
for _,_radio in pairs(RadioTable) do
local radio=_radio --#RADIOS.Radio
-- The table structure of callsign is a bit awkward. We need to get the airbase name.
-- Note that unfortunately, the callsign does not always correspond to the airbase name.
if false then
local cs=radio.callsign[1]
if cs and cs.common then
radio.name=cs.common[1]
elseif cs and cs.nato then
radio.name=cs.nato[1]
else
radio.name="Unknown"
end
radio.name=self:_GetAirbaseName(airbasenames, radio.name)
radio.airbase=AIRBASE:FindByName(radio.name)
end
-- Each radio item has a key radioId = 'airfield106_0', where 106 is the UID of the airbase.
-- So we can use that to get the airbase.
local aid = tonumber(string.match(radio.radioId, "airfield(%d+)_"))
-- Get airbase
radio.airbase=self:_GetAirbaseByID(airdromes, aid)
-- Set other stuff
if radio.airbase then
radio.coordinate=radio.airbase:GetCoordinate()
radio.vec3=radio.airbase:GetVec3()
radio.name=radio.airbase:GetName()
end
-- Add to table
table.insert(self.radios, radio)
end
-- Debug output
self:I(string.format("Added %d radios", #self.radios))
return self
end
--- Create a new RADIOS class instance from a given file.
-- @param #RADIOS self
-- @param #string FileName Full path to the file containing the map radios.
-- @return #RADIOS self
function RADIOS:NewFromFile(FileName)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #RADIOS
local exists=UTILS.FileExists(FileName)
if exists==false then
self:E(string.format("ERROR: file with radios info does not exist! File=%s", tostring(FileName)))
return nil
end
-- Backup DCS radio table
local radiobak=UTILS.DeepCopy(radio)
-- This will create a global table `radio`
dofile(FileName)
-- Get radios from table.
self=self:NewFromTable(radio)
-- Restore DCS radio table
radio=UTILS.DeepCopy(radiobak)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get 3D position vector of a specific radio.
-- @param #RADIOS self
-- @param #RADIOS.Radio radio The radio data structure.
-- @return DCS#Vec3 Position vector.
function RADIOS:GetVec3(radio)
return radio.vec3
end
--- Get COORDINATE of a specific radio.
-- @param #RADIOS self
-- @param #RADIOS.Radio radio The radio data structure.
-- @return Core.Point#COORDINATE The coordinate.
function RADIOS:GetCoordinate(radio)
return radio.coordinate
end
--- Find closest radio to a given coordinate.
-- @param #RADIOS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @param #table ExcludeList (Optional) List of radios to exclude.
-- @return #RADIOS.Radio The closest radio.
function RADIOS:GetClosestRadio(Coordinate, DistMax, ExcludeList)
local radio=nil --#RADIOS.Radio
local distmin=math.huge
ExcludeList=ExcludeList or {}
for _,_radio in pairs(self.radios) do
local ra=_radio --#RADIOS.Radio
if (not UTILS.IsInTable(ExcludeList, ra, "radioId")) then
local dist=Coordinate:Get2DDistance(ra.coordinate)
if dist<distmin and (DistMax==nil or dist<=DistMax) then
distmin=dist
radio=ra
end
end
end
return radio
end
--- Find closest radios to a given coordinate.
-- @param #RADIOS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of radios. Default 5.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #RADIOS.Radio closest radios.
function RADIOS:GetClosestRadios(Coordinate, Nmax, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local radio=self:GetClosestRadio(Coordinate, DistMax, closest)
if radio then
table.insert(closest, radio)
else
break
end
end
return closest
end
--- Add markers for all radios on the F10 map.
-- @param #RADIOS self
-- @param #RADIOS.Radio Radio (Optional) Only this specifc radio.
-- @return #RADIOS self
function RADIOS:MarkerShow(Radio)
for _,_radio in pairs(self.radios) do
local radio=_radio --#RADIOS.Radio
if Radio==nil or Radio.radioId==radio.radioId then
local coord=self:GetCoordinate(radio)
if coord then
local text=self:_GetMarkerText(radio)
if radio.markerID then
UTILS.RemoveMark(radio.markerID)
end
radio.markerID=coord:MarkToAll(text)
end
end
end
return self
end
--- Remove markers of all radios from the F10 map.
-- @param #RADIOS self
-- @param #RADIOS.Radio Radio (Optional) Only this specifc radio.
-- @return #RADIOS self
function RADIOS:MarkerRemove(Radio)
for _,_radio in pairs(self.radios) do
local radio=_radio --#RADIOS.Radio
if Radio==nil or Radio.radioId==radio.radioId then
if radio.markerID then
UTILS.RemoveMark(radio.markerID)
radio.markerID=nil
end
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get text displayed in the F10 marker.
-- @param #RADIOS self
-- @param #RADIOS.Radio radio The radio data structure.
-- @return #string Marker text.
function RADIOS:_GetMarkerText(radio)
local text=string.format("Radio %s", tostring(radio.name))
for b,f in pairs(radio.frequency) do
local frequency=f --#RADIOS.Frequency
local mod=frequency[1]
local fre=frequency[2]
local freq, funit=self:_GetFrequency(fre)
--UTILS.PrintTableToLog(frequency)
local band=self:_GetBandName(b)
text=text..string.format("\n%s: %.3f %s", band, freq, funit)
end
return text
end
--- Get converted frequency.
-- @param #RADIOS self
-- @param #number freq Frequency in Hz.
-- @return #number Frequency in better unit.
-- @return #string Unit ("Hz", "kHz", "MHz").
function RADIOS:_GetFrequency(freq)
freq=freq or 0
local unit="Hz"
if freq>=1e6 then
freq=freq/1e6
unit="MHz"
elseif freq>=1e3 then
freq=freq/1e3
unit="kHz"
end
return freq, unit
end
--- Get name of frequency band.
-- @param #RADIOS self
-- @param #number BandNumber Band as number.
-- @return #string Band name.
function RADIOS:_GetBandName(BandNumber)
if BandNumber~=nil then
for bandName,bandNumber in pairs(ENUMS.FrequencyBand) do
if bandNumber==BandNumber then
return bandName
end
end
end
return "Unknown"
end
--- Get name of frequency band.
-- @param #RADIOS self
-- @param #table airbasenames Names of all airbases.
-- @param #string name Name of airbase.
-- @return #string Name of airbase
function RADIOS:_GetAirbaseName(airbasenames, name)
local airbase=AIRBASE:FindByName(name)
if airbase then
return name
else
for _,airbasename in pairs(airbasenames) do
if string.find(airbasename, name) then
return airbasename
end
end
end
return "Unknown"
end
--- Get name of frequency band.
-- @param #RADIOS self
-- @param #table airbases Table of airbases.
-- @param #number aid Airbase ID.
-- @return Wrapper.Airbase#AIRBASE Airbase matching the ID or nil.
function RADIOS:_GetAirbaseByID(airbases, aid)
for _,_airbase in pairs(airbases) do
local airbase=_airbase --Wrapper.Airbase#AIRBASE
local id=airbase:GetID(true)
if id==aid then
return airbase
end
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,332 @@
--- **NAVIGATION** - Towns of the map/theatre.
--
-- **Main Features:**
--
-- * Find towns of map
-- * Road and rail connections
-- * Find closest town to a given coordinate
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Towns).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Towns
-- @image MOOSE.JPG
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- TOWNS class.
-- @type TOWNS
--
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table towns Towns.
--
-- @extends Core.Base#BASE
--- *Hope is the beacon that guides lost ships back to the shore.*
--
-- ===
--
-- # The TOWNS Concept
--
-- This class is designed to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `towns.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data.
--
-- # Basic Setup
--
-- A new `TOWNS` object can be created with the @{#TOWNS.NewFromFile}(*towns_lua_file*) function.
--
-- local towns=TOWNS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\map\towns.lua")
-- towns:MarkerShow()
--
-- This will load the towns 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
-- you provided is correct and all relevant towns are present.
--
-- # User Functions
--
-- ## F10 Map Markers
--
-- ## Position
--
-- ## Get Closest Town
--
--
-- @field #TOWNS
TOWNS = {
ClassName = "TOWNS",
verbose = 0,
towns = {},
}
--- Town data.
-- @type TOWNS.Town
-- @field #string display_name Displayed name.
-- @field #string name Name of the town.
-- @field #number latitude Latitude.
-- @field #number longitude Longitude
-- @field DCS#Vec3 vec3 Position vector 3D.
-- @field Core.Point#COORDINATE coordinate The coordinate.
-- @field Core.Point#COORDINATE coordRoad The coordinate of the closest road.
-- @field Core.Point#COORDINATE coordRail The coordinate of the closest railway.
-- @field #number markerID ID for the F10 marker.
--- TOWNS class version.
-- @field #string version
TOWNS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- DONE: Road connection
-- DONE: Rail connection
-- DONE: Connection between towns
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new TOWNS class instance from a given table.
-- @param #TOWNS self
-- @param #table TownTable Table with all towns data.
-- @return #TOWNS self
function TOWNS:NewFromTable(TownTable)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #TOWNS
for TownName,_town in pairs(TownTable) do
local town=_town --#TOWNS.Town
town.name=TownName
-- Get coordinate
town.coordinate=COORDINATE:NewFromLLDD(town.latitude, town.longitude)
-- Get coordinate of closest road
town.coordRoad=town.coordinate:GetClosestPointToRoad()
-- Get coordinate of closest rail
town.coordRail=town.coordinate:GetClosestPointToRoad(true)
-- Add to table
table.insert(self.towns, town)
end
-- Debug output
self:I(string.format("Added %d towns", #self.towns))
return self
end
--- Create a new TOWNS class instance from a given file.
-- @param #TOWNS self
-- @param #string FileName Full path to the file containing the towns data.
-- @return #TOWNS self
function TOWNS:NewFromFile(FileName)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #TOWNS
local exists=UTILS.FileExists(FileName)
if exists==false then
self:E(string.format("ERROR: file with towns info does not exist!"))
return nil
end
-- This will create a global table `towns`
dofile(FileName)
-- Get towns from table.
self=self:NewFromTable(towns)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get 3D position vector of a specific town.
-- @param #TOWNS self
-- @param #TOWNS.Town town The town data structure.
-- @return DCS#Vec3 Position vector.
function TOWNS:GetVec3(town)
return town.vec3
end
--- Get COORDINATE of a specific town.
-- @param #TOWNS self
-- @param #TOWNS.Town town The town data structure.
-- @return Core.Point#COORDINATE The coordinate.
function TOWNS:GetCoordinate(town)
return town.coordinate
end
--- Get closest road coordinate of a town.
-- @param #TOWNS self
-- @param #TOWNS.Town town The town data structure.
-- @return Core.Point#COORDINATE The closest road coordinate.
function TOWNS:GetCoordRoad(town)
return town.coordRoad
end
--- Get closest rail coordinate of a town.
-- @param #TOWNS self
-- @param #TOWNS.Town town The town data structure.
-- @return Core.Point#COORDINATE The closest rail coordinate.
function TOWNS:GetCoordRail(town)
return town.coordRail
end
--- Get road or rail connection between two towns.
-- @param #TOWNS self
-- @param #TOWNS.Town townA The town data structure.
-- @param #TOWNS.Town townB The town data structure.
-- @param #boolean Railroad If `true`, find rail road connection
-- @return Core.Pathline#PATHLINE Pathline connecting the two towns on road.
function TOWNS:GetConnectionRoad(townA, townB, Railroad)
local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad, false, Railroad)
return path
end
--- Find closest town to a given coordinate.
-- @param #TOWNS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @param #table ExcludeList (Optional) List of towns excluded from the search.
-- @return #TOWNS.Town The closest town.
function TOWNS:GetClosestTown(Coordinate, DistMax, ExcludeList)
local Town=nil --#TOWNS.Town
local distmin=math.huge
ExcludeList=ExcludeList or {}
for _,_town in pairs(self.towns) do
local town=_town --#TOWNS.Town
if (not UTILS.IsInTable(ExcludeList, town, "name")) then
local dist=Coordinate:Get2DDistance(town.coordinate)
if dist<distmin then
distmin=dist
Town=town
end
end
end
return Town
end
--- Find closest towns to a given coordinate.
-- @param #TOWNS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of towns. Default 5.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #TOWNS.Town closest towns.
function TOWNS:GetClosestTowns(Coordinate, Nmax, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local town=self:GetClosestTown(Coordinate, DistMax, closest)
if town then
table.insert(closest, town)
else
break
end
end
return closest
end
--- Get table of all towns, optionally of a given type.
-- @param #TOWNS self
-- @return #table Table of towns. Each element is of type #TOWN.Town.
function TOWNS:GetTowns()
return self.towns
end
--- Add markers for all towns on the F10 map.
-- @param #TOWNS self
-- @param #TOWNS.Town Town (Optional) Only this specifc town.
-- @return #TOWNS self
function TOWNS:MarkerShow(Town)
for _,_town in pairs(self.towns) do
local town=_town --#TOWNS.Town
if Town==nil or Town.name==town.name then
local text=self:_GetMarkerText(town)
local coord=town.coordinate
if town.markerID then
UTILS.RemoveMark(town.markerID)
end
town.markerID=coord:MarkToAll(text)
end
end
return self
end
--- Remove markers of all towns from the F10 map.
-- @param #TOWNS self
-- @param #TOWNS.Town Town (Optional) Only this specifc town.
-- @return #TOWNS self
function TOWNS:MarkerRemove(Town)
for _,_town in pairs(self.towns) do
local town=_town --#TOWNS.Town
if Town==nil or Town.name==town.name then
if town.markerID then
UTILS.RemoveMark(town.markerID)
town.markerID=nil
end
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get text displayed in the F10 marker.
-- @param #TOWNS self
-- @param #TOWNS.Town town The town data structure.
-- @return #string Marker text.
function TOWNS:_GetMarkerText(town)
local text=string.format("Town %s", town.name)
--text=text..string.format("\nCallsign: %s", tostring(beacon.callsign))
return text
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+36
View File
@@ -326,6 +326,42 @@ function AIRWING:AddSquadron(Squadron)
if Squadron:IsStopped() then
Squadron:Start()
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
end
+20 -14
View File
@@ -45,7 +45,7 @@
-- **Supported Aircraft:**
--
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [F-14A/B/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
-- * [T-45C Goshawk](https://forum.dcs.world/topic/203816-vnao-t-45-goshawk/) (VNAO mod) (Player & AI)
@@ -63,7 +63,7 @@
-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier.
-- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing.
--
-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version.
-- Heatblur's mighty F-14A/B/A Early Tomcat has been added as well.
--
-- The [DCS Supercarriers](https://www.digitalcombatsimulator.com/de/shop/modules/supercarrier/) are also supported.
--
@@ -1276,6 +1276,7 @@ AIRBOSS = {
-- @field #string A4EC A-4E Community mod.
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
-- @field #string F14A F-14A by Heatblur.
-- @field #string F14A_Early F-14A-135-GR-Early by Heatblur.
-- @field #string F14B F-14B by Heatblur.
-- @field #string F14A_AI F-14A Tomcat (AI).
-- @field #string FA18C F/A-18C Hornet (AI).
@@ -1294,6 +1295,7 @@ AIRBOSS.AircraftCarrier={
HORNET="FA-18C_hornet",
A4EC="A-4E-C",
F14A="F-14A-135-GR",
F14A_Early="F-14A-135-GR-Early",
F14B="F-14B",
F14A_AI="F-14A",
FA18C="F/A-18C",
@@ -5473,7 +5475,7 @@ function AIRBOSS:_GetAircraftAoA( playerData )
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
-- Table with AoA values.
@@ -5498,11 +5500,15 @@ function AIRBOSS:_GetAircraftAoA( playerData )
aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.0 ) -- 14.17 --14.5 units -- VNAO Edit - Original value 14.5
aoa.Fast = self:_AoAUnit2Deg( playerData, 13.5 ) -- 13.33 --14.0 units -- VNAO Edit - Original value 14
aoa.FAST = self:_AoAUnit2Deg( playerData, 12.5 ) -- 11.67 --13.0 units -- VNAO Edit - Original value 13
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
elseif goshawk then --These parameters edited by CIRCUIT to support new VNAO flight model
-- T-45C Goshawk parameters.
aoa.SLOW = 9.5 -- 8.00 -- 19
aoa.Slow = 9.25 -- 7.75 -- 18
aoa.OnSpeedMax = 9.0 --7.25 -- 17.5
aoa.OnSpeed = 8.5 -- 7.00 -- 17
aoa.OnSpeedMin = 8.25 -- 6.75 -- 16.5
aoa.Fast = 7.75 -- 6.25 -- 16
aoa.FAST = 5.5 -- 6.00 -- 15
elseif skyhawk then
-- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390
-- Note that these are arbitrary UNITS and not degrees. We need a conversion formula!
@@ -5548,7 +5554,7 @@ function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits )
local degrees = aoaunits
-- Check aircraft type of player.
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
-------------
-- F-14A/B --
@@ -5591,7 +5597,7 @@ function AIRBOSS:_AoADeg2Units( playerData, degrees )
local aoaunits = degrees
-- Check aircraft type of player.
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
-------------
-- F-14A/B --
@@ -11589,7 +11595,7 @@ function AIRBOSS:_AttitudeMonitor( playerData )
local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B -- VNAO Edit - Added
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early -- VNAO Edit - Added
if hornet then -- VNAO Edit - Added
local nozzlePosL = 0 -- VNAO Edit - Added
@@ -11822,7 +11828,7 @@ function AIRBOSS:_NozzleArgumentLeft( unit ) -- VNAO Edit - Added
else -- VNAO Edit - Added
nozzlePosL = 0 -- VNAO Edit - Added
end -- VNAO Edit - Added
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added
end -- VNAO Edit - Added
@@ -11847,7 +11853,7 @@ function AIRBOSS:_NozzleArgumentRight( unit ) -- VNAO Edit - Added
else -- VNAO Edit - Added
nozzlePosR = 0 -- VNAO Edit - Added
end -- VNAO Edit - Added
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added
end -- VNAO Edit - Added
return nozzlePosR -- VNAO Edit - Added
@@ -15062,7 +15068,7 @@ function AIRBOSS:_GetACNickname( actype )
nickname = "Hawkeye"
elseif actype == AIRBOSS.AircraftCarrier.C2A then
nickname = "Greyhound"
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B or actype == AIRBOSS.AircraftCarrier.F14A_Early then
nickname = "Tomcat"
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
nickname = "Hornet"
+6 -7
View File
@@ -839,8 +839,8 @@ function ARMYGROUP:Status()
local ammo=self:GetAmmoElement(element)
-- Output text for element.
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg",
i, name, status, life, life0, ammo.Guns, ammo.Cannons, ammo.Rockets, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
end
if #self.elements==0 then
text=text.." none!"
@@ -1571,7 +1571,7 @@ end
-- @param Core.Zone#ZONE Zone The zone to return to.
-- @param #number Formation Formation of the group.
function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation)
self:T2(self.lid.."onafterRTZ")
self:T(self.lid.."onafterRTZ")
-- Zone.
local zone=Zone or self.homezone
@@ -1841,8 +1841,6 @@ function ARMYGROUP:_UpdateEngageTarget()
-- Check if target moved more than 100 meters or we do not have line of sight.
if dist>100 or los==false then
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
-- Update new position.
self.engage.Coordinate:UpdateFromVec3(vec3)
@@ -1852,13 +1850,14 @@ function ARMYGROUP:_UpdateEngageTarget()
-- Remove current waypoint
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
-- Get new coordinate where to go.
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95)
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=0
self.engage.Waypoint.detour=1
end
+101 -27
View File
@@ -324,6 +324,10 @@
--
-- A ground attack mission can be created with the @{#AUFTRAG.NewGROUNDATTACK}() function.
--
-- ## NAVALENGAGEMENT
--
-- A naval engagement mission can be created with the @{#AUFTRAG.NewNAVALENGAGEMENT}() function.
--
-- # Assigning Missions
--
-- An AUFTRAG can be assigned to groups (FLIGHTGROUP, ARMYGROUP, NAVYGROUP), legions (AIRWING, BRIGADE, FLEET) or to a COMMANDER.
@@ -443,6 +447,7 @@ _AUFTRAGSNR=0
-- @field #string HOVER Hover.
-- @field #string LANDATCOORDINATE Land at coordinate.
-- @field #string GROUNDATTACK Ground attack.
-- @field #string NAVALENGAGEMENT Naval engagement (similar to GROUNDATTACK).
-- @field #string CARGOTRANSPORT Cargo transport.
-- @field #string RELOCATECOHORT Relocate a cohort from one legion to another.
-- @field #string AIRDEFENSE Air defense.
@@ -491,6 +496,7 @@ AUFTRAG.Type={
HOVER="Hover",
LANDATCOORDINATE="Land at Coordinate",
GROUNDATTACK="Ground Attack",
NAVALENGAGEMENT="Naval Engagement",
CARGOTRANSPORT="Cargo Transport",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defence",
@@ -515,6 +521,7 @@ AUFTRAG.Type={
-- @field #string BARRAGE Barrage.
-- @field #string HOVER Hover.
-- @field #string GROUNDATTACK Ground attack.
-- @field #string NAVALENGAGEMENT Naval engagement.
-- @field #string FERRY Ferry mission.
-- @field #string RELOCATECOHORT Relocate cohort.
-- @field #string AIRDEFENSE Air defense.
@@ -537,6 +544,7 @@ AUFTRAG.SpecialTask={
ARMORATTACK="AmorAttack",
HOVER="Hover",
GROUNDATTACK="Ground Attack",
NAVALENGAGEMENT="Naval Engagement",
FERRY="Ferry",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defense",
@@ -666,7 +674,7 @@ AUFTRAG.Category={
--- AUFTRAG class version.
-- @field #string version
AUFTRAG.version="1.2.1"
AUFTRAG.version="1.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -2029,7 +2037,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier)
-- Mission options:
mission.missionTask=ENUMS.MissionTask.NOTHING
mission.missionFraction=0.5
mission.missionFraction=0.9
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.NoReaction
@@ -2210,7 +2218,7 @@ end
-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API.
-- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target Center of the firing solution.
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
-- @param #number Nshots Number of shots to be fired. Default `#nil`. If value is in (0,1), it is interpreted as per cent of available ammo.
-- @param #number Radius Radius of the shells in meters. Default 100 meters.
-- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`.
-- @return #AUFTRAG self
@@ -2382,11 +2390,13 @@ function AUFTRAG:NewARMORATTACK(Target, Speed, Formation)
return mission
end
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack.
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack at their own discretion.
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
-- @param #AUFTRAG self
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
-- @param #number Speed Speed in knots. Default max.
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`.
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`. Only working for ground, not naval!
-- @return #AUFTRAG self
function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
@@ -2413,6 +2423,38 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
return mission
end
--- **[NAVAL]** Create a NAVALENGAGEMENT mission. Naval group(s) will go to a target object and attack at their own discretion.
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
-- @param #AUFTRAG self
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
-- @param #number Speed Speed in knots. Default max.
-- @param #number Depth The attack depth in meters. Only for submarines!
-- @return #AUFTRAG self
function AUFTRAG:NewNAVALENGAGEMENT(Target, Speed, Depth)
local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT)
mission:_TargetFromObject(Target)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT)
-- Defaults.
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=0.70
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil
mission.missionAltitude=Depth or 0
mission.categories={AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
return mission
end
--- **[AIR, GROUND, NAVAL]** Create a RECON mission.
-- @param #AUFTRAG self
-- @param Core.Set#SET_ZONE ZoneSet The recon zones.
@@ -2705,33 +2747,35 @@ function AUFTRAG:NewFromTarget(Target, MissionType)
local mission=nil --#AUFTRAG
if MissionType==AUFTRAG.Type.ANTISHIP then
mission=self:NewANTISHIP(Target, Altitude)
mission=self:NewANTISHIP(Target)
elseif MissionType==AUFTRAG.Type.ARTY then
mission=self:NewARTY(Target, Nshots, Radius)
mission=self:NewARTY(Target, 0.3) -- use 30% of the available ammo
elseif MissionType==AUFTRAG.Type.BAI then
mission=self:NewBAI(Target, Altitude)
mission=self:NewBAI(Target)
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mission=self:NewBOMBCARPET(Target, Altitude, CarpetLength)
mission=self:NewBOMBCARPET(Target)
elseif MissionType==AUFTRAG.Type.BOMBING then
mission=self:NewBOMBING(Target, Altitude)
mission=self:NewBOMBING(Target)
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
mission=self:NewBOMBRUNWAY(Target, Altitude)
mission=self:NewBOMBRUNWAY(Target)
elseif MissionType==AUFTRAG.Type.STRAFING then
mission=self:NewSTRAFING(Target, Altitude)
mission=self:NewSTRAFING(Target)
elseif MissionType==AUFTRAG.Type.CAS then
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, Target:GetAverageCoordinate(), Heading, Leg, TargetTypes)
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), nil, nil, Target:GetAverageCoordinate())
elseif MissionType==AUFTRAG.Type.CASENHANCED then
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, RangeMax, NoEngageZoneSet, TargetTypes)
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000))
elseif MissionType==AUFTRAG.Type.INTERCEPT then
mission=self:NewINTERCEPT(Target)
elseif MissionType==AUFTRAG.Type.SEAD then
mission=self:NewSEAD(Target, Altitude)
mission=self:NewSEAD(Target)
elseif MissionType==AUFTRAG.Type.STRIKE then
mission=self:NewSTRIKE(Target, Altitude)
mission=self:NewSTRIKE(Target)
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
mission=self:NewARMORATTACK(Target, Speed)
mission=self:NewARMORATTACK(Target)
elseif MissionType==AUFTRAG.Type.GROUNDATTACK then
mission=self:NewGROUNDATTACK(Target, Speed, Formation)
mission=self:NewGROUNDATTACK(Target)
elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then
mission=self:NewNAVALENGAGEMENT(Target)
else
return nil
end
@@ -2848,7 +2892,7 @@ function AUFTRAG:NewAUTO(EngageGroup)
if auftrag==AUFTRAG.Type.ANTISHIP then
mission=AUFTRAG:NewANTISHIP(Target)
elseif auftrag==AUFTRAG.Type.ARTY then
mission=AUFTRAG:NewARTY(Target)
mission=AUFTRAG:NewARTY(Target, 0.2)
elseif auftrag==AUFTRAG.Type.AWACS then
mission=AUFTRAG:NewAWACS(Coordinate, Altitude,Speed,Heading,Leg)
elseif auftrag==AUFTRAG.Type.BAI then
@@ -3016,7 +3060,7 @@ end
--- **[LEGION, COMMANDER, CHIEF]** Set the repeat delay in seconds after a mission is successful/failed. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self
-- @param #number Nrepeat Repeat delay in seconds. Default 1.
-- @param #number RepeatDelay Repeat delay in seconds. Default 1.
-- @return #AUFTRAG self
function AUFTRAG:SetRepeatDelay(RepeatDelay)
self.repeatDelay = RepeatDelay
@@ -4399,7 +4443,7 @@ function AUFTRAG:onafterStatus(From, Event, To)
-- Group info.
if self.verbose>=3 then
-- Data on assigned groups.
local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
local text=string.format("Assets [N=%d, Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
for i,_asset in pairs(self.assets or {}) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s", i, asset.spawngroupname, tostring(asset.spawned), tostring(asset.requested), tostring(asset.reserved))
@@ -4440,7 +4484,7 @@ function AUFTRAG:Evaluate()
local owndamage=self.Ncasualties/self.Nelements*100
-- Current number of mission targets.
local Ntargets=self:CountMissionTargets()
local Ntargets=self:CountMissionTargets(true)
local Ntargets0=self:GetTargetInitialNumber()
local Life=self:GetTargetLife()
@@ -4891,16 +4935,25 @@ function AUFTRAG:CheckGroupsDone()
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!", self.status, self:GetState(), self.reinforce))
return false
end
local NopsgroupsAlive=self:CountOpsGroups()
local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED)
-- It could be that all groups were destroyed on the way to the mission execution waypoint.
-- TODO: would be better to check if everybody is dead by now.
if self:IsStarted() and self:CountOpsGroups()==0 then
if self:IsStarted() and NopsgroupsAlive==0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!", self.status, self:GetState()))
return true
end
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and self:CountOpsGroups()>0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
-- Every group alive is done or cancelled
if NopsgroupsAlive==NopsgroupsDone then
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!", self.status, self:GetState(), NopsgroupsAlive))
return true
end
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and NopsgroupsAlive>0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
return false
end
@@ -5494,8 +5547,9 @@ end
--- Count alive mission targets.
-- @param #AUFTRAG self
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target units.
function AUFTRAG:CountMissionTargets()
function AUFTRAG:CountMissionTargets(OnlyReallyAlive)
local N=0
@@ -5503,7 +5557,7 @@ function AUFTRAG:CountMissionTargets()
local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition, true) or nil
if self.engageTarget then
N=self.engageTarget:CountTargets(Coalitions)
N=self.engageTarget:CountTargets(Coalitions, OnlyReallyAlive)
end
return N
@@ -6642,6 +6696,26 @@ function AUFTRAG:GetDCSMissionTask()
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then
---------------------------
-- NAVAL ENGAGEMENT Mission --
---------------------------
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT
-- We create a "fake" DCS task and pass the parameters to the NAVYGROUP.
local param={}
param.target=self:GetTargetData()
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
param.altitude=self.missionAltitude or 0
DCStask.params=param
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.AMMOSUPPLY then
-------------------------
+58 -1
View File
@@ -509,7 +509,7 @@ do
-- @field #AWACS
AWACS = {
ClassName = "AWACS", -- #string
version = "0.2.73", -- #string
version = "0.2.74", -- #string
lid = "", -- #string
coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string
@@ -2060,6 +2060,55 @@ function AWACS:SetRejectionZone(Zone,Draw)
return self
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.
-- @param #AWACS self
-- @return #AWACS self
@@ -4336,6 +4385,14 @@ function AWACS:_StartIntel(awacs)
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
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
local function NewCluster(Cluster)
self:__NewCluster(5,Cluster)
+74 -1
View File
@@ -599,7 +599,80 @@ function BRIGADE:onafterStatus(From, Event, To)
text=text..string.format("\n* %s: spawned=%s", asset.spawngroupname, tostring(asset.spawned))
end
self:I(self.lid..text)
end
end
if self.verbose>=3 then
-- Count numbers
local Ntotal=0
local Nspawned=0
local Nrequested=0
local Nreserved=0
local Nstock=0
local text="\n===========================================\n"
text=text.."Assets:"
local legion=self --Ops.Legion#LEGION
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
for _,_asset in pairs(cohort.assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
local state="In Stock"
if asset.flightgroup then
state=asset.flightgroup:GetState()
local mission=legion:GetAssetCurrentMission(asset)
if mission then
state=state..string.format(", Mission \"%s\" [%s]", mission:GetName(), mission:GetType())
end
else
if asset.spawned then
env.info("FF ERROR: asset has opsgroup but is NOT spawned!")
end
if asset.requested and asset.isReserved then
env.info("FF ERROR: asset is requested and reserved. Should not be both!")
state="Reserved+Requested!"
elseif asset.isReserved then
state="Reserved"
elseif asset.requested then
state="Requested"
end
end
-- Text.
text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]",
asset.uid, asset.spawngroupname, legion.alias, cohort.name, state, tostring(asset.rid))
if asset.spawned then
Nspawned=Nspawned+1
end
if asset.requested then
Nrequested=Nrequested+1
end
if asset.isReserved then
Nreserved=Nreserved+1
end
if not (asset.spawned or asset.requested or asset.isReserved) then
Nstock=Nstock+1
end
Ntotal=Ntotal+1
end
end
text=text.."\n-------------------------------------------"
text=text..string.format("\nNstock = %d", Nstock)
text=text..string.format("\nNreserved = %d", Nreserved)
text=text..string.format("\nNrequested = %d", Nrequested)
text=text..string.format("\nNspawned = %d", Nspawned)
text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved)
text=text.."\n==========================================="
self:I(self.lid..text)
end
end
+85 -32
View File
@@ -31,7 +31,7 @@
-- @image OPS_CSAR.jpg
---
-- Last Update Oct 2025
-- Last Update Jan 2026
-------------------------------------------------------------------------
--- **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.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.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.
@@ -119,6 +119,7 @@
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it).
-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins).
-- 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
--
@@ -272,6 +273,8 @@ CSAR = {
UserSetGroup = nil,
AllowIRStrobe = false,
IRStrobeRuntime = 300,
FARPRescueDistance = 500,
EnableMenuSmokeMASH = true,
}
--- Downed pilots info.
@@ -312,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH58D"] = 2
CSAR.AircraftType["CH-47Fbl1"] = 31
CSAR.AircraftType["AH-6J"] = 2
CSAR.AircraftType["MH-6J"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.34"
CSAR.version="1.0.36"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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.beaconRefresher = 29 -- seconds
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.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.template = Template or "generic" -- template for downed pilot
self.mashprefix = {"MASH"} -- prefixes used to find MASHes
self.EnableMenuSmokeMASH = true
self.autosmoke = false -- automatically smoke location when heli is near
self.autosmokedistance = 2000 -- distance for autosmoke
@@ -673,7 +679,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
DownedPilot.alive = true
DownedPilot.wetfeet = Wetfeet or false
DownedPilot.BeaconName = BeaconName
-- Add Pilot
local PilotTable = self.downedPilots
local counter = self.downedpilotcounter
@@ -851,6 +857,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
else
BeaconName = "Ghost-1-1"..math.random(1,10000)
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
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
@@ -1232,7 +1242,7 @@ function CSAR:_EventHandler(EventData)
local _place = _event.Place -- Wrapper.Airbase#AIRBASE
if _place == nil then
self:T(self.lid .. " Landing Place Nil")
self:T(self.lid .. " Landing Place nil")
return self -- error!
end
@@ -1267,7 +1277,7 @@ function CSAR:_EventHandler(EventData)
self:T("Country = ".._country.." Coalition = ".._coalition)
if _coalition == self.coalition then
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.
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
@@ -1761,7 +1771,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I
end
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
self:T(self.lid.."[Drop off debug] Distance ok, door check")
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
@@ -2138,15 +2155,52 @@ function CSAR:_Reqsmoke( _unitName )
return self
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.
-- @param #CSAR self
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
-- @return #number Distance in meters
-- @return #string MASH Name as string
-- @return Core.Point#COORDINATE Coordinate The MASH/AFB Coordinate (for smoke)
function CSAR:_GetClosestMASH(_heli)
self:T(self.lid .. " _GetClosestMASH")
local _mashset = self.mash -- Core.Set#SET_GROUP
local MashSets = {}
local MashSets = {}
--local _mashes = _mashset.Set-- #table
table.insert(MashSets,_mashset.Set)
table.insert(MashSets,self.zonemashes.Set)
@@ -2155,12 +2209,21 @@ function CSAR:_GetClosestMASH(_heli)
local _distance = 0
local _helicoord = _heli:GetCoordinate()
local MashName = nil
local Coordinate = nil -- Core.Point#COORDINATE
if self.allowFARPRescue then
local position = _heli:GetCoordinate()
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
local afb,distance = _helicoord:GetClosestAirbase(nil,self.coalition)
_shortestDistance = distance
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
for _,_mashes in pairs(MashSets) do
@@ -2175,12 +2238,13 @@ function CSAR:_GetClosestMASH(_heli)
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
MashName = _mashUnit:GetName() or "Unknown"
Coordinate = _mashcoord
end
end
end
if _shortestDistance ~= -1 then
return _shortestDistance, MashName
return _shortestDistance, MashName, Coordinate
else
return -1
end
@@ -2249,6 +2313,9 @@ function CSAR:_AddMedevacMenuItem()
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
if self.AllowIRStrobe then
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
end
if self.EnableMenuSmokeMASH then
local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath, self._ReqsmokeMash,self,_unitName)
else
_rootMenu4:Refresh()
end
@@ -2269,6 +2336,7 @@ function CSAR:_GetDistance(_point1, _point2)
if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_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
return distance1
elseif distance2 and type(distance2) == "number" then
@@ -2368,7 +2436,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
--local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound
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
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.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
local csarhq = self.mash:GetRandom()
if csarhq then
@@ -2854,6 +2906,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
if DownedPilot and DownedPilot.alive then
-- get downed pilot data for saving
local playerName = DownedPilot.player
if playerName == nil or playerName == "" then playerName = "AI MIA" end
local group = DownedPilot.group
local coalition = group:GetCoalition()
local country = group:GetCountry()
@@ -2864,7 +2917,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
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)
self:I(self.lid.."Saving to CSAR File: " .. txt)
self:T(self.lid.."Saving to CSAR File: " .. txt)
data = data .. txt
end
@@ -2979,8 +3032,8 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
-- Info message.
local text=string.format("Loading CSAR state from file %s", filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.verbose>0)
self:T(self.lid..text)
local file=assert(io.open(filename, "rb"))
File diff suppressed because it is too large Load Diff
+18 -4
View File
@@ -35,7 +35,7 @@
-- @field #number Nsuccess Number of successful missions.
-- @field #number Nfailure Number of failed mission.
-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`.
-- @extends Ops.Intel#INTEL
-- @extends Ops.Intelligence#INTEL
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
--
@@ -48,7 +48,7 @@
--
-- # Territory
--
-- The chief class allows you to define boarder zones, conflict zones and attack zones.
-- The chief class allows you to define border zones, conflict zones and attack zones.
--
-- ## Border Zones
--
@@ -332,7 +332,7 @@ CHIEF.Strategy = {
--- CHIEF class version.
-- @field #string version
CHIEF.version="0.6.1"
CHIEF.version="0.7.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -396,7 +396,8 @@ function CHIEF:New(Coalition, AgentSet, Alias)
self.TransportCategories = {Group.Category.HELICOPTER}
-- Create a new COMMANDER.
self.commander=COMMANDER:New(Coalition)
self.commander=COMMANDER:New(Coalition, Alias)
-- Add FSM transitions.
-- From State --> Event --> To State
@@ -2897,6 +2898,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
else
-- Everything else
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50))
@@ -2912,6 +2915,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
---
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT, 50))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
else
self:E(self.lid.."ERROR: Unknown Group category!")
@@ -3353,6 +3358,15 @@ function CHIEF._CheckAssetProperties(Asset, Properties)
return false
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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+28 -18
View File
@@ -629,32 +629,42 @@ end
--- Remove assets from pool. Not that assets must not be spawned or already reserved or requested.
-- @param #COHORT self
-- @param #number N Number of assets to be removed. Default 1.
-- @param #number Delay Delay in seconds before assets are removed.
-- @return #COHORT self
function COHORT:RemoveAssets(N)
function COHORT:RemoveAssets(N, Delay)
self:T2(self.lid..string.format("Remove %d assets of Cohort", N))
N=N or 1
local n=0
for i=#self.assets,1,-1 do
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if Delay and Delay>0 then
-- Delayed call
self:ScheduleOnce(Delay, COHORT.RemoveAssets, self, N, 0)
else
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
if not (asset.requested or asset.spawned or asset.isReserved) then
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
table.remove(self.assets, i)
n=n+1
else
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
N=N or 1
local n=0
for i=#self.assets,1,-1 do
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
if not (asset.requested or asset.spawned or asset.isReserved) then
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
-- Remove from warehouse and warehouse DB
asset.legion:_DeleteStockItem(asset)
table.remove(self.assets, i)
n=n+1
else
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
end
if n>=N then
break
end
end
if n>=N then
break
end
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
end
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
return self
end
+64
View File
@@ -1748,6 +1748,70 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
return cohorts
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.
-- @param #COMMANDER self
-- @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
-------------------------------------------------------------------------
@@ -12,7 +21,7 @@
--
-------------------------------------------------------------------------
-- Date: September 2023
-- Last Update: Aug 2025
-- Last Update: Jan 2026
-------------------------------------------------------------------------
--
--- **Ops** - Easy GCI & CAP Manager
@@ -80,6 +89,8 @@
-- @field #number FuelCriticalThreshold
-- @field #boolean showpatrolpointmarks
-- @field #table EngageTargetTypes
-- @field #number maintenancetime
-- @field #number repairtime
-- @extends Core.Fsm#FSM
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
@@ -109,7 +120,7 @@
-- ### 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 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.
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
--
@@ -205,7 +216,7 @@ EASYGCICAP = {
coalition = "blue",
alias = nil,
wings = {},
Intel = nil,
Intel = nil, -- Ops.Intel#INTEL
resurrection = 900,
capspeed = 300,
capalt = 25000,
@@ -275,10 +286,11 @@ EASYGCICAP = {
--- EASYGCICAP class version.
-- @field #string version
EASYGCICAP.version="0.1.30"
EASYGCICAP.version="0.1.34"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
--
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: TBD
@@ -300,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
-- defaults
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.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
self.wings = {}
@@ -333,10 +349,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false
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.
-- From State --> Event --> To State
self:SetStartState("Stopped")
@@ -350,6 +364,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
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
end
@@ -369,6 +411,28 @@ function EASYGCICAP:GetAirwing(AirbaseName)
return nil
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
-- @param #EASYGCICAP self
-- @return #table Table of Ops.AirWing#AIRWING Airwings
@@ -548,6 +612,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range)
return self
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)
-- @param #EASYGCICAP self
-- @param #number Airframes defaults to 2
@@ -611,7 +687,6 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End)
return self
end
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
-- @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"},
@@ -656,7 +731,7 @@ function EASYGCICAP:_CreateAirwings()
return self
end
--- (internal) Create and add another AirWing to the manager
--- (Internal) Create and add another AirWing to the manager
-- @param #EASYGCICAP self
-- @param #string Airbasename
-- @param #string Alias
@@ -763,8 +838,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
end
end
if self.noalert5 > 0 then
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
if self.noalert5 > 0 then
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:SetRepeat(99)
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:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
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:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
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:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
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:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
@@ -1300,6 +1380,54 @@ function EASYGCICAP:AddConflictZone(Zone)
return self
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.
-- @param #EASYGCICAP self
@@ -1459,6 +1587,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
success = false
end
else
success = true
end
return success
end,
@@ -1468,6 +1598,14 @@ function EASYGCICAP:_AssignIntercept(Cluster)
)
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:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
if not assigned then
@@ -1500,6 +1638,14 @@ function EASYGCICAP:_StartIntel()
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:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
end
end
BlueIntel:Start()
if self.debug then
@@ -1603,7 +1749,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
assets = assets + count
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
self.ReadyFlightGroups = nil
self.ReadyFlightGroups = {}
@@ -1614,7 +1760,8 @@ function EASYGCICAP:onafterStatus(From,Event,To)
local name = FG:GetName()
local engage = FG:IsEngaging()
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)))
if ready then
self.ReadyFlightGroups[name] = FG
+1 -1
View File
@@ -2782,7 +2782,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N)
-- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP.
local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current")
table.insert(wp, current)
-- Add remaining waypoints to route.
for i=n, N do
table.insert(wp, self.waypoints[i])
+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 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 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 ContactsLost Table of lost 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 #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 #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
--- Top Secret!
@@ -102,6 +108,9 @@ INTEL = {
clusterarrows = false,
prediction = 300,
detectStatics = false,
DetectAccoustic = false,
DetectAccousticRadius = 1000,
DetectAccousticUnitTypes = {Unit.Category.HELICOPTER},
}
--- Detected item info.
@@ -160,14 +169,14 @@ INTEL.Ctype={
--- INTEL class version.
-- @field #string version
INTEL.version="0.3.6"
INTEL.version="0.3.10"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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: Filter detection methods.
-- DONE: Accept zones.
@@ -175,6 +184,7 @@ INTEL.version="0.3.6"
-- 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: Combine units to groups for all, new and lost.
-- DONE: Add corridor zones.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -266,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
self:SetForgetTime()
self:SetAcceptZones()
self:SetRejectZones()
self:SetCorridorZones()
self:SetConflictZones()
------------------------
@@ -398,6 +409,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet)
return self
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.
-- @param #INTEL self
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
@@ -475,7 +506,69 @@ function INTEL:RemoveConflictZone(ConflictZone)
return self
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.
-- This avoids fast oscillations between a contact being detected and undetected.
-- @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)
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
@@ -884,6 +989,36 @@ function INTEL:UpdateIntel()
table.insert(remove, unitname)
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.
if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then
@@ -1040,7 +1175,7 @@ function INTEL:_CreateContact(Positionable, RecceName)
item.category=3 --static:GetCategory()
item.categoryname=static:GetCategoryName() or "Unknown"
item.threatlevel=static:GetThreatLevel() or 0
item.position=static:GetCoordinate()
item.position=static:GetCoord()
item.velocity=static:GetVelocityVec3()
item.speed=0
item.recce=RecceName
@@ -1106,7 +1241,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti
return self
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.
-- If no detection method is given, the detection will use all the available methods by default.
-- @param #INTEL self
@@ -1147,15 +1282,15 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
local DetectionAccepted = true
if self.RadarAcceptRange then
local reccecoord = Unit:GetCoordinate()
local coord = unit:GetCoordinate()
local reccecoord = Unit:GetCoord()
local coord = unit:GetCoord()
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
end
if self.RadarBlur then
local reccecoord = Unit:GetCoordinate()
local coord = unit:GetCoordinate()
local reccecoord = Unit:GetCoord()
local coord = unit:GetCoord()
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
local AGL = unit:GetAltitude(true)
local minheight = self.RadarBlurMinHeight or 250 -- meters
@@ -1202,6 +1337,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -2144,7 +2306,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update)
return Cluster.coordinate
end
--- Check if the coorindate of the cluster changed.
--- Check if the coordindate of the cluster changed.
-- @param #INTEL self
-- @param #INTEL.Cluster Cluster The cluster.
-- @param #number Threshold in meters. Default 100 m.
+10 -5
View File
@@ -1901,7 +1901,7 @@ function LEGION:_TacticalOverview()
for _,mtype in pairs(AUFTRAG.Type) do
local n=self:CountMissionsInQueue(mtype)
if n>0 then
local N=self:CountMissionsInQueue(mtype)
local N=self:CountMissionsInQueue(mtype, true)
text=text..string.format(" - %s: %d [Running=%d]\n", mtype, n, N)
end
end
@@ -2057,18 +2057,23 @@ end
--- Count missions in mission queue.
-- @param #LEGION self
-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`.
-- @param #boolean OnlyRunning If `true`, only count running missions.
-- @return #number Number of missions that are not over yet.
function LEGION:CountMissionsInQueue(MissionTypes)
function LEGION:CountMissionsInQueue(MissionTypes, OnlyRunning)
MissionTypes=MissionTypes or AUFTRAG.Type
local N=0
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if (not OnlyRunning) or (mission.statusLegion~=AUFTRAG.Status.PLANNED) then
-- Check if this mission type is requested.
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
-- Check if this mission type is requested.
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
end
end
end
+25 -19
View File
@@ -86,12 +86,14 @@ NAVYGROUP = {
-- @field Ops.Target#TARGET Target The target.
-- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target.
-- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target.
-- @field #number Speed Speed in knots.
-- @field #number Depth Depth of the engagement (submarines).
-- @field #number roe ROE backup.
-- @field #number alarmstate Alarm state backup.
--- NavyGroup version.
-- @field #string version
NAVYGROUP.version="1.0.3"
NAVYGROUP.version="1.0.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -1081,7 +1083,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Cannons, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
@@ -1602,8 +1604,10 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP Group the group to be engaged.
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
-- @param Ops.Target#TARGET Target The target to be engaged. Can also be a GROUP or UNIT object.
-- @param #number Speed Attack speed in knots.
-- @param #number Depth The depth in meters. Only for submarins.
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Depth)
self:T(self.lid.."Engaging Target")
if Target:IsInstanceOf("TARGET") then
@@ -1615,11 +1619,9 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
-- Target coordinate.
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
-- Get a coordinate close to the target.
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
-- Backup ROE and alarm state.
self.engage.roe=self:GetROE()
self.engage.alarmstate=self:GetAlarmstate()
@@ -1629,11 +1631,17 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
self:SwitchROE(ENUMS.ROE.OpenFire)
-- ID of current waypoint.
local uid=self:GetWaypointCurrent().uid
local uid=self:GetWaypointCurrentUID()
-- Set formation.
self.engage.Depth=Depth or 0
-- Set speed.
self.engage.Speed=Speed
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
self.engage.Waypoint=self:AddWaypoint(intercoord, Speed, uid, Depth, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=1
@@ -1656,24 +1664,22 @@ function NAVYGROUP:_UpdateEngageTarget()
-- Check if target moved more than 100 meters.
if dist>100 then
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
-- Update new position.
self.engage.Coordinate:UpdateFromVec3(vec3)
-- ID of current waypoint.
local uid=self:GetWaypointCurrent().uid
local uid=self:GetWaypointCurrentUID()
-- Remove current waypoint
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Depth, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=0
self.engage.Waypoint.detour=1
end
@@ -1709,8 +1715,8 @@ function NAVYGROUP:onafterDisengage(From, Event, To)
local task=self:GetTaskCurrent()
-- Get if current task is ground attack.
if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then
self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!")
if task and (task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT) then
self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!")
self:TaskDone(task)
end
+106 -25
View File
@@ -405,7 +405,9 @@ OPSGROUP.TaskType={
--- Ammo data.
-- @type OPSGROUP.Ammo
-- @field #number Total Total amount of ammo.
-- @field #number Guns Amount of gun shells.
-- @field #number Shells Amount of shells (guns + cannons).
-- @field #number Guns Amount of gun shells (caliber < 25).
-- @field #number Cannons Amount of cannon shells (caliber >= 25).
-- @field #number Bombs Amount of bombs.
-- @field #number Rockets Amount of rockets.
-- @field #number Torpedos Amount of torpedos.
@@ -512,7 +514,7 @@ OPSGROUP.CargoStatus={
--- OpsGroup version.
-- @field #string version
OPSGROUP.version="1.0.4"
OPSGROUP.version="1.0.5"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -1189,8 +1191,8 @@ function OPSGROUP:GetDCSObject()
return self.dcsgroup
end
--- Set detection on or off.
-- If detection is on, detected targets of the group will be evaluated and FSM events triggered.
--- Make a target (unit, group, opsgroup) known to this group.
-- This is useing the DCS function `controller.knowTarget`.
-- @param #OPSGROUP self
-- @param Wrapper.Positionable#POSITIONABLE TargetObject The target object.
-- @param #boolean KnowType Make type known.
@@ -2739,6 +2741,15 @@ function OPSGROUP:IsOutOfTorpedos()
return self.outofTorpedos
end
--- Check if the group is out of A2G Ammo
-- @param #OPSGROUP self
-- @return #boolean If `true`, group is out of torpedos.
function OPSGROUP:IsOutOfA2GAmmo()
if (self.outofMissilesAG and self.outofBombs and self.outofGuns) or self.outofAmmo then
return true
end
return false
end
--- Check if the group has currently switched a LASER on.
-- @param #OPSGROUP self
@@ -4300,7 +4311,27 @@ function OPSGROUP:_UpdateTask(Task, Mission)
Mission=Mission or self:GetMissionByTaskID(self.taskcurrent)
if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then
if Mission.type == AUFTRAG.Type.RESCUEHELO then
self:T("**********")
self:T("** RESCUEHELO USED")
self:T("**********")
local param=Task.dcstask.params
local followUnit=UNIT:FindByName(param.unitname)
local helogroupname = self:GetGroup():GetName()
Task.formation = RESCUEHELO:New(followUnit,helogroupname)
Task.formation:SetRespawnOnOff(false)
Task.formation.respawninair=false
Task.formation:SetTakeoffCold()
Task.formation:SetHomeBase(followUnit)
Task.formation.helo = self:GetGroup()
-- Start formation FSM.
Task.formation:Start()
if self:IsFlightgroup() then
self:SetDespawnAfterLanding()
end
else
-- Set of group(s) to follow Mother.
local followSet=SET_GROUP:New():AddGroup(self.group)
@@ -4309,7 +4340,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
local followUnit=UNIT:FindByName(param.unitname)
-- Define AI Formation object.
Task.formation=AI_FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION, "Follow X at given parameters.")
Task.formation=FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION)
-- Formation parameters.
Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50)
@@ -4318,11 +4349,13 @@ function OPSGROUP:_UpdateTask(Task, Mission)
Task.formation:SetFollowTimeInterval(param.dtFollow)
-- Formation mode.
Task.formation:SetFlightModeFormation(self.group)
--Task.formation:SetFlightModeFormation(self.group)
-- Start formation FSM.
Task.formation:Start()
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
---
@@ -4513,6 +4546,25 @@ function OPSGROUP:_UpdateTask(Task, Mission)
if target then
self:EngageTarget(target, speed, Task.dcstask.params.formation)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
---
-- Task "Naval Engagement" Mission.
---
-- Engage target.
local target=Task.dcstask.params.target --Ops.Target#TARGET
-- Set speed. Default max.
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
if Task.dcstask.params.speed then
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
end
if target then
self:EngageTarget(target, speed, Task.dcstask.params.altitude)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then
@@ -4739,7 +4791,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
elseif weaponType==ENUMS.WeaponFlag.AnyRocket then
nAmmo=ammo.Rockets
elseif weaponType==ENUMS.WeaponFlag.Cannons then
nAmmo=ammo.Guns
nAmmo=ammo.Cannons
end
--TODO: Update target location while we're at it anyway.
@@ -4886,7 +4938,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task)
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then
done=true
@@ -5048,7 +5100,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task)
if Task.description=="Task_Land_At" then
self:T(self.lid.."Taske DONE Task_Land_At ==> Wait")
self:Cruise()
-- After the land task, we set the helo to wait. This is because of an issue that the passing waypoint function is triggered immidiately if we do not do this!
self:Wait(20, 100)
else
self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec")
@@ -5718,7 +5770,7 @@ end
function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
-- Debug info.
local text=string.format("Mission %s DONE!", Mission.name)
local text=string.format("Mission DONE %s!", Mission.name)
self:T(self.lid..text)
-- Set group status.
@@ -6231,11 +6283,12 @@ function OPSGROUP:RouteToMission(mission, delay)
self:T(self.lid.."Already in mission zone ==> TaskExecute()")
self:TaskExecute(waypointtask)
-- TODO: Calling PassingWaypoint here is probably better as it marks the mission waypoint as passed!
--self:PassingWaypoint(waypoint)
self:PassingWaypoint(waypoint)
return
elseif d<25 then
self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()")
self:TaskExecute(waypointtask)
self:PassingWaypoint(waypoint)
return
end
@@ -7850,7 +7903,7 @@ function OPSGROUP:_Spawn(Delay, Template)
self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template)
else
-- Debug output.
self:T2({Template=Template})
--self:T2({Template=Template})
if self:IsArmygroup() and self.ValidateAndRepositionGroundUnits then
UTILS.ValidateAndRepositionGroundUnits(Template.units)
@@ -7998,11 +8051,16 @@ function OPSGROUP:onafterDead(From, Event, To)
-- Get asset.
local asset=self.legion:GetAssetByName(self.groupname)
if asset then
-- Get request.
local request=self.legion:GetRequestByID(asset.rid)
-- Trigger asset dead event.
self.legion:AssetDead(asset, request)
end
end
-- Stop in 5 sec to give possible respawn attempts a chance.
@@ -8273,7 +8331,8 @@ function OPSGROUP:_CheckCargoTransport()
end
-- Boarding finished ==> Transport cargo.
if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self) and not boarding then
local required=self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self)
if gotcargo and required and not boarding then
self:T(self.lid.."Boarding/loading finished ==> Loaded")
self.Tloading=nil
self:LoadingDone()
@@ -9909,6 +9968,10 @@ function OPSGROUP:onafterTransport(From, Event, To)
self:T(self.lid.."ERROR: No current task but landed at?!")
end
end
if self:IsWaiting() then
self:__Cruise(-10)
end
elseif self:IsArmygroup() then
@@ -11190,11 +11253,11 @@ function OPSGROUP:_CheckAmmoStatus()
self:OutOfAmmo()
end
-- Guns.
if self.outofGuns and ammo.Guns>0 then
-- Guns (changed to shells)
if self.outofGuns and ammo.Shells>0 then
self.outofGuns=false
end
if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then
if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then
self.outofGuns=true
self:OutOfGuns()
end
@@ -12695,7 +12758,7 @@ end
-- @return #OPSGROUP self
function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber)
self:T(self.lid..string.format("Setting Default callsing %s-%s", tostring(CallsignName), tostring(CallsignNumber)))
self:T(self.lid..string.format("Setting Default callsign %s-%s", tostring(CallsignName), tostring(CallsignNumber)))
self.callsignDefault={} --#OPSGROUP.Callsign
self.callsignDefault.NumberSquad=CallsignName
@@ -13370,7 +13433,9 @@ function OPSGROUP:GetAmmoTot()
local Ammo={} --#OPSGROUP.Ammo
Ammo.Total=0
Ammo.Shells=0
Ammo.Guns=0
Ammo.Cannons=0
Ammo.Rockets=0
Ammo.Bombs=0
Ammo.Torpedos=0
@@ -13391,7 +13456,9 @@ function OPSGROUP:GetAmmoTot()
-- Add up total.
Ammo.Total=Ammo.Total+ammo.Total
Ammo.Shells=Ammo.Shells+ammo.Shells
Ammo.Guns=Ammo.Guns+ammo.Guns
Ammo.Cannons=Ammo.Cannons+ammo.Cannons
Ammo.Rockets=Ammo.Rockets+ammo.Rockets
Ammo.Bombs=Ammo.Bombs+ammo.Bombs
Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos
@@ -13424,6 +13491,8 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Init counter.
local nammo=0
local nshells=0
local nguns=0
local ncannons=0
local nrockets=0
local nmissiles=0
local nmissilesAA=0
@@ -13446,10 +13515,10 @@ function OPSGROUP:GetAmmoUnit(unit, display)
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
--self:I(ammotable)
--self:I(ammotable)
--UTILS.PrintTableToLog(ammotable)
-- Loop over all weapons.
for w=1,weapons do
@@ -13457,9 +13526,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Number of current weapon.
local Nammo=ammotable[w]["count"]
-- Range in meters. Seems only to exist for missiles (not shells).
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
-- Range in meters. Seems only to exist for missiles (not shells).
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
-- Type name of current weapon.
local Tammo=ammotable[w]["desc"]["typeName"]
@@ -13481,6 +13550,16 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Add up all shells.
nshells=nshells+Nammo
-- Add small and large caliber shells for guns and cannons
if ammotable[w]["desc"]["warhead"] and ammotable[w]["desc"]["warhead"]["caliber"] then
local caliber=ammotable[w]["desc"]["warhead"]["caliber"]
if caliber<25 then
nguns=nguns+Nammo
else
ncannons=ncannons+Nammo
end
end
-- Debug info.
text=text..string.format("- %d shells of type %s, range=%d - %d meters\n", Nammo, _weaponName, rmin, rmax)
@@ -13559,7 +13638,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
local ammo={} --#OPSGROUP.Ammo
ammo.Total=nammo
ammo.Guns=nshells
ammo.Shells=nshells
ammo.Guns=nguns
ammo.Cannons=ncannons
ammo.Rockets=nrockets
ammo.Bombs=nbombs
ammo.Torpedos=ntorps
+4 -2
View File
@@ -244,7 +244,7 @@ _OPSTRANSPORTID=0
--- Army Group version.
-- @field #string version
OPSTRANSPORT.version="0.8.0"
OPSTRANSPORT.version="0.9.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -2127,7 +2127,9 @@ function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo, CarrierGroup)
requiredCargos={}
for _,_cargo in pairs(TransportZoneCombo.Cargos) do
local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup
table.insert(requiredCargos, cargo.opsgroup)
if not cargo.delivered then
table.insert(requiredCargos, cargo.opsgroup)
end
end
end
+379 -36
View File
@@ -21,7 +21,7 @@
-- ===
-- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg
-- @date Last Update Oct 2025
-- @date Last Update Dec 2025
do
@@ -61,6 +61,7 @@ do
-- @field #number PreviousCount
-- @field #boolean CanSmoke
-- @field #boolean ShowThreatDetails
-- @field #boolean PersistMe
-- @extends Core.Fsm#FSM
@@ -98,11 +99,12 @@ PLAYERTASK = {
PreviousCount = 0,
CanSmoke = true,
ShowThreatDetails = true,
PersistMe = false,
}
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASK.version="0.1.29"
PLAYERTASK.version="0.1.31"
--- Generic task condition.
-- @type PLAYERTASK.Condition
@@ -235,7 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
-- @param #string From From state.
-- @param #string Event Event.
-- @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.
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
@@ -401,8 +403,17 @@ function PLAYERTASK:CanJoinTask(Group, Client)
return true
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
-- @param #PLAYERTASK self
--
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
-- @return #PLAYERTASK self
function PLAYERTASK:_SetController(Controller)
@@ -1088,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
return self
end
--- [Internal] On after progress call
-- @param #PLAYERTASK self
-- @param #string From
@@ -1363,6 +1373,12 @@ do
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask
-- @field #boolean InformationMenu Show Radio Info Menu
-- @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
---
@@ -1583,6 +1599,7 @@ do
-- ELEVATION = "\nTarget Elevation: %s %s",
-- METER = "meter",
-- FEET = "feet",
-- INTERCEPTCOURSE = "Intercept course",
-- },
--
-- 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.
-- 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.
--
@@ -1720,6 +1757,12 @@ PLAYERTASKCONTROLLER = {
MenuNoTask = nil,
InformationMenu = false,
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.CSAR = "Combat Rescue"
AUFTRAG.Type.CONQUER = "Conquer"
---
-- @type Scores
PLAYERTASKCONTROLLER.Scores = {
@@ -1759,6 +1803,24 @@ PLAYERTASKCONTROLLER.Scores = {
[AUFTRAG.Type.CAP] = 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
@@ -1860,6 +1922,7 @@ PLAYERTASKCONTROLLER.Messages = {
DESTROYER = "Destroyer",
CARRIER = "Aircraft Carrier",
RADIOS = "Radios",
INTERCEPTCOURSE = "Intercept course",
},
DE = {
TASKABORT = "Auftrag abgebrochen!",
@@ -1947,12 +2010,13 @@ PLAYERTASKCONTROLLER.Messages = {
DESTROYER = "Zerstörer",
CARRIER = "Flugzeugträger",
RADIOS = "Frequenzen",
INTERCEPTCOURSE = "Abfangkurs",
},
}
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASKCONTROLLER.version="0.1.71"
PLAYERTASKCONTROLLER.version="0.1.73"
--- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self
@@ -2059,7 +2123,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
self:AddTransition("*", "Stop", "Stopped")
self:__Start(2)
local starttime = math.random(5,10)
local starttime = math.random(10,15)
self:__Status(starttime)
self:I(self.lid..self.version.." Started.")
@@ -2174,6 +2238,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
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
-- @param #PLAYERTASKCONTROLLER self
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
@@ -2861,15 +2949,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
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
end
@@ -3566,9 +3645,9 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
PlayerTask:_SetController(self)
PlayerTask:SetCoalition(self.Coalition)
self.TaskQueue:Push(PlayerTask)
if not Silent then
self:__TaskAdded(10,PlayerTask)
end
--if not Silent then
self:__TaskAdded(10,PlayerTask,Silent)
--end
else
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
end
@@ -3700,6 +3779,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client)
return self
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
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
@@ -3709,16 +3823,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo()
if _client and _client:IsAlive() then
if self.TasksPerPlayer:HasUniqueID(_playername) then
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
local Coordinate = task.Target:GetCoordinate()
local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE
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)
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
local text = targettxt..": "..CoordText
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
else
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
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
local text = "Target: "..CoordText
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
end
end
end
@@ -3770,7 +3902,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local CoordTextLLDM = nil
local ShowThreatInfo = task.ShowThreatDetails
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)
else
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
@@ -3807,8 +3939,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
end
-- ELEVATION = "\nTarget Elevation: %s %s",
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
if task.Type ~= AUFTRAG.Type.INTERCEPT then
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
end
-- Prec bombing
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if LasingDrone and LasingDrone.playertask then
@@ -4584,6 +4718,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
return self
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.
-- @param #PLAYERTASKCONTROLLER self
-- @param #string Name The name to use as the top menu designation.
@@ -4765,6 +4969,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
return self
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
-- TODO: FSM Functions PLAYERTASKCONTROLLER
@@ -4788,7 +5114,12 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft, 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
end
@@ -4828,6 +5159,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
self:I(text)
end
-- Persistence
if self.TaskPersistanceSwitch == true then
self:_SaveTasksPersisted()
end
if self:GetState() ~= "Stopped" then
self:__Status(-30)
end
@@ -4983,19 +5319,26 @@ end
-- @param #string Event
-- @param #string To
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Silent
-- @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(self.lid.."TaskAdded")
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
if not Silent then
if not self.NoScreenOutput then
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
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)
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)))
if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then
self:_AddPersistenceData(Task)
end
return self
end
+34 -4
View File
@@ -310,7 +310,7 @@ _RECOVERYTANKERID=0
--- Class version.
-- @field #string version
RECOVERYTANKER.version="1.0.10"
RECOVERYTANKER.version="1.0.11"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
Spawn:InitRadioCommsOnOff(true)
Spawn:InitRadioFrequency(self.RadioFreq)
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 on carrier.
@@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
-- Task combo.
-- Be a tanker or be an AWACS.
local taskroll = self.tanker:EnRouteTaskTanker()
local taskrole = self.tanker:EnRouteTaskTanker()
if self.awacs then
taskroll=self.tanker:EnRouteTaskAWACS()
taskrole=self.tanker:EnRouteTaskAWACS()
end
--local taskeplrs=self.tanker:TaskEPLRS(true, 2)
@@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
local taskroute = self.tanker:TaskRoute(wp)
-- 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.
self.tanker:SetTask(taskcombo, 1)
@@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
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.
-- @param #RECOVERYTANKER self
-- @param Core.Event#EVENTDATA EventData Event data.
+29 -21
View File
@@ -20,7 +20,6 @@
-- ===
--
-- ### Author: **funkyfranky**
-- ### Contributions: Flightcontrol (@{AI.AI_Formation} class being used here)
--
-- @module Ops.RescueHelo
-- @image Ops_RescueHelo.png
@@ -37,7 +36,7 @@
-- @field #number takeoff Takeoff type.
-- @field Wrapper.Airbase#AIRBASE airbase The airbase object acting as home base of the helo.
-- @field Core.Set#SET_GROUP followset Follow group set.
-- @field AI.AI_Formation#AI_FORMATION formation AI_FORMATION object.
-- @field Functional.Formation#FORMATION formation FORMATION object.
-- @field #number lowfuel Low fuel threshold of helo in percent.
-- @field #number altitude Altitude of helo in meters.
-- @field #number offsetX Offset in meters to carrier in longitudinal direction.
@@ -235,7 +234,7 @@ _RESCUEHELOID=0
--- Class version.
-- @field #string version
RESCUEHELO.version="1.1.0"
RESCUEHELO.version="1.2.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -248,6 +247,7 @@ RESCUEHELO.version="1.1.0"
-- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse.
-- DONE: Add rescue event when aircraft crashes.
-- DONE: Make offset input parameter.
-- DONE: Make useable for AUFTRAG
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -337,6 +337,9 @@ function RESCUEHELO:New(carrierunit, helogroupname)
self:AddTransition("*", "Status", "*")
self:AddTransition("*", "Stop", "Stopped")
self:I(self.lid.."Started.")
--- Triggers the FSM event "Start" that starts the rescue helo. Initializes parameters and starts event handlers.
-- @function [parent=#RESCUEHELO] Start
@@ -871,15 +874,25 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- Delay before formation is started.
local delay=120
-- 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)
-- Set modex for spawn.
Spawn:InitModex(self.modex)
local UsesAliveGroup=false
local Spawn = GROUP:FindByName(self.helogroupname)
if Spawn and Spawn:IsAlive() then
self.helo=Spawn
UsesAliveGroup = true
delay = 1
else
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
-- Set modex for spawn.
Spawn:InitModex(self.modex)
end
-- Spawn in air or at airbase.
if self.takeoff==SPAWN.Takeoff.Air then
if UsesAliveGroup==false and self.takeoff==SPAWN.Takeoff.Air then
-- Carrier heading
local hdg=self.carrier:GetHeading()
@@ -899,7 +912,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- Start formation in 1 seconds
delay=1
else
elseif UsesAliveGroup==false and self.uncontrolledac then
-- Check if an uncontrolled helo group was requested.
if self.uncontrolledac then
@@ -919,9 +932,9 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- No group of that name!
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname))
return
end
else
end
end
elseif UsesAliveGroup==false then
-- Spawn at airbase.
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
@@ -935,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
delay=60
end
end
end
-- Set of group(s) to follow Mother.
self.followset=SET_GROUP:New()
@@ -947,17 +959,13 @@ function RESCUEHELO:onafterStart(From, Event, To)
self.HeloFuel0=self.helo:GetFuel()
-- Define AI Formation object.
self.formation=AI_FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier", "Follow Carrier at given parameters.")
self.formation=FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier")
-- Formation parameters.
self.formation:FormationCenterWing(-self.offsetX, 50, math.abs(self.altitude), 50, self.offsetZ, 50)
-- Set follow time interval.
self.formation:SetFollowTimeInterval(self.dtFollow)
-- Formation mode.
self.formation:SetFlightModeFormation(self.helo)
-- Start formation FSM.
self.formation:__Start(delay)
+12 -6
View File
@@ -2006,9 +2006,10 @@ end
--- Count alive objects.
-- @param #TARGET self
-- @param #TARGET.Object Target Target objective.
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target objects.
function TARGET:CountObjectives(Target, Coalitions)
function TARGET:CountObjectives(Target, Coalitions, OnlyReallyAlive)
local N=0
@@ -2067,13 +2068,17 @@ function TARGET:CountObjectives(Target, Coalitions)
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
-- This is also more consitent with the life and is alive status.
N=N+1
if not OnlyReallyAlive then
N=N+1
end
elseif Target.Type==TARGET.ObjectType.ZONE then
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
-- This is also more consitent with the life and is alive status.
N=N+1
if not OnlyReallyAlive then
N=N+1
end
elseif Target.Type==TARGET.ObjectType.OPSZONE then
@@ -2093,15 +2098,16 @@ end
--- Count alive targets.
-- @param #TARGET self
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target objects.
function TARGET:CountTargets(Coalitions)
function TARGET:CountTargets(Coalitions, OnlyReallyAlive)
local N=0
for _,_target in pairs(self.targets) do
local Target=_target --#TARGET.Object
N=N+self:CountObjectives(Target, Coalitions)
N=N+self:CountObjectives(Target, Coalitions, OnlyReallyAlive)
end
@@ -1,823 +0,0 @@
--- **Tasking** - A command center governs multiple missions, and takes care of the reporting and communications.
--
-- **Features:**
--
-- * Govern multiple missions.
-- * Communicate to coalitions, groups.
-- * Assign tasks.
-- * Manage the menus.
-- * Manage reference zones.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.CommandCenter
-- @image Task_Command_Center.JPG
--- The COMMANDCENTER class
-- @type COMMANDCENTER
-- @field Wrapper.Group#GROUP HQ
-- @field DCS#coalition CommandCenterCoalition
-- @list<Tasking.Mission#MISSION> Missions
-- @extends Core.Base#BASE
--- Governs multiple missions, the tasking and the reporting.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow.
-- It can assign a random task to a player when requested.
-- The commandcenter provides the facilitites to communicate between human players online, executing a task.
--
-- ## 1. Create a command center object.
--
-- * @{#COMMANDCENTER.New}(): Creates a new COMMANDCENTER object.
--
-- ## 2. Command center mission management.
--
-- Command centers manage missions. These can be added, removed and provides means to retrieve missions.
-- These methods are heavily used by the task dispatcher classes.
--
-- * @{#COMMANDCENTER.AddMission}(): Adds a mission to the commandcenter control.
-- * @{#COMMANDCENTER.RemoveMission}(): Removes a mission to the commandcenter control.
-- * @{#COMMANDCENTER.GetMissions}(): Retrieves the missions table controlled by the commandcenter.
--
-- ## 3. Communication management between players.
--
-- Command center provide means of communication between players.
-- Because a command center is a central object governing multiple missions,
-- there are several levels at which communication needs to be done.
-- Within MOOSE, communication is facilitated using the message system within the DCS simulator.
--
-- Messages can be sent between players at various levels:
--
-- - On a global level, to all players.
-- - On a coalition level, only to the players belonging to the same coalition.
-- - On a group level, to the players belonging to the same group.
--
-- Messages can be sent to **all players** by the command center using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToAll}().
--
-- To send messages to **the coalition of the command center**, there are two methods available:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToCoalition}() to send a specific message to the coalition, with a given message display duration.
-- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToCoalition}().
-- This will send a message of a specific type to the coalition, and as a result its display duration will be flexible according the message display time selection by the human player.
--
-- To send messages **to the group** of human players, there are also two methods available:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToGroup}() to send a specific message to a group, with a given message display duration.
-- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToGroup}().
-- This will send a message of a specific type to the group, and as a result its display duration will be flexible according the message display time selection by the human player .
--
-- Messages are considered to be sometimes disturbing for human players, therefore, the settings menu provides the means to activate or deactivate messages.
-- For more information on the message types and display timings that can be selected and configured using the menu, refer to the @{Core.Settings} menu description.
--
-- ## 4. Command center detailed methods.
--
-- Various methods are added to manage command centers.
--
-- ### 4.1. Naming and description.
--
-- There are 3 methods that can be used to retrieve the description of a command center:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.GetName}() to retrieve the name of the command center.
-- This is the name given as part of the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
-- The returned name using this method, is not to be used for message communication.
--
-- A textual description can be retrieved that provides the command center name to be used within message communication:
--
-- - @{Tasking.CommandCenter#COMMANDCENTER.GetShortText}() returns the command center name as `CC [CommandCenterName]`.
-- - @{Tasking.CommandCenter#COMMANDCENTER.GetText}() returns the command center name as `Command Center [CommandCenterName]`.
--
-- ### 4.2. The coalition of the command center.
--
-- The method @{Tasking.CommandCenter#COMMANDCENTER.GetCoalition}() returns the coalition of the command center.
-- The return value is an enumeration of the type @{DCS#coalition.side}, which contains the RED, BLUE and NEUTRAL coalition.
--
-- ### 4.3. The command center is a real object.
--
-- The command center must be represented by a live object within the DCS simulator. As a result, the command center
-- can be a @{Wrapper.Unit}, a @{Wrapper.Group}, an @{Wrapper.Airbase} or a @{Wrapper.Static} object.
--
-- Using the method @{Tasking.CommandCenter#COMMANDCENTER.GetPositionable}() you retrieve the polymorphic positionable object representing
-- the command center, but just be aware that you should be able to use the representable object derivation methods.
--
-- ### 5. Command center reports.
--
-- Because a command center giverns multiple missions, there are several reports available that are generated by command centers.
-- These reports are generated using the following methods:
--
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportSummary}(): Creates a summary report of all missions governed by the command center.
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportDetails}(): Creates a detailed report of all missions governed by the command center.
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportMissionPlayers}(): Creates a report listing the players active at the missions governed by the command center.
--
-- ## 6. Reference Zones.
--
-- Command Centers may be aware of certain Reference Zones within the battleground. These Reference Zones can refer to
-- known areas, recognizable buildings or sites, or any other point of interest.
-- Command Centers will use these Reference Zones to help pilots with defining coordinates in terms of navigation
-- during the WWII era.
-- The Reference Zones are related to the WWII mode that the Command Center will operate in.
-- Use the method @{#COMMANDCENTER.SetModeWWII}() to set the mode of communication to the WWII mode.
--
-- In WWII mode, the Command Center will receive detected targets, and will select for each target the closest
-- nearby Reference Zone. This allows pilots to navigate easier through the battle field readying for combat.
--
-- The Reference Zones need to be set by the Mission Designer in the Mission Editor.
-- Reference Zones are set by normal trigger zones. One can color the zones in a specific color,
-- and the radius of the zones doesn't matter, only the point is important. Place the center of these Reference Zones at
-- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc).
-- The trigger zones indicating a Reference Zone need to follow a specific syntax.
-- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object,
-- followed by a #, followed by a symbolic name of the Reference Zone.
-- A few examples:
--
-- * A church at Tskinvali would be indicated as: *Church#Tskinvali*
-- * A train station near Kobuleti would be indicated as: *Station#Kobuleti*
--
-- The COMMANDCENTER class contains a method to indicate which trigger zones need to be used as Reference Zones.
-- This is done by using the method @{#COMMANDCENTER.SetReferenceZones}().
-- For the moment, only one Reference Zone class can be specified, but in the future, more classes will become possible.
--
-- ## 7. Tasks.
--
-- ### 7.1. Automatically assign tasks.
--
-- One of the most important roles of the command center is the management of tasks.
-- The command center can assign automatically tasks to the players using the @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAssignTasks}() method.
-- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned.
-- For those players; the tasking is enabled to assign automatically a task.
-- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off.
--
-- ### 7.2. Automatically accept assigned tasks.
--
-- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task.
-- Use the method @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAcceptTasks}() to configure this behaviour.
-- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and
-- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds.
-- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned.
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #COMMANDCENTER
COMMANDCENTER = {
ClassName = "COMMANDCENTER",
CommandCenterName = "",
CommandCenterCoalition = nil,
CommandCenterPositionable = nil,
Name = "",
ReferencePoints = {},
ReferenceNames = {},
CommunicationMode = "80",
}
---
-- @type COMMANDCENTER.AutoAssignMethods
COMMANDCENTER.AutoAssignMethods = {
["Random"] = 1,
["Distance"] = 2,
["Priority"] = 3,
}
--- The constructor takes an IDENTIFIABLE as the HQ command center.
-- @param #COMMANDCENTER self
-- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable
-- @param #string CommandCenterName
-- @return #COMMANDCENTER
function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER
self.CommandCenterPositionable = CommandCenterPositionable
self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName()
self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition()
self.Missions = {}
self:SetAutoAssignTasks( false )
self:SetAutoAcceptTasks( true )
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
self:SetFlashStatus( false )
self:SetMessageDuration(10)
self:HandleEvent( EVENTS.Birth,
-- @param #COMMANDCENTER self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
if EventData.IniObjectCategory == 1 then
local EventGroup = GROUP:Find( EventData.IniDCSGroup )
--self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } )
if EventGroup and EventGroup:IsAlive() and self:HasGroup( EventGroup ) then
local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
--self:ReportSummary( EventGroup )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled!
Mission:JoinUnit( PlayerUnit, PlayerGroup )
end
self:SetMenu()
end
end
end
)
-- -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do.
-- -- For these elements, it will=
-- -- - Set the correct menu.
-- -- - Assign the PlayerUnit to the Task if required.
-- -- - Send a message to the other players in the group that this player has joined.
-- self:HandleEvent( EVENTS.PlayerEnterUnit,
-- -- @param #COMMANDCENTER self
-- -- @param Core.Event#EVENTDATA EventData
-- function( self, EventData )
-- local PlayerUnit = EventData.IniUnit
-- for MissionID, Mission in pairs( self:GetMissions() ) do
-- local Mission = Mission -- Tasking.Mission#MISSION
-- local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled!
-- Mission:JoinUnit( PlayerUnit, PlayerGroup )
-- end
-- self:SetMenu()
-- end
-- )
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.MissionEnd,
-- @param #TASK self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
Mission:Stop()
end
end
)
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.PlayerLeaveUnit,
-- @param #TASK self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:IsENGAGED() then
Mission:AbortUnit( PlayerUnit )
end
end
end
)
-- Handle when a player crashes ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.Crash,
-- @param #TASK self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:IsENGAGED() then
Mission:CrashUnit( PlayerUnit )
end
end
end
)
self:SetMenu()
_SETTINGS:SetSystemMenu( CommandCenterPositionable )
self:SetCommandMenu()
return self
end
--- Gets the name of the HQ command center.
-- @param #COMMANDCENTER self
-- @return #string
function COMMANDCENTER:GetName()
return self.CommandCenterName
end
--- Gets the text string of the HQ command center.
-- @param #COMMANDCENTER self
-- @return #string
function COMMANDCENTER:GetText()
return "Command Center [" .. self.CommandCenterName .. "]"
end
--- Gets the short text string of the HQ command center.
-- @param #COMMANDCENTER self
-- @return #string
function COMMANDCENTER:GetShortText()
return "CC [" .. self.CommandCenterName .. "]"
end
--- Gets the coalition of the command center.
-- @param #COMMANDCENTER self
-- @return #number Coalition of the command center.
function COMMANDCENTER:GetCoalition()
return self.CommandCenterCoalition
end
--- Gets the POSITIONABLE of the HQ command center.
-- @param #COMMANDCENTER self
-- @return Wrapper.Positionable#POSITIONABLE
function COMMANDCENTER:GetPositionable()
return self.CommandCenterPositionable
end
--- Get the Missions governed by the HQ command center.
-- @param #COMMANDCENTER self
-- @return #list<Tasking.Mission#MISSION>
function COMMANDCENTER:GetMissions()
return self.Missions or {}
end
--- Add a MISSION to be governed by the HQ command center.
-- @param #COMMANDCENTER self
-- @param Tasking.Mission#MISSION Mission
-- @return Tasking.Mission#MISSION
function COMMANDCENTER:AddMission( Mission )
self.Missions[Mission] = Mission
return Mission
end
--- Removes a MISSION to be governed by the HQ command center.
-- The given Mission is not nilified.
-- @param #COMMANDCENTER self
-- @param Tasking.Mission#MISSION Mission
-- @return Tasking.Mission#MISSION
function COMMANDCENTER:RemoveMission( Mission )
self.Missions[Mission] = nil
return Mission
end
--- Set special Reference Zones known by the Command Center to guide airborne pilots during WWII.
--
-- These Reference Zones are normal trigger zones, with a special naming.
-- The Reference Zones need to be set by the Mission Designer in the Mission Editor.
-- Reference Zones are set by normal trigger zones. One can color the zones in a specific color,
-- and the radius of the zones doesn't matter, only the center of the zone is important. Place the center of these Reference Zones at
-- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc).
-- The trigger zones indicating a Reference Zone need to follow a specific syntax.
-- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object,
-- followed by a #, followed by a symbolic name of the Reference Zone.
-- A few examples:
--
-- * A church at Tskinvali would be indicated as: *Church#Tskinvali*
-- * A train station near Kobuleti would be indicated as: *Station#Kobuleti*
--
-- Taking the above example, this is how this method would be used:
--
-- CC:SetReferenceZones( "Church" )
-- CC:SetReferenceZones( "Station" )
--
--
-- @param #COMMANDCENTER self
-- @param #string ReferenceZonePrefix The name before the #-mark indicating the class of the Reference Zones.
-- @return #COMMANDCENTER
function COMMANDCENTER:SetReferenceZones( ReferenceZonePrefix )
local MatchPattern = "(.*)#(.*)"
self:F( { MatchPattern = MatchPattern } )
for ReferenceZoneName in pairs( _DATABASE.ZONENAMES ) do
local ZoneName, ReferenceName = string.match( ReferenceZoneName, MatchPattern )
self:F( { ZoneName = ZoneName, ReferenceName = ReferenceName } )
if ZoneName and ReferenceName and ZoneName == ReferenceZonePrefix then
self.ReferencePoints[ReferenceZoneName] = ZONE:New( ReferenceZoneName )
self.ReferenceNames[ReferenceZoneName] = ReferenceName
end
end
return self
end
--- Set the commandcenter operations in WWII mode
-- This will disable LL, MGRS, BRA, BULLS navigatin messages sent by the Command Center,
-- and will be replaced by a navigation using Reference Zones.
-- It will also disable the settings at the settings menu for these.
-- @param #COMMANDCENTER self
-- @return #COMMANDCENTER
function COMMANDCENTER:SetModeWWII()
self.CommunicationMode = "WWII"
return self
end
--- Returns if the commandcenter operations is in WWII mode
-- @param #COMMANDCENTER self
-- @return #boolean true if in WWII mode.
function COMMANDCENTER:IsModeWWII()
return self.CommunicationMode == "WWII"
end
--- Sets the menu structure of the Missions governed by the HQ command center.
-- @param #COMMANDCENTER self
function COMMANDCENTER:SetMenu()
self:F2()
local MenuTime = timer.getTime()
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
local Mission = Mission -- Tasking.Mission#MISSION
Mission:SetMenu( MenuTime )
end
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
Mission = Mission -- Tasking.Mission#MISSION
Mission:RemoveMenu( MenuTime )
end
end
--- Gets the commandcenter menu structure governed by the HQ command center.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#Group TaskGroup Task Group.
-- @return Core.Menu#MENU_COALITION
function COMMANDCENTER:GetMenu( TaskGroup )
local MenuTime = timer.getTime()
self.CommandCenterMenus = self.CommandCenterMenus or {}
local CommandCenterMenu
local CommandCenterText = self:GetText()
CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ):SetTime(MenuTime)
self.CommandCenterMenus[TaskGroup] = CommandCenterMenu
if self.AutoAssignTasks == false then
local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove( MenuTime, "AutoTask" )
return self.CommandCenterMenus[TaskGroup]
end
--- Assigns a random task to a TaskGroup.
-- @param #COMMANDCENTER self
-- @return #COMMANDCENTER
function COMMANDCENTER:AssignTask( TaskGroup )
local Tasks = {}
local AssignPriority = 99999999
local AutoAssignMethod = self.AutoAssignMethod
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
local MissionTasks = Mission:GetGroupTasks( TaskGroup )
for MissionTaskName, MissionTask in pairs( MissionTasks or {} ) do
local MissionTask = MissionTask -- Tasking.Task#TASK
if MissionTask:IsStatePlanned() or MissionTask:IsStateReplanned() or MissionTask:IsStateAssigned() then
local TaskPriority = MissionTask:GetAutoAssignPriority( self.AutoAssignMethod, self, TaskGroup )
if TaskPriority < AssignPriority then
AssignPriority = TaskPriority
Tasks = {}
end
if TaskPriority == AssignPriority then
Tasks[#Tasks+1] = MissionTask
end
end
end
end
local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK
if Task then
self:T( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority )
if not self.AutoAcceptTasks == true then
Task:SetAutoAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) )
end
Task:AssignToGroup( TaskGroup )
end
end
--- Sets the menu of the command center.
-- This command is called within the :New() method.
-- @param #COMMANDCENTER self
function COMMANDCENTER:SetCommandMenu()
local MenuTime = timer.getTime()
if self.CommandCenterPositionable and self.CommandCenterPositionable:IsInstanceOf(GROUP) then
local CommandCenterText = self:GetText()
local CommandCenterMenu = MENU_GROUP:New( self.CommandCenterPositionable, CommandCenterText ):SetTime(MenuTime)
if self.AutoAssignTasks == false then
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask")
else
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove( MenuTime, "AutoTask" )
end
end
--- Automatically assigns tasks to all TaskGroups.
-- One of the most important roles of the command center is the management of tasks.
-- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned.
-- For those players; the tasking is enabled to assign automatically a task.
-- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off.
-- @param #COMMANDCENTER self
-- @param #boolean AutoAssign true for ON and false or nil for OFF.
function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false
if self.AutoAssignTasks == true then
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop()
-- FF this is not the schedule ID
--self:ScheduleStop( self.AssignTasks )
end
end
--- Automatically accept tasks for all TaskGroups.
-- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task.
-- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and
-- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds.
-- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned.
-- @param #COMMANDCENTER self
-- @param #boolean AutoAccept true for ON and false or nil for OFF.
function COMMANDCENTER:SetAutoAcceptTasks( AutoAccept )
self.AutoAcceptTasks = AutoAccept or false
end
--- Define the method to be used to assign automatically a task from the available tasks in the mission.
-- There are 3 types of methods that can be applied for the moment:
--
-- 1. Random - assigns a random task in the mission to the player.
-- 2. Distance - assigns a task based on a distance evaluation from the player. The closest are to be assigned first.
-- 3. Priority - assigns a task based on the priority as defined by the mission designer, using the SetTaskPriority parameter.
--
-- The different task classes implement the logic to determine the priority of automatic task assignment to a player, depending on one of the above methods.
-- The method @{Tasking.Task#TASK.GetAutoAssignPriority} calculate the priority of the tasks to be assigned.
-- @param #COMMANDCENTER self
-- @param #COMMANDCENTER.AutoAssignMethods AutoAssignMethod A selection of an assign method from the COMMANDCENTER.AutoAssignMethods enumeration.
function COMMANDCENTER:SetAutoAssignMethod( AutoAssignMethod )
self.AutoAssignMethod = AutoAssignMethod or COMMANDCENTER.AutoAssignMethods.Random
end
--- Automatically assigns tasks to all TaskGroups.
-- @param #COMMANDCENTER self
function COMMANDCENTER:AssignTasks()
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() then
self:GetMenu( TaskGroup )
if self:IsGroupAssigned( TaskGroup ) then
else
-- Only groups with planes or helicopters will receive automatic tasks.
-- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696
if TaskGroup:IsAir() then
self:AssignTask( TaskGroup )
end
end
end
end
end
--- Get all the Groups active within the command center.
-- @param #COMMANDCENTER self
-- @return Core.Set#SET_GROUP The set of groups active within the command center.
function COMMANDCENTER:AddGroups()
local GroupSet = SET_GROUP:New()
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
GroupSet = Mission:AddGroups( GroupSet )
end
return GroupSet
end
--- Checks of the TaskGroup has a Task.
-- @param #COMMANDCENTER self
-- @return #boolean When true, the TaskGroup has a Task, otherwise the returned value will be false.
function COMMANDCENTER:IsGroupAssigned( TaskGroup )
local Assigned = false
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:IsGroupAssigned( TaskGroup ) then
Assigned = true
break
end
end
return Assigned
end
--- Checks of the command center has the given MissionGroup.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP MissionGroup The group active within one of the missions governed by the command center.
-- @return #boolean
function COMMANDCENTER:HasGroup( MissionGroup )
local Has = false
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:HasGroup( MissionGroup ) then
Has = true
break
end
end
return Has
end
--- Let the command center send a Message to all players.
-- @param #COMMANDCENTER self
-- @param #string Message The message text.
function COMMANDCENTER:MessageToAll( Message )
self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
end
--- Let the command center send a message to the MessageGroup.
-- @param #COMMANDCENTER self
-- @param #string Message The message text.
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
end
--- Let the command center send a message to the MessageGroup.
-- @param #COMMANDCENTER self
-- @param #string Message The message text.
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToGroup( Message, MessageGroup, MessageType )
self:GetPositionable():MessageTypeToGroup( Message, MessageType, MessageGroup, self:GetShortText() )
end
--- Let the command center send a message to the coalition of the command center.
-- @param #COMMANDCENTER self
-- @param #string Message The message text.
function COMMANDCENTER:MessageToCoalition( Message )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
end
--- Let the command center send a message of a specified type to the coalition of the command center.
-- @param #COMMANDCENTER self
-- @param #string Message The message text.
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() )
end
--- Let the command center send a report of the status of all missions to a group.
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP ReportGroup The group to receive the report.
function COMMANDCENTER:ReportSummary( ReportGroup )
self:F( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetName()
Report:Add( string.format( '%s - Report Summary Missions', Name ) )
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
Report:Add( " - " .. Mission:ReportSummary( ReportGroup ) )
end
self:MessageToGroup( Report:Text(), ReportGroup )
end
--- Let the command center send a report of the players of all missions to a group.
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP ReportGroup The group to receive the report.
function COMMANDCENTER:ReportMissionsPlayers( ReportGroup )
self:F( ReportGroup )
local Report = REPORT:New()
Report:Add( "Players active in all missions." )
for MissionID, MissionData in pairs( self.Missions ) do
local Mission = MissionData -- Tasking.Mission#MISSION
Report:Add( " - " .. Mission:ReportPlayersPerTask(ReportGroup) )
end
self:MessageToGroup( Report:Text(), ReportGroup )
end
--- Let the command center send a report of the status of a task to a group.
-- Report the details of a Mission, listing the Mission, and all the Task details.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP ReportGroup The group to receive the report.
-- @param Tasking.Task#TASK Task The task to be reported.
function COMMANDCENTER:ReportDetails( ReportGroup, Task )
self:F( ReportGroup )
local Report = REPORT:New()
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
Report:Add( " - " .. Mission:ReportDetails() )
end
self:MessageToGroup( Report:Text(), ReportGroup )
end
--- Let the command center flash a report of the status of the subscribed task to a group.
-- @param #COMMANDCENTER self
-- @param Flash #boolean
function COMMANDCENTER:SetFlashStatus( Flash )
self:F()
self.FlashStatus = Flash and true
end
--- Duration a command center message is shown.
-- @param #COMMANDCENTER self
-- @param seconds #number
function COMMANDCENTER:SetMessageDuration(seconds)
self:F()
self.MessageDuration = 10 or seconds
end
@@ -1,404 +0,0 @@
--- **Tasking** - This module contains the DETECTION_MANAGER class and derived classes.
--
-- ===
--
-- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups.
-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- 1.1) DETECTION_MANAGER constructor:
-- -----------------------------------
-- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance.
--
-- 1.2) DETECTION_MANAGER reporting:
-- ---------------------------------
-- Derived DETECTION_MANAGER classes will reports detected units using the method @{#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour.
--
-- The time interval in seconds of the reporting can be changed using the methods @{#DETECTION_MANAGER.SetRefreshTimeInterval}().
-- To control how long a reporting message is displayed, use @{#DETECTION_MANAGER.SetReportDisplayTime}().
-- Derived classes need to implement the method @{#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report.
--
-- Reporting can be started and stopped using the methods @{#DETECTION_MANAGER.StartReporting}() and @{#DETECTION_MANAGER.StopReporting}() respectively.
-- If an ad-hoc report is requested, use the method @{#DETECTION_MANAGER.ReportNow}().
--
-- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds.
--
-- ===
--
-- 2) @{#DETECTION_REPORTING} class, extends @{#DETECTION_MANAGER}
-- ===
-- The @{#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class.
--
-- 2.1) DETECTION_REPORTING constructor:
-- -------------------------------
-- The @{#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance.
--
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- ===
--
-- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing
-- ### Author: FlightControl - Framework Design & Programming
--
-- @module Tasking.DetectionManager
-- @image Task_Detection_Manager.JPG
do -- DETECTION MANAGER
-- @type DETECTION_MANAGER
-- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects.
-- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players.
-- @extends Core.Fsm#FSM
--- DETECTION_MANAGER class.
-- @field #DETECTION_MANAGER
DETECTION_MANAGER = {
ClassName = "DETECTION_MANAGER",
SetGroup = nil,
Detection = nil,
}
-- @field Tasking.CommandCenter#COMMANDCENTER
DETECTION_MANAGER.CC = nil
--- FAC constructor.
-- @param #DETECTION_MANAGER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Functional.Detection#DETECTION_BASE Detection
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:New( SetGroup, Detection )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_MANAGER
self.SetGroup = SetGroup
self.Detection = Detection
self:SetStartState( "Stopped" )
self:AddTransition( "Stopped", "Start", "Started" )
--- Start Handler OnBefore for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnBeforeStart
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Start Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterStart
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Start Trigger for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] Start
-- @param #DETECTION_MANAGER self
--- Start Asynchronous Trigger for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] __Start
-- @param #DETECTION_MANAGER self
-- @param #number Delay
self:AddTransition( "Started", "Stop", "Stopped" )
--- Stop Handler OnBefore for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnBeforeStop
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Stop Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterStop
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Stop Trigger for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] Stop
-- @param #DETECTION_MANAGER self
--- Stop Asynchronous Trigger for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] __Stop
-- @param #DETECTION_MANAGER self
-- @param #number Delay
self:AddTransition( "Started", "Success", "Started" )
--- Success Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterSuccess
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Failed", "Started" )
--- Failed Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterFailed
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Aborted", "Started" )
--- Aborted Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterAborted
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Cancelled", "Started" )
--- Cancelled Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterCancelled
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Report", "Started" )
self:SetRefreshTimeInterval( 30 )
self:SetReportDisplayTime( 25 )
Detection:__Start( 3 )
return self
end
function DETECTION_MANAGER:onafterStart( From, Event, To )
self:Report()
end
function DETECTION_MANAGER:onafterReport( From, Event, To )
self:__Report( -self._RefreshTimeInterval )
self:ProcessDetected( self.Detection )
end
--- Set the reporting time interval.
-- @param #DETECTION_MANAGER self
-- @param #number RefreshTimeInterval The interval in seconds when a report needs to be done.
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval )
self:F2()
self._RefreshTimeInterval = RefreshTimeInterval
end
--- Set the reporting message display time.
-- @param #DETECTION_MANAGER self
-- @param #number ReportDisplayTime The display time in seconds when a report needs to be done.
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime )
self:F2()
self._ReportDisplayTime = ReportDisplayTime
end
--- Get the reporting message display time.
-- @param #DETECTION_MANAGER self
-- @return #number ReportDisplayTime The display time in seconds when a report needs to be done.
function DETECTION_MANAGER:GetReportDisplayTime()
self:F2()
return self._ReportDisplayTime
end
--- Set a command center to communicate actions to the players reporting to the command center.
-- @param #DETECTION_MANAGER self
-- @return #DETECTION_MANGER self
function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText )
local DispatcherMainMenu = MENU_MISSION:New( DispatcherMainMenuText, nil )
local DispatcherMenu = MENU_MISSION_COMMAND:New( DispatcherMenuText, DispatcherMainMenu,
function()
self:ShowTacticalDisplay( self.Detection )
end
)
return self
end
--- Set a command center to communicate actions to the players reporting to the command center.
-- @param #DETECTION_MANAGER self
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @return #DETECTION_MANGER self
function DETECTION_MANAGER:SetCommandCenter( CommandCenter )
self.CC = CommandCenter
return self
end
--- Get the command center to communicate actions to the players.
-- @param #DETECTION_MANAGER self
-- @return Tasking.CommandCenter#COMMANDCENTER The command center.
function DETECTION_MANAGER:GetCommandCenter()
return self.CC
end
--- Send an information message to the players reporting to the command center.
-- @param #DETECTION_MANAGER self
-- @param #table Squadron The squadron table.
-- @param #string Message The message to be sent.
-- @param #string SoundFile The name of the sound file .wav or .ogg.
-- @param #number SoundDuration The duration of the sound.
-- @param #string SoundPath The path pointing to the folder in the mission file.
-- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message.
-- @return #DETECTION_MANGER self
function DETECTION_MANAGER:MessageToPlayers( Squadron, Message, DefenderGroup )
self:F( { Message = Message } )
-- if not self.PreviousMessage or self.PreviousMessage ~= Message then
-- self.PreviousMessage = Message
-- if self.CC then
-- self.CC:MessageToCoalition( Message )
-- end
-- end
if self.CC then
self.CC:MessageToCoalition( Message )
end
Message = Message:gsub( "°", " degrees " )
Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" )
-- Here we handle the transmission of the voice over.
-- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from.
local RadioQueue = Squadron.RadioQueue -- Core.RadioSpeech#RADIOSPEECH
if RadioQueue then
local DefenderUnit = DefenderGroup:GetUnit(1)
if DefenderUnit and DefenderUnit:IsAlive() then
RadioQueue:SetSenderUnitName( DefenderUnit:GetName() )
end
RadioQueue:Speak( Message, Squadron.Language )
end
return self
end
--- Reports the detected items to the @{Core.Set#SET_GROUP}.
-- @param #DETECTION_MANAGER self
-- @param Functional.Detection#DETECTION_BASE Detection
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:ProcessDetected( Detection )
end
end
do -- DETECTION_REPORTING
--- DETECTION_REPORTING class.
-- @type DETECTION_REPORTING
-- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects.
-- @extends #DETECTION_MANAGER
DETECTION_REPORTING = {
ClassName = "DETECTION_REPORTING",
}
--- DETECTION_REPORTING constructor.
-- @param #DETECTION_REPORTING self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Functional.Detection#DETECTION_AREAS Detection
-- @return #DETECTION_REPORTING self
function DETECTION_REPORTING:New( SetGroup, Detection )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING
self:Schedule( 1, 30 )
return self
end
--- Creates a string of the detected items in a @{Functional.Detection} object.
-- @param #DETECTION_MANAGER self
-- @param Core.Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object.
-- @return #DETECTION_MANAGER self
function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet )
self:F2()
local MT = {} -- Message Text
local UnitTypes = {}
for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do
local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT
if DetectedUnit:IsAlive() then
local UnitType = DetectedUnit:GetTypeName()
if not UnitTypes[UnitType] then
UnitTypes[UnitType] = 1
else
UnitTypes[UnitType] = UnitTypes[UnitType] + 1
end
end
end
for UnitTypeID, UnitType in pairs( UnitTypes ) do
MT[#MT+1] = UnitType .. " of " .. UnitTypeID
end
return table.concat( MT, ", " )
end
--- Reports the detected items to the @{Core.Set#SET_GROUP}.
-- @param #DETECTION_REPORTING self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} object to where the report needs to go.
-- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object.
-- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop.
function DETECTION_REPORTING:ProcessDetected( Group, Detection )
self:F2( Group )
local DetectedMsg = {}
for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do
local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea
DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set )
end
local FACGroup = Detection:GetDetectionGroups()
FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group )
return true
end
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,379 +0,0 @@
--- **Tasking** - Controls the information of a Task.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.TaskInfo
-- @image MOOSE.JPG
---
-- @type TASKINFO
-- @extends Core.Base#BASE
---
-- # TASKINFO class, extends @{Core.Base#BASE}
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ## The TASKINFO class implements the methods to contain information and display information of a task.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #TASKINFO
TASKINFO = {
ClassName = "TASKINFO",
}
---
-- @type TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report.
--
-- - "M" for Markings on the Map (F10).
-- - "S" for Summary Reports.
-- - "O" for Overview Reports.
-- - "D" for Detailed Reports.
TASKINFO.Detail = ""
--- Instantiates a new TASKINFO.
-- @param #TASKINFO self
-- @param Tasking.Task#TASK Task The task owning the information.
-- @return #TASKINFO self
function TASKINFO:New( Task )
local self = BASE:Inherit( self, BASE:New() ) -- Core.Base#BASE
self.Task = Task
self.VolatileInfo = SET_BASE:New()
self.PersistentInfo = SET_BASE:New()
self.Info = self.VolatileInfo
return self
end
--- Add taskinfo.
-- @param #TASKINFO self
-- @param #string Key The info key.
-- @param Data The data of the info.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep, ShowKey, Type )
self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } )
if Keep == true then
self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } )
end
return self
end
--- Get taskinfo.
-- @param #TASKINFO self
-- @param #string The info key.
-- @return Data The data of the info.
-- @return #number Order The display order, which is a number from 0 to 100.
-- @return #TASKINFO.Detail Detail The detail Level.
function TASKINFO:GetInfo( Key )
local Object = self:Get( Key )
return Object.Data, Object.Order, Object.Detail
end
--- Get data.
-- @param #TASKINFO self
-- @param #string The info key.
-- @return Data The data of the info.
function TASKINFO:GetData( Key )
local Object = self.Info:Get( Key )
return Object and Object.Data
end
--- Add Text.
-- @param #TASKINFO self
-- @param #string Key The key.
-- @param #string Text The text.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddText( Key, Text, Order, Detail, Keep )
self:AddInfo( Key, Text, Order, Detail, Keep )
return self
end
--- Add the task name.
-- @param #TASKINFO self
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTaskName( Order, Detail, Keep )
self:AddInfo( "TaskName", self.Task:GetName(), Order, Detail, Keep )
return self
end
--- Add a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep, ShowKey, Name )
self:AddInfo( Name or "Coordinate", Coordinate, Order, Detail, Keep, ShowKey, "Coordinate" )
return self
end
--- Get the Coordinate.
-- @param #TASKINFO self
-- @return Core.Point#COORDINATE Coordinate
function TASKINFO:GetCoordinate( Name )
return self:GetData( Name or "Coordinate" )
end
--- Add Coordinates.
-- @param #TASKINFO self
-- @param #list<Core.Point#COORDINATE> Coordinates
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCoordinates( Coordinates, Order, Detail, Keep )
self:AddInfo( "Coordinates", Coordinates, Order, Detail, Keep )
return self
end
--- Add Threat.
-- @param #TASKINFO self
-- @param #string ThreatText The text of the Threat.
-- @param #string ThreatLevel The level of the Threat.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddThreat( ThreatText, ThreatLevel, Order, Detail, Keep )
self:AddInfo( "Threat", " [" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]:" .. ThreatText, Order, Detail, Keep )
return self
end
--- Get Threat.
-- @param #TASKINFO self
-- @return #string The threat
function TASKINFO:GetThreat()
self:GetInfo( "Threat" )
return self
end
--- Add the Target count.
-- @param #TASKINFO self
-- @param #number TargetCount The amount of targets.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTargetCount( TargetCount, Order, Detail, Keep )
self:AddInfo( "Counting", string.format( "%d", TargetCount ), Order, Detail, Keep )
return self
end
--- Add the Targets.
-- @param #TASKINFO self
-- @param #number TargetCount The amount of targets.
-- @param #string TargetTypes The text containing the target types.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTargets( TargetCount, TargetTypes, Order, Detail, Keep )
self:AddInfo( "Targets", string.format( "%d of %s", TargetCount, TargetTypes ), Order, Detail, Keep )
return self
end
--- Get Targets.
-- @param #TASKINFO self
-- @return #string The targets
function TASKINFO:GetTargets()
self:GetInfo( "Targets" )
return self
end
--- Add the QFE at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddQFEAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "QFE", Coordinate, Order, Detail, Keep )
return self
end
--- Add the Temperature at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTemperatureAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "Temperature", Coordinate, Order, Detail, Keep )
return self
end
--- Add the Wind at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddWindAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "Wind", Coordinate, Order, Detail, Keep )
return self
end
--- Add Cargo.
-- @param #TASKINFO self
-- @param Cargo.Cargo#CARGO Cargo
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCargo( Cargo, Order, Detail, Keep )
self:AddInfo( "Cargo", Cargo, Order, Detail, Keep )
return self
end
--- Add Cargo set.
-- @param #TASKINFO self
-- @param Core.Set#SET_CARGO SetCargo
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep )
local CargoReport = REPORT:New()
CargoReport:Add( "" )
SetCargo:ForEachCargo(
-- @param Cargo.Cargo#CARGO Cargo
function( Cargo )
CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) )
end
)
self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep )
return self
end
--- Create the taskinfo Report
-- @param #TASKINFO self
-- @param Core.Report#REPORT Report
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param Wrapper.Group#GROUP ReportGroup
-- @param Tasking.Task#TASK Task
-- @return #TASKINFO self
function TASKINFO:Report( Report, Detail, ReportGroup, Task )
local Line = 0
local LineReport = REPORT:New()
if not self.Task:IsStatePlanned() and not self.Task:IsStateAssigned() then
self.Info = self.PersistentInfo
end
for Key, Data in UTILS.spairs( self.Info.Set, function( t, a, b ) return t[a].Order < t[b].Order end ) do
if Data.Detail:find( Detail ) then
local Text = ""
local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true )
if Key == "TaskName" then
Key = nil
Text = Data.Data
elseif Data.Type and Data.Type == "Coordinate" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task )
elseif Key == "Threat" then
local DataText = Data.Data -- #string
Text = DataText
elseif Key == "Counting" then
local DataText = Data.Data -- #string
Text = DataText
elseif Key == "Targets" then
local DataText = Data.Data -- #string
Text = DataText
elseif Key == "QFE" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringPressure( ReportGroup:GetUnit(1), nil, Task )
elseif Key == "Temperature" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringTemperature( ReportGroup:GetUnit(1), nil, Task )
elseif Key == "Wind" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, Task )
elseif Key == "Cargo" then
local DataText = Data.Data -- #string
Text = DataText
elseif Key == "Friendlies" then
local DataText = Data.Data -- #string
Text = DataText
elseif Key == "Players" then
local DataText = Data.Data -- #string
Text = DataText
else
local DataText = Data.Data -- #string
if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string
Text = DataText
end
end
if Line < math.floor( Data.Order / 10 ) then
if Line == 0 then
Report:AddIndent( LineReport:Text( ", " ), "-" )
else
Report:AddIndent( LineReport:Text( ", " ) )
end
LineReport = REPORT:New()
Line = math.floor( Data.Order / 10 )
end
if Text ~= "" then
LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text )
end
end
end
Report:AddIndent( LineReport:Text( ", " ) )
end
@@ -1,657 +0,0 @@
--- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.Task_A2A
-- @image MOOSE.JPG
do -- TASK_A2A
--- The TASK_A2A class
-- @type TASK_A2A
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines Air To Air tasks for a @{Core.Set} of Target Units,
-- based on the tasking capabilities defined in @{Tasking.Task#TASK}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The TASK_A2A is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
--
-- * **None**: Start of the process
-- * **Planned**: The A2A task is planned.
-- * **Assigned**: The A2A task is assigned to a @{Wrapper.Group#GROUP}.
-- * **Success**: The A2A task is successfully completed.
-- * **Failed**: The A2A task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
--
-- # 1) Set the scoring of achievements in an A2A attack.
--
-- Scoring or penalties can be given in the following circumstances:
--
-- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed.
-- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed.
-- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed.
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #TASK_A2A
TASK_A2A = {
ClassName = "TASK_A2A"
}
--- Instantiates a new TASK_A2A.
-- @param #TASK_A2A self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT UnitSetTargets
-- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range.
-- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known.
-- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be.
-- @return #TASK_A2A self
function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A
self:F()
self.TargetSetUnit = TargetSetUnit
self.TaskType = TaskType
local Fsm = self:GetUnitProcess()
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #TASK_CARGO Task
function Fsm:OnLeaveAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:SelectAction()
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit
if Task:GetRendezVousZone( TaskUnit ) then
self:__RouteToRendezVousZone( 0.1 )
else
if Task:GetRendezVousCoordinate( TaskUnit ) then
self:__RouteToRendezVousPoint( 0.1 )
else
self:__ArriveAtRendezVous( 0.1 )
end
end
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
self:__Engage( 0.1 )
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task
function Fsm:onafterEngage( TaskUnit, Task )
self:F( { self } )
self:__Account( 0.1 )
self:__RouteToTarget( 0.1 )
self:__RouteToTargets( -10 )
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToTarget( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
self:__RouteToTargetZone( 0.1 )
else
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
local Coordinate = TargetUnit:GetPointVec3()
self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetAlt(), Coordinate:GetZ() } )
Task:SetTargetCoordinate( Coordinate, TaskUnit )
end
self:__RouteToTargetPoint( 0.1 )
end
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToTargets( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit )
end
self:__RouteToTargets( -10 )
end
return self
end
-- @param #TASK_A2A self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2A:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit
end
-- @param #TASK_A2A self
function TASK_A2A:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
end
-- @param #TASK_A2A self
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
ActRouteRendezVous:SetRange( RendezVousRange )
end
-- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
function TASK_A2A:GetRendezVousCoordinate( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
end
-- @param #TASK_A2A self
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteRendezVous:SetZone( RendezVousZone )
end
-- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map.
function TASK_A2A:GetRendezVousZone( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteRendezVous:GetZone()
end
-- @param #TASK_A2A self
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteTarget:SetCoordinate( TargetCoordinate )
end
-- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map.
function TASK_A2A:GetTargetCoordinate( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
return ActRouteTarget:GetCoordinate()
end
-- @param #TASK_A2A self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteTarget:SetZone( TargetZone, Altitude, Heading )
end
-- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
function TASK_A2A:GetTargetZone( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteTarget:GetZone()
end
function TASK_A2A:SetGoalTotal()
self.GoalTotal = self.TargetSetUnit:Count()
end
function TASK_A2A:GetGoalTotal()
return self.GoalTotal
end
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
-- @param #TASK_A2A self
function TASK_A2A:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- This method checks every 10 seconds if the goal has been reached of the task.
-- @param #TASK_A2A self
function TASK_A2A:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
-- @param #TASK_A2A self
function TASK_A2A:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName( 0, "MSOD" )
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
local ThreatLevel, ThreatText
if DetectedItem then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
end
end
end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_A2A self
-- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
return math.floor( Distance )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end
do -- TASK_A2A_INTERCEPT
--- The TASK_A2A_INTERCEPT class
-- @type TASK_A2A_INTERCEPT
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines an intercept task for a human player to be executed.
-- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there!
--
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
-- based on detected airborne enemy targets intruding friendly airspace.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_INTERCEPT
TASK_A2A_INTERCEPT = {
ClassName = "TASK_A2A_INTERCEPT"
}
--- Instantiates a new TASK_A2A_INTERCEPT.
-- @param #TASK_A2A_INTERCEPT self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT
self:F()
Mission:AddTask( self )
self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" )
return self
end
--- Set a score when a target in scope of the A2A attack, has been destroyed.
-- @param #TASK_A2A_INTERCEPT self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:SetScoreOnProgress( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score )
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
-- @param #TASK_A2A_INTERCEPT self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:SetScoreOnSuccess( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score )
return self
end
--- Set a penalty when the A2A attack has failed.
-- @param #TASK_A2A_INTERCEPT self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:SetScoreOnFail( PlayerName, Penalty, TaskUnit )
self:F( { PlayerName, Penalty, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty )
return self
end
end
do -- TASK_A2A_SWEEP
--- The TASK_A2A_SWEEP class
-- @type TASK_A2A_SWEEP
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines a sweep task for a human player to be executed.
-- A sweep task needs to be given when targets were detected but somehow the detection was lost.
-- Most likely, these enemy planes are hidden in the mountains or are flying under radar.
-- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters.
--
-- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
-- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_SWEEP
TASK_A2A_SWEEP = {
ClassName = "TASK_A2A_SWEEP"
}
--- Instantiates a new TASK_A2A_SWEEP.
-- @param #TASK_A2A_SWEEP self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP
self:F()
Mission:AddTask( self )
self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" )
return self
end
-- @param #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed.
-- @param #TASK_A2A_SWEEP self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_SWEEP
function TASK_A2A_SWEEP:SetScoreOnProgress( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score )
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
-- @param #TASK_A2A_SWEEP self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_SWEEP
function TASK_A2A_SWEEP:SetScoreOnSuccess( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score )
return self
end
--- Set a penalty when the A2A attack has failed.
-- @param #TASK_A2A_SWEEP self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_SWEEP
function TASK_A2A_SWEEP:SetScoreOnFail( PlayerName, Penalty, TaskUnit )
self:F( { PlayerName, Penalty, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty )
return self
end
end
do -- TASK_A2A_ENGAGE
--- The TASK_A2A_ENGAGE class
-- @type TASK_A2A_ENGAGE
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines an engage task for a human player to be executed.
-- When enemy planes are close to human players, use this task type is used urge the players to get out there!
--
-- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
-- based on detected airborne enemy targets intruding friendly airspace.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_ENGAGE
TASK_A2A_ENGAGE = {
ClassName = "TASK_A2A_ENGAGE"
}
--- Instantiates a new TASK_A2A_ENGAGE.
-- @param #TASK_A2A_ENGAGE self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_ENGAGE self
function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE
self:F()
Mission:AddTask( self )
self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" )
return self
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
-- @param #TASK_A2A_ENGAGE self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_ENGAGE
function TASK_A2A_ENGAGE:SetScoreOnProgress( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score )
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
-- @param #TASK_A2A_ENGAGE self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_ENGAGE
function TASK_A2A_ENGAGE:SetScoreOnSuccess( PlayerName, Score, TaskUnit )
self:F( { PlayerName, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score )
return self
end
--- Set a penalty when the A2A attack has failed.
-- @param #TASK_A2A_ENGAGE self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_A2A_ENGAGE
function TASK_A2A_ENGAGE:SetScoreOnFail( PlayerName, Penalty, TaskUnit )
self:F( { PlayerName, Penalty, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty )
return self
end
end
@@ -1,622 +0,0 @@
--- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network.
--
-- **Features:**
--
-- * Dynamically assign tasks to human players based on detected targets.
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
-- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP.
-- * Dynamically assign (GCI) Ground Control Intercept tasks for human players to perform GCI.
-- * Dynamically assign Engage tasks for human players to engage on close-by airborne bogeys.
-- * Define and use an EWR (Early Warning Radar) network.
-- * Define different ranges to engage upon intruders.
-- * Keep task achievements.
-- * Score task achievements.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.Task_A2A_Dispatcher
-- @image Task_A2A_Dispatcher.JPG
do -- TASK_A2A_DISPATCHER
--- TASK_A2A_DISPATCHER class.
-- @type TASK_A2A_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Core.Set} of EWR installation groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG)
--
-- The EWR will detect units, will group them, and will dispatch @{Tasking.Task}s to groups. Depending on the type of target detected, different tasks will be dispatched.
-- Find a summary below describing for which situation a task type is created:
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG)
--
-- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range.
-- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range.
-- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task.
--
-- ## 1. TASK\_A2A\_DISPATCHER constructor:
--
-- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance.
--
-- ### 1.1. Define or set the **Mission**:
--
-- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter.
--
-- local HQ = GROUP:FindByName( "HQ", "Bravo" )
-- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" )
-- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED )
--
-- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission.
-- Create the MISSION object, and hook it under the command center.
--
-- ### 1.2. Build a set of the groups seated by human players:
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG)
--
-- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into.
--
-- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart()
--
-- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission.
-- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available.
--
-- ### 1.3. Define the **EWR network**:
--
-- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter.
-- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG)
--
-- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units.
-- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US).
-- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar.
-- The position of these units is very important as they need to provide enough coverage
-- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG)
--
-- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates.
-- For example if they are a long way forward and can detect enemy planes on the ground and taking off
-- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition.
-- Having the radars further back will mean a slower escalation because fewer targets will be detected and
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
-- It all depends on what the desired effect is.
--
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
-- increasing or decreasing the radar coverage of the Early Warning System.
--
-- See the following example to setup an EWR network containing EWR stations and AWACS.
--
-- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart()
--
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
-- EWRDetection:SetFriendliesRange( 10000 )
-- EWRDetection:SetRefreshTimeInterval(30)
--
-- -- Setup the A2A dispatcher, and initialize it.
-- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
--
-- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**.
-- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set.
-- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km.
-- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism.
--
-- ### 2. Define the detected **target grouping radius**:
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG)
--
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.
-- Fast planes like in the 80s, need a larger radius than WWII planes.
-- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft.
--
-- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate
-- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!
--
-- ## 3. Set the **Engage radius**:
--
-- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG)
--
-- So, if there is a target area detected and reported,
-- then any friendlies that are airborne near this target area,
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
-- will be considered to receive the command to engage that target area.
-- You need to evaluate the value of this parameter carefully.
-- If too small, more intercept missions may be triggered upon detected target areas.
-- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
--
-- ## 4. Set **Scoring** and **Messages**:
--
-- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Tasking.Task} dispatched by the TASK\_A2A\_DISPATCHER.
-- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_,
-- when the player reaches certain achievements in the task.
--
-- The prototype to handle the **Assign** event needs to be developed as follows:
--
-- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... )
--
-- -- @param #TaskDispatcher self
-- -- @param #string From Contains the name of the state from where the Event was triggered.
-- -- @param #string Event Contains the name of the event that was triggered. In this case Assign.
-- -- @param #string To Contains the name of the state that will be transitioned to.
-- -- @param Tasking.Task_A2A#TASK_A2A Task The Task object, which is any derived object from TASK_A2A.
-- -- @param Wrapper.Unit#UNIT TaskUnit The Unit or Client that contains the Player.
-- -- @param #string PlayerName The name of the Player that joined the TaskUnit.
-- function TaskDispatcher:OnAfterAssign( From, Event, To, Task, TaskUnit, PlayerName )
-- Task:SetScoreOnProgress( PlayerName, 20, TaskUnit )
-- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit )
-- Task:SetScoreOnFail( PlayerName, -100, TaskUnit )
-- end
--
-- The **OnAfterAssign** method (function) is added to the TaskDispatcher object.
-- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher.
-- So, this method will be called only **ONCE** when a player joins a unit in scope of the task.
--
-- The TASK class implements various methods to additional **set scoring** for player achievements:
--
-- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task.
-- Examples of **task progress** can be destroying units, arriving at zones etc.
--
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state.
-- This means the **task has been successfully completed**.
--
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state.
-- This means the **task has not been successfully completed**, and the scores must be given with a negative value!
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--
-- @field #TASK_A2A_DISPATCHER
TASK_A2A_DISPATCHER = {
ClassName = "TASK_A2A_DISPATCHER",
Mission = nil,
Detection = nil,
Tasks = {},
SweepZones = {},
}
--- TASK_A2A_DISPATCHER constructor.
-- @param #TASK_A2A_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
-- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission.
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
-- @return #TASK_A2A_DISPATCHER self
function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER
self.Detection = Detection
self.Mission = Mission
self.FlashNewTask = false
-- TODO: Check detection through radar.
self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER )
self.Detection:InitDetectRadar( true )
self.Detection:SetRefreshTimeInterval( 30 )
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign
-- @param #TASK_A2A_DISPATCHER self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Tasking.Task_A2A#TASK_A2A Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName
self:__Start( 5 )
return self
end
--- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission.
-- So, if there is a target area detected and reported,
-- then any friendlies that are airborne near this target area,
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
-- An ENGAGE task will be created for those pilots.
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
-- will be considered to receive the command to engage that target area.
-- You need to evaluate the value of this parameter carefully.
-- If too small, more intercept missions may be triggered upon detected target areas.
-- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
-- @param #TASK_A2A_DISPATCHER self
-- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target.
-- @return #TASK_A2A_DISPATCHER
-- @usage
--
-- -- Set 50km as the radius to engage any target by airborne friendlies.
-- TaskA2ADispatcher:SetEngageRadius( 50000 )
--
-- -- Set 100km as the radius to engage any target by airborne friendlies.
-- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value.
--
function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius )
self.Detection:SetFriendliesRange( EngageRadius or 100000 )
return self
end
--- Set flashing player messages on or off
-- @param #TASK_A2A_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function TASK_A2A_DISPATCHER:SetSendMessages( onoff )
self.FlashNewTask = onoff
end
--- Creates an INTERCEPT task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone
-- Check if there is at least one UNIT in the DetectedSet is visible.
if DetectedItem.IsDetected == true then
-- Here we're doing something advanced... We're copying the DetectedSet.
local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
--- Creates an SWEEP task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
if DetectedItem.IsDetected == false then
-- Here we're doing something advanced... We're copying the DetectedSet.
local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
--- Creates an ENGAGE task when there are human friendlies airborne near the targets.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
-- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone.
if PlayersCount > 0 and DetectedItem.IsDetected == true then
-- Here we're doing something advanced... We're copying the DetectedSet.
local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
--- Evaluates the removal of the Task from the Mission.
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
-- @param #TASK_A2A_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission
-- @param Tasking.Task#TASK Task
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @param #boolean DetectedItemID
-- @param #boolean DetectedItemChange
-- @return Tasking.Task#TASK
function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged )
if Task then
if Task:IsStatePlanned() then
local TaskName = Task:GetName()
local TaskType = TaskName:match( "(%u+)%.%d+" )
self:T2( { TaskType = TaskType } )
local Remove = false
local IsPlayers = Detection:IsPlayersNearBy( DetectedItem )
if TaskType == "ENGAGE" then
if IsPlayers == false then
Remove = true
end
end
if TaskType == "INTERCEPT" then
if IsPlayers == true then
Remove = true
end
if DetectedItem.IsDetected == false then
Remove = true
end
end
if TaskType == "SWEEP" then
if DetectedItem.IsDetected == true then
Remove = true
end
end
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
-- DetectedSet:Flush( self )
-- self:F( { DetectedSetCount = DetectedSet:Count() } )
if DetectedSet:Count() == 0 then
Remove = true
end
if DetectedItemChanged == true or Remove then
Task = self:RemoveTask( DetectedItemIndex )
end
end
end
return Task
end
--- Calculates which friendlies are nearby the area
-- @param #TASK_A2A_DISPATCHER self
-- @param DetectedItem
-- @return #number, Tasking.CommandCenter#REPORT
function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
local FriendlyTypes = {}
local FriendliesCount = 0
if FriendlyUnitsNearBy then
local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G()
for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do
local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT
if FriendlyUnit:IsAirPlane() then
local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel()
FriendliesCount = FriendliesCount + 1
local FriendlyType = FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
end
end
end
end
-- self:F( { FriendliesCount = FriendliesCount } )
local FriendlyTypesReport = REPORT:New()
if FriendliesCount > 0 then
for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do
FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) )
end
else
FriendlyTypesReport:Add( "-" )
end
return FriendliesCount, FriendlyTypesReport
end
--- Calculates which HUMAN friendlies are nearby the area
-- @param #TASK_A2A_DISPATCHER self
-- @param DetectedItem
-- @return #number, Tasking.CommandCenter#REPORT
function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set
local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem )
local PlayerTypes = {}
local PlayersCount = 0
if PlayersNearBy then
local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G()
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
local PlayerName = PlayerUnit:GetPlayerName()
-- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
PlayersCount = PlayersCount + 1
local PlayerType = PlayerUnit:GetTypeName()
PlayerTypes[PlayerName] = PlayerType
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
end
end
end
end
local PlayerTypesReport = REPORT:New()
if PlayersCount > 0 then
for PlayerName, PlayerType in pairs( PlayerTypes ) do
PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) )
end
else
PlayerTypesReport:Add( "-" )
end
return PlayersCount, PlayerTypesReport
end
function TASK_A2A_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil
end
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_A2A_DISPATCHER:ProcessDetected( Detection )
self:F()
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
local Mission = self.Mission
if Mission:IsIDLE() or Mission:IsENGAGED() then
local TaskReport = REPORT:New()
-- Checking the task queue for the dispatcher, and removing any obsolete task!
for TaskIndex, TaskData in pairs( self.Tasks ) do
local Task = TaskData -- Tasking.Task#TASK
if Task:IsStatePlanned() then
local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex )
if not DetectedItem then
local TaskText = Task:GetName()
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
end
Task = self:RemoveTask( TaskIndex )
end
end
end
-- Now that all obsolete tasks are removed, loop through the detected targets.
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedCount = DetectedSet:Count()
local DetectedZone = DetectedItem.Zone
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
-- DetectedSet:Flush( self )
local DetectedID = DetectedItem.ID
local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
local Task = self.Tasks[TaskIndex]
Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed.
-- Evaluate INTERCEPT
if not Task and DetectedCount > 0 then
local TargetSetUnit = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_ENGAGE:New( Mission, self.SetGroup, string.format( "ENGAGE.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
else
local TargetSetUnit = self:EvaluateINTERCEPT( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_INTERCEPT:New( Mission, self.SetGroup, string.format( "INTERCEPT.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
else
local TargetSetUnit = self:EvaluateSWEEP( DetectedItem ) -- Returns a SetUnit
if TargetSetUnit then
Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
end
end
end
if Task then
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone, DetectedItem.Coordinate.y, DetectedItem.Coordinate.Heading )
Task:SetDispatcher( self )
Mission:AddTask( Task )
function Task.OnEnterSuccess( Task, From, Event, To )
self:Success( Task )
end
function Task.OnEnterCancelled( Task, From, Event, To )
self:Cancelled( Task )
end
function Task.OnEnterFailed( Task, From, Event, To )
self:Failed( Task )
end
function Task.OnEnterAborted( Task, From, Event, To )
self:Aborted( Task )
end
TaskReport:Add( Task:GetName() )
else
self:F( "This should not happen" )
end
end
if Task then
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" )
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" )
end
-- OK, so the tasking has been done, now delete the changes reported for the area.
Detection:AcceptChanges( DetectedItem )
end
-- TODO set menus using the HQ coordinator
Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text( ", " )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end
end
end
return true
end
end

Some files were not shown because too many files have changed in this diff Show More