Compare commits

...

686 Commits

Author SHA1 Message Date
Frank
59f795fc17 Merge pull request #1349 from FlightControl-Master/FF/MasterDevel
RANGE, TIMER & Minor
2020-09-02 00:08:36 +02:00
Frank
26a935c29c RANGE etc
RANGE v2.2.3
- Added relay unit option.

TIMER
- Fixed bug.

Minor other stuff
2020-09-01 17:30:26 +02:00
Frank
848336f8ac Merge pull request #1348 from FlightControl-Master/FF/MasterDevel
Updates, fixes and new classes
2020-08-29 22:15:14 +02:00
Frank
8e6e9cbb4d Updated Moose Modules.lua
- Added new class files Profiler, Timer, Marker
2020-08-29 22:10:45 +02:00
Frank
15cb9bec40 Updates, new classes and fixes
**AI_FORMATION**
- Performance improvents. This also affects the **RESCUEHELO** class.

**EVENT**
- Added events when object is of category BASE.

**FSM**
- Removed a few tracing calls.
- Updated docs.

**POINT**
- Added Update Vec functions to COORDINATE.
- Added *overwrite* option to COORDINATE:Translate() function.
- Removed second COORDINATE:Translate() function.
- Optimized COORDINATE:GetClosestAirbase() function.
- Added *Offset* parameter to COORDINATE:IsLOS() function.

**RADIO**
- Updated BEACON type and system enums.

**RADIOQUEUE**
- Use Vec3 instead of COORDINATE. Performance improvement.

**SET**
- Added some functions.

**TIMER**
- Added new class. Little sister of SCHEDULER class.

**DCS**
- Minor changes regarding docs.

**ATIS**
- Added "Miles.ogg", "StatuteMiles.ogg", "Zulu.ogg".

**ENUMS**
- Added ENUMS.Formation.Vehicle
- Added ENUMS.AlarmState

** PROFILER**
- Added new lua profiler.

**UTILS**
- Minor changes and additions.

**AIRBASE**
- Improved Registration.
- Improved Parking spot handling.
- Aded :IsAirdrome(), IsHelipad(), IsShip() functions.
- Improved :GetRunwayData() for Syria airports.

**CONTROLLABLE**
- Fixed bug in :CommandSetFrequency() fuction (Hz vs. MHz).
- Updated/fixed :TaskFAC_AttackGroup() function.

**GROUP**
- Added :GetThreatLevel() function.
- Added :IsInZone() function to check if any unit is in the zone.

**MARKER**
- Added new class handling F10 markers using FSM.
2020-08-29 21:55:59 +02:00
Frank
38f5fd8249 SET_STATIC
- Fixed #1345
2020-08-19 23:10:20 +02:00
Frank
6a04f83280 Syria
ATIS
- Added runway magnetic to true conversion. Channel map -10°, Syria +5°.
- ICAOP phraseology The Channel and Syria true.

UTILS
- Added DCSMAP.Syria="Syria"
- Magnetic declination for Syria set to 5°
- GMT to local time for Syria is GMT+3.

AIRBASE
- Added AIRBASE.Syria enums.
2020-08-19 23:04:38 +02:00
Frank
ec4d12a0d5 Merge pull request #1338 from RISCfuture/improve-atis
Modify ATIS format to more closely match real world
2020-08-19 22:28:31 +02:00
Frank
999aba5d77 Update README.md 2020-07-19 21:15:18 +02:00
Tim Morgan
652f29b782 Use SM for visibility, not NM 2020-07-11 13:15:50 -07:00
Tim Morgan
0c6aae45c1 Do not report visibilities > 10 NM or 9999 m 2020-07-11 13:15:50 -07:00
Tim Morgan
25b8cff442 Don't use ICAO phraseology for US airports 2020-07-11 13:03:41 -07:00
Tim Morgan
9f7471178b Add option to suppress reporting QFE 2020-07-11 13:03:41 -07:00
Tim Morgan
f3f3406d64 Reorder ATIS components to match real-life ATIS, add option to suppress all but Zulu times 2020-07-11 13:03:40 -07:00
Tim Morgan
4eb886d0f3 Typo fix 2020-07-11 13:03:40 -07:00
Frank
5c6e50e7f9 Update SpawnStatic.lua
- Fixed #1335
2020-06-11 23:44:19 +02:00
Frank
fb3115a0f1 The Channel Update
**AIRBASE**
- Added "The Channel" map airbase name enumerators.

**ATIS v0.8.0**
- Added sunset and sunrise times (required new sound files).
- Supports "The Channel" map (needs new sound files).

**UTILS**
- Corrected NTTR local time diff to GMT-8 (was -7) hours.
- Corrected Normany local time diff to GMT+0 (was -1) hours.
- Added "The Channel" map local time diff as GMT+2 (should be GMT).
- Fixed bug in UTILS.GMTToLocalTimeDifference() function.
2020-06-04 23:46:26 +02:00
Frank
833d4f7b65 SPAWNSTATIC class reworked
**SPAWNSTATIC**
- Added option to link statics to units, e.g. carriers.
- Fixed SpawnFromType function.
- Added :InitXYZ functions to set parameters.
- Removed ReSpawn functions. Pointless here. Use Respawn of STATIC class instead.
- Updated docs.

**COORDINATE**
- Added optional parameter to IsDay() and IsNight() functions to check on specific time.

**STATIC**
- Fixed Respawn functions so that statics do not appear on top of each other.

**UTILS**
- Added optional parameter to GetMissionDay and GetMissionDayOfYear functions.

**AIRBOSS**
- Adjusted recovery turn time interval back to 5 min in :SetRecoveryTurnTime() function.
2020-05-27 22:18:18 +02:00
Frank
61bb59d8b3 Astro Update
**COORDINATE**
* Added functions to get sunrise and sunset times.

**UTILS**
* Added functions to calculate sunset and sunrise
* Added function to replace illegal characters.

**AIRBOSS v1.1.5**
* Fixed wrong function name when recovery is resumed.
* Replace illegcal characters in player names when writing trapsheet.
2020-05-24 23:33:21 +02:00
Frank
21652de804 AIRBOSS v1.1.4
Updated Nimitz class parameters.
2020-05-20 23:03:25 +02:00
Frank
e677e2acf4 Merge pull request #1330 from FlightControl-Master/develop
Small fix to A2G dispatcher
2020-05-19 09:24:07 +02:00
Frank
2658230331 Update and fixes
AI_A2G_DISPATCHER
- Fixed report output.

ENUMS
- Added Morse code.
2020-05-17 00:28:12 +02:00
Frank
bb0827bb8c Merge pull request #1327 from FlightControl-Master/develop
Develop
2020-05-16 19:48:58 +02:00
Frank
be1f8cde34 Merge pull request #1326 from FlightControl-Master/FF/Develop
Fixes
2020-05-16 13:14:55 +02:00
Frank
29028bf128 Merge branch 'develop' into FF/Develop 2020-05-16 13:12:49 +02:00
Frank
c30ca2b18a Fixes
RADIOQUEUE
- Fix for issue #1321

USERFLAG
- Added delay parameter

CLEANUP
- Fixes iniunit nil #1324

WAREHOUSE v1.0.2
- Fixes #1322
2020-05-16 13:09:04 +02:00
Frank
d1b40b63c2 Merge pull request #1320 from zlinman/develop
Develop
2020-05-06 09:03:00 +02:00
zlinman
0b7ac754c5 only type error corrected, spaces deleted 2020-05-04 12:53:09 +02:00
Frank
5b514d223b Merge pull request #1319 from FlightControl-Master/FF/Develop
Ff/develop
2020-05-04 00:39:26 +02:00
Frank
7d8e27c83e Update Warehouse.lua 2020-05-04 00:37:28 +02:00
Frank
dcd1060496 Update Warehouse.lua 2020-05-04 00:33:33 +02:00
Frank
533e2abf17 Merge branch 'develop' into FF/Develop 2020-05-04 00:31:54 +02:00
Frank
6b318d0309 Update Enums.lua 2020-05-04 00:31:43 +02:00
Frank
bee44b97fd WAREHOUSE v1.0.1 2020-05-03 23:56:32 +02:00
Frank
77de1dda2b Stuff 2020-05-03 01:14:37 +02:00
Frank
5b03bc5066 Merge branch 'develop' into FF/Develop 2020-05-01 23:05:55 +02:00
Frank
0d6f32581c Merge pull request #1315 from FlightControl-Master/FF/Develop
Fixes
2020-05-01 23:03:54 +02:00
Frank
2620370890 Fixes
UTILS
- Corrected Big Smoke and Fire presets. Issue #1313

CONTROLLABLE
- Fixed callsign number in :CommandSetCallsign function. #1314

ENUMS
- Added formations (old and new)
2020-05-01 23:01:43 +02:00
zlinman
f866af0d4a Update CleanUp.lua 2020-04-30 18:45:06 +02:00
zlinman
b86eac33b6 Revert "Update CleanUp.lua"
This reverts commit c78bd2652a.
2020-04-30 18:38:04 +02:00
zlinman
c78bd2652a Update CleanUp.lua 2020-04-30 18:33:15 +02:00
Frank
392d465b30 Merge pull request #1310 from SteveLockeUK/fix/task-report-order
Added call to self:UpdateTaskInfo( self.DetectedItem ) to RerportOrde…
2020-04-25 21:48:44 +02:00
Stephen Locke
056cc1630d Added call to self:UpdateTaskInfo( self.DetectedItem ) to RerportOrder for both Task_A2A and Task_A2G. This fixes an issue with multi task missions where coordinates are not available when Moose attempts to sort the tasks for the comms menu. 2020-04-25 15:07:04 +01:00
Frank
364ce927e7 Merge pull request #1309 from FlightControl-Master/FF/Develop
Misc Fixes and Improvements
2020-04-25 01:26:53 +02:00
Frank
517d5860fb Update Controllable.lua 2020-04-25 00:57:59 +02:00
Frank
4e49184da2 Update Static.lua 2020-04-24 22:53:42 +02:00
Frank
68ece29ab5 Update Controllable.lua 2020-04-24 22:45:45 +02:00
Frank
c3cc76c15b Update Controllable.lua 2020-04-24 21:57:10 +02:00
Frank
0a570da986 Up 2020-04-24 21:16:42 +02:00
Frank
b25697e261 Merge branch 'develop' into FF/Develop 2020-04-24 16:57:40 +02:00
Frank
863e239204 Update Utils.lua 2020-04-24 16:53:15 +02:00
Frank
457b30ed07 ATIS v0.7.1
Fixed bug that windfrom < 0.
2020-04-24 16:46:46 +02:00
Frank
7360c51a8f Updates Misc 2020-04-24 16:03:28 +02:00
Frank
df512f7a58 Update RadioQueue.lua 2020-04-24 01:19:04 +02:00
Frank
da05c41c27 Update AI_Air_Engage.lua
Should fix #1206
2020-04-24 00:40:54 +02:00
Frank
3c428723f8 Update AI_Air.lua
Should fix issue #1205
2020-04-24 00:16:18 +02:00
Frank
4c3fd1d867 Airboss v1.1.3
- Unicorn only when Tgroove 16-18 sec.
- Recovery time can be given as relative seconds.
- Time before turn can be specified by user.
- Function to get next recovery time.
- Removed some old code.
2020-04-23 16:44:38 +02:00
Frank
909c028e48 Update AI_A2A_Patrol.lua
Fixes issue #1307
2020-04-23 16:04:50 +02:00
Frank
14e12dc36b Merge pull request #1306 from FlightControl-Master/thebgpikester-SPAWN-DOC-UPDATE
Spawn Documentation fixes
2020-04-22 15:37:16 +02:00
Pikes
0502e0a020 Spawn Documentation fixes
no code touched
no code touched
I went through removing where 6-7 year old docs used a non existent method "Schedule" and replaced it with SpawnSchedule() wher ethat worked or Spawn() where I couldnt understand the ancient demo.
Also added warnings on IniLimit and InitCleanUp() the two worst offenders of misconfiguration.
Couple of Belgian typos corrected.
2020-04-22 14:09:31 +01:00
Frank
36aebd3c04 Merge pull request #1299 from FlightControl-Master/thebgpikester-ai_Patrol_Baro
change default to "BARO"
2020-04-20 22:02:43 +02:00
Frank
007831bb9a Merge pull request #1298 from FlightControl-Master/thebgpikester-Dispatcherchanges
Changing default altitude to BARO
2020-04-20 21:57:13 +02:00
Frank
def44bde89 Merge pull request #1304 from FlightControl-Master/FF/Develop
Fixes
2020-04-08 12:04:28 +02:00
Frank
4b369fae95 A2G Dispatcher
Fixes issue #1303
2020-04-08 11:54:09 +02:00
Frank
cb4a44b512 Event 2020-04-08 01:10:43 +02:00
Frank
53fbec9c15 events 2020-04-08 00:09:47 +02:00
Frank
d476922af2 Merge pull request #1301 from FlightControl-Master/FF/Develop
ATIS and WAREHOUSE
2020-04-04 23:39:56 +02:00
Frank
861f2da4d6 ATIS and WAREHOUSE 2020-04-04 23:08:23 +02:00
Pikes
6d75b25f57 change default to "BARO"
addition on AI_A2A_Patrol already done
2020-04-02 13:50:41 +01:00
Pikes
bfb3ae33f4 Changing default altitude to BARO
to avoid DCS AI stopping engaging or even flying int he right direction.
2020-04-02 13:26:03 +01:00
Pikes
a796c7a594 Urgent fix for Ford_AB name change. this breaks Moose completely until pushed.
ATC table was changed to the correct name for the enumerator string of "Ford" to "Ford_AB" The main enumerator table was not also changed so the two mismatched.
One has to agree with the other else Moose stops with an index error.
However, there could be minor risk if someone refers to the old enumerator. Whilst we dont care about the string name, the AB was changed by ED anyway. The actual way to keep is to have the string equal the map name. Either way is damned if you do, damned if you don't but I'd rather keep the convention and blame ED than keep it wrong and inconsistent.
2020-03-28 18:56:14 +00:00
Frank
af63c1a219 Merge pull request #1269 from FlightControl-Master/SPAWNInitArray_#1255
Spawn init array #1255
2020-03-27 23:04:09 +01:00
Frank
72da7a2c04 Merge pull request #1282 from FlightControl-Master/Pikes-ATC---added-the-6-new-airbases
Normandy added 7 new airbases with coords
2020-03-27 22:58:57 +01:00
Frank
f9bdbf4e3f Merge pull request #1293 from FlightControl-Master/FF/Develop
AIRBASE
2020-03-20 16:37:23 +01:00
Frank
850951bcb4 Update Airbase.lua 2020-03-20 16:35:28 +01:00
Frank
3d0f002f73 Merge pull request #1292 from FlightControl-Master/ATC_GROUND_PG_#1291
Applied @Warlords fix on swapped Airbases in ATC_Ground.lua PG map
2020-03-16 18:07:53 +01:00
Thor Vik
ff8c454752 Applied @Warlords fix on swapped Airbases in ATC_Ground.lua PG map
Solves Issue #1291
2020-03-16 17:52:14 +01:00
Frank
6882c4b42f Merge pull request #1290 from FlightControl-Master/FF/Develop
SPAWN
2020-03-14 20:34:39 +01:00
Frank
9b0f474edf Update Spawn.lua 2020-03-14 20:30:58 +01:00
Frank
ceaae5421c SPAWN
corrected spawn name
2020-03-14 20:28:32 +01:00
Frank
36666df76f Temp 2020-03-14 19:19:55 +01:00
Frank
3616e4264a Merge pull request #1288 from FlightControl-Master/Issue_#1286_SpawnNewFromTemplate
Made parameter SpawnAliasPrefix mandatory for SpawnFromTemplate funct…
2020-03-11 22:49:27 +01:00
Thor Vik
a704693abf Made parameter SpawnAliasPrefix mandatory for SpawnFromTemplate function. 2020-03-11 15:28:36 +01:00
Frank
4dc303a166 Merge pull request #1287 from FlightControl-Master/Issue_#1286_SpawnNewFromTemplate
Fix for Issue #1286, partly tested.
2020-03-11 10:50:23 +01:00
Thor Vik
71b5140e69 Fix for Issue #1286, partly tested. 2020-03-11 10:11:59 +01:00
Frank
00e5499f81 Merge pull request #1285 from Loophole62ndFF/InitGroupHeading_implementation
Init group heading implementation
2020-03-05 22:57:29 +01:00
Michael Barnes
7cf73af707 Added some protection against unexpectedly-nil values. 2020-03-05 13:05:25 +10:00
Michael Barnes
4ef827836c Added SPAWN:InitGroupHeading function, to allow orientation of the overall group formation when spawned (handy for static formations like SAM batteries, etc.) 2020-03-05 13:05:11 +10:00
Frank
e3d6337481 Merge pull request #1284 from FlightControl-Master/FF/Develop
MGRS
2020-02-26 20:52:48 +01:00
Frank
f258e9263e MGRS 2020-02-26 20:44:35 +01:00
Frank
48d99a19c5 Merge pull request #1270 from FlightControl-Master/WAREHOUSE-_#1260
Added some line to docs to tell Spawn state might not be as expected.
2020-02-25 21:53:23 +01:00
Frank
f34511bd8c Merge pull request #1283 from FlightControl-Master/FF/Develop
Fixes & Improvements
2020-02-25 21:41:48 +01:00
Frank
63a5d2e3ac Fixes & Improvements 2020-02-25 21:35:00 +01:00
Pikes
3fb52d4fa1 Normandy added 6 airbases with coords
added the coords 
added the list of airbases at the top
2020-02-22 22:17:34 +00:00
Frank
463403cef4 Merge pull request #1280 from FlightControl-Master/Pikes---adding-new-Normandy-airbase-enumerators-to-AIRBASE.Normandy
Update Airbase.lua
2020-02-22 18:48:26 +01:00
Pikes
fedee49eb1 Update Airbase.lua
https://github.com/FlightControl-Master/MOOSE/issues/1279
I dont know if anything else needs to be done apart from this.
2020-02-22 16:35:18 +00:00
132nd-Entropy
042cc7a85f Update ATIS.lua 2020-02-20 15:04:00 +01:00
132nd-Entropy
9583168c4d Update ATIS.lua 2020-02-20 14:38:11 +01:00
Frank
0c32c35c27 Merge pull request #1275 from FlightControl-Master/FF/Develop
Ff/develop
2020-02-17 23:42:47 +01:00
Frank
6ab85072d2 DCS 2.5.6 fixes 2020-02-17 23:19:28 +01:00
Frank
04da941c36 Updates 2020-02-15 23:51:28 +01:00
Wingthor
d6c919a097 Added some line to docs to tell Spawn state might not be as expected.
Added:
Requested assets spawn in various "Engagement Rules" (ROE) and Alerts modes. If your assets will cross into dangerous areas, be sure to change these states. You can do this in @{#WAREHOUSE:OnAfterAssetSpawned}(*From, *Event, *To, *group, *asset, *request)) function.

Initial Spawn states is as follows:
    GROUND: ROE, "Return Fire" Alarm, "Green"
    AIR: 	ROE, "Return Fire" Reaction to Threat, "Passive Defense"
    NAVAL	ROE, "Return Fire" Alarm,"N/A"

Solves issue #1260
2020-02-13 12:16:56 +01:00
Wingthor
1f9ffcd92d The error is to deep to solve on the fly. In database new groups appear, but not in map, when regular spawning (:Spawn()) is done. Spawning scheduled causes groups to appear on map. OnSpawnGroup is not triggered in neither of methods.
#Issue 1255 is for now solved by adding a caution to documentation for function/directive: "CAUTION: this directive will NOT work with OnSpawnGroup function."

Requires work from author or someone else with deep knowledge of the class.
2020-02-13 09:58:59 +01:00
Wingthor
12ef34acaa Merge pull request #1266 from FlightControl-Master/AIRBASE.Nevada.Mellan_Airstrip_does_not_exist_#1263
FIX: removed Mellan_Airstrip from list of Nevada airfields (not a val…

Fixes #1266 which now can be closed.
2020-02-12 18:38:53 +01:00
132nd-Entropy
ce60e7ce74 FIX: removed Mellan_Airstrip from list of Nevada airfields (not a valid airport and has no taxiway designations, resulting in a scripting error if included) 2020-02-12 16:25:45 +01:00
thebgpikester
aa5d36db88 Merge pull request #1265 from FlightControl-Master/ATC_GROUND_PERSIANGULF_Abu_Musa_Island_Airport_double_entry
Issue #1264 Abu_Musa_Island_Airport
2020-02-11 15:11:23 +00:00
Wingthor
a2759b1cc0 Issue #1264 Abu_Musa_Island_Airport 2020-02-11 15:55:10 +01:00
Frank
0da0b025b5 Merge pull request #1262 from FlightControl-Master/#1258-WAREHOUSE-onafterChangeCountry(From,-Event,-To,-Country)
#1258 warehouse onafter change country(from, event, to, country)
2020-02-09 19:41:01 +01:00
Wingthor
f685064c0c fixed the typo on self.airbasse to self.airbase.
Tested with and without this line `SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi))` in the test script.

Resolves issue #1258
2020-02-08 12:44:50 +01:00
Wingthor
b68e254bc0 Fixes issue #1258 WAREHOUSE:onafterChangeCountry() 2020-02-06 15:36:54 +01:00
Wingthor
d43f77706e Merge branch '1254-range-setsoundfilespath-path' into 'develop'
Fixed issue 1254

Closes #1254

See merge request Wingthor/MOOSE!1259
2020-02-01 20:28:07 +01:00
Wingthor
5e1fb36d01 Fixed issue 1254 2020-02-01 20:27:00 +01:00
Wingthor
6d3bbe88e6 Merge branch 'wingthor_fixes' into 'develop'
Test Commits and pipe

See merge request Wingthor/MOOSE!1258
2020-02-01 19:53:26 +01:00
Wingthor
e4906eb083 Test Commits and pipe 2020-02-01 19:30:32 +01:00
Frank
19eb81866a Merge pull request #1257 from FlightControl-Master/FF/Develop
Misc/Minor
2020-01-25 23:59:06 +01:00
Frank
cd34755036 Merge pull request #1253 from NachtRaveVL/RouteNameFix
Adding route name fetch to getGroupRoute()
2020-01-21 19:04:23 +01:00
NachtRaveVL
c72625c60f Adding route name fetch to getGroupRoute() 2020-01-20 15:28:03 -08:00
Frank
1e1154d190 controllable 2020-01-19 22:14:45 +01:00
Frank
ff0f62c01b Merge pull request #1252 from FlightControl-Master/FF/Develop
ATIS v0.6.2 and Misc
2020-01-08 19:43:56 +01:00
Frank
e6e5787fc6 Update Point.lua 2020-01-08 19:26:40 +01:00
Frank
431e8a05f9 ZCC
- Fixed unit counting in status report.
2020-01-08 18:45:57 +01:00
Frank
b91a8cf4c8 ATIS v0.6.2
- Added F10 marker option.
2020-01-08 12:04:45 +01:00
Frank
3e8455410e ZCC, ATIS, CONTROLLABLE 2020-01-07 23:34:36 +01:00
Frank
3a9ad1b7cc Merge pull request #1251 from FlightControl-Master/FF/Develop
FOX fixed log spamming
2020-01-06 21:58:25 +01:00
Frank
6000421957 Update Fox.lua 2020-01-06 21:54:59 +01:00
Frank
231ceef41f Merge pull request #1250 from FlightControl-Master/FF/Develop
Misc Improvements
2020-01-06 21:43:31 +01:00
Frank
4661f6981e Misc 2020-01-06 21:09:21 +01:00
Frank
fcfcfeae00 Misc 2020-01-06 00:15:44 +01:00
Frank
09ed562f61 SUPPRESSION & AIRBOSS
- Fixed bug/typo in SUPPRESSION:SetHomeBase() function.
- Added AIRBOSS Marshal event.
2020-01-04 00:15:00 +01:00
Frank
b1f2bab87e Merge pull request #1249 from FlightControl-Master/FF/Develop
Ff/develop
2020-01-02 22:58:51 +01:00
Frank
aa6515e1ca AI_AIR
- Fixed onafterRefuel
2020-01-02 22:50:35 +01:00
Frank
d5b8ed62ae AI_AIR
- Fixed call to .Resume function if AI_A2A_CAP is calling.
2020-01-02 20:15:42 +01:00
Frank
6208c79d1f ARTY v1.1.7 & AIRBASE
- Improved ARTY._PassingWaypoint function.
- Corrected AIRBASE.Ras_Al_Khaimah
2020-01-02 16:17:25 +01:00
Frank
740ee74f61 SUPPRESSION 2019-12-31 00:00:45 +01:00
Frank
0bb0c70667 Merge pull request #1247 from FlightControl-Master/FF/Develop
ZONE_CAPTURE_COALITION and other fixes
2019-12-29 23:40:27 +01:00
Frank
9fb311bf7d Update AI_A2A_Patrol.lua 2019-12-29 23:10:49 +01:00
Frank
8915657e91 ZONE_CAPTURE_COALITION 2019-12-29 23:08:23 +01:00
Frank
8b506a2f20 ZONE_CAPTURE_COALITON & RANGE 2019-12-28 23:59:39 +01:00
Frank
6dd73dba44 AI_AIR
Fixed task in onafterPatrolRoute function
2019-12-28 19:53:02 +01:00
Frank
d3a3d96436 ZONE CAP COAL 2019-12-27 23:03:19 +01:00
Frank
8337058306 CAPTURE ZONE 2019-12-26 23:20:55 +01:00
Frank
389931fbf0 AI_PATROL
- output fix
2019-12-21 01:09:59 +01:00
Frank
b9fd6c0e31 Update RAT.lua 2019-12-19 22:59:37 +01:00
Frank
f48f53fb37 Update RAT.lua
Fixed bug with uncontrolled spawning.
2019-12-19 13:11:58 +01:00
Frank
07edcd7e73 SCHEDULER
Clean up
2019-12-18 23:00:42 +01:00
Frank
a193d342d7 Merge pull request #1244 from FlightControl-Master/FF/Develop
A2A Dispatcher and other fixes/improvements
2019-12-17 00:16:04 +01:00
Frank
5b9978e390 A2A Dispatcher fixes
- Increased engage distance.
2019-12-17 00:09:24 +01:00
Frank
c3c984381e SUPPRESSION 2019-12-16 00:59:23 +01:00
Frank
ca49e8f8ae FOX v0.6.1
- Added Stop event.
2019-12-15 22:30:05 +01:00
Frank
59c46f9b4b Push 2019-12-11 21:38:53 +01:00
Frank
b9384831fb Merge pull request #1242 from FlightControl-Master/FF/Develop
ATIS v0.6.0
2019-12-11 21:12:24 +01:00
Frank
0d57ad5584 ATIS v0.6.0
- Added altimeter QNH calculation
2019-12-11 21:10:23 +01:00
Frank
cf34ec0905 Merge pull request #1241 from FlightControl-Master/FF/Develop
SPAWN AI_Balancer
2019-12-10 20:16:09 +01:00
Frank
1c99e474b2 SPAWN AI_Balancer
SPAWN
- Added :InitAirbase()

AI_BALANCER
- Fixed bug in Return function.
2019-12-10 20:11:49 +01:00
Frank
e45134f086 Merge pull request #1240 from FlightControl-Master/FF/Develop
SCHEDULER fixes
2019-12-08 00:29:32 +01:00
Frank
1b5e9df586 SCHEDULER fixes 2019-12-08 00:27:20 +01:00
Frank
4b6612e114 Cargo fixes 2019-12-04 22:17:51 +01:00
Frank
e4473010eb Docs
HelicopterUsable
2019-12-02 16:33:03 +01:00
Frank
bb259b2572 Merge pull request #1235 from FlightControl-Master/FF/Develop
Ff/develop
2019-11-25 12:17:36 +01:00
Frank
e16e2851c1 ARTY v1.1.6
- Check that arty unit is not dead when addressed via marker
2019-11-25 11:58:29 +01:00
Frank
169c5a674c Fixes
AI_PATROL:
- Target unit is nil. Issue #1234
DETECTION:
- ReportFriendliesNearBy ForEachPlayer added nil check.
CONTROLLABLE:
- Added IsHelicopter()
- Added OptionRestrictBurner()
AI_A2A_Cap
- AtteckUnit not nil check.
AI_A2A_Dispatcher:
- DefenderGroup not nil and alive check #1228
AI_A2A_GCICAP:
- AttackCoordinate nil check
2019-11-25 11:50:07 +01:00
Frank
60042e14dc Update Airboss.lua
- Removed Orion
2019-11-23 21:02:02 +01:00
Frank
14de37f390 AIRBOSS & BASE
BASE:
- Fixed bug that tracing is always ON

AIRBOSS v1.1.0
- Added support for P-3C Orion and C-2A Greyhound (AI) from MAM
2019-11-23 20:36:45 +01:00
Frank
e67b819df9 Merge pull request #1224 from FlightControl-Master/FF/Develop
RANGE v2.2.0
2019-11-09 14:56:55 -05:00
Frank
efccefecad RANGE v2.2.0
- Added option for range instructor and range control radio (needs voice overs).
- Reduced number of smokes for strafe pits.
- Updated docs.
- SetAutoSave will auto load as well.
- other
2019-11-09 20:55:28 +01:00
Frank
faa2debbae DATABASE & RAT
- DATABASE: spawned units (ships) are registered as airbases.
- RAT v2.3.8: script will not crash if (spawned) AIRBASE cannot be found .
2019-11-04 22:17:26 +01:00
Frank
ddf61d1bf3 ARTY v1.1.5
- Fixed bug for tac nukes (critial mass insufficient to trigger nuclear fission).
2019-10-31 18:55:21 +01:00
Frank
c5d514efdf Merge pull request #1220 from FlightControl-Master/FF/Develop
AI_Air_Patrol
2019-10-22 17:28:42 -04:00
Frank
f1c16def9a Update AI_Air_Patrol.lua
- fixed bug that route is not called for racetrack
2019-10-22 23:25:30 +02:00
Frank
626db45a7e Merge pull request #1219 from FlightControl-Master/FF/Develop
AI_Air_Patrol
2019-10-22 16:54:52 -04:00
Frank
21a7594134 Update AI_Air_Patrol.lua
- fixed bug that altitude and speed are not defined for racetrack patterns
2019-10-22 22:53:04 +02:00
Frank
5fd20477d9 Merge pull request #1218 from FlightControl-Master/FF/Develop
AIRBOSS v1.0.9
2019-10-22 08:46:45 -04:00
Frank
01dd3c93d2 AIRBOSS v1.0.9
- Case II/III marshal stacks are not collapsed.
- Fixed little bug that Charlie time cannot be calculated if no future recovery window is defined.
2019-10-22 14:35:10 +02:00
Frank
b733e3b6d8 Merge pull request #1217 from FlightControl-Master/FF/Develop
ATIS v0.5.0
2019-10-21 16:47:44 -04:00
Frank
fe2ef11f8d ATIS v0.5.0 2019-10-21 22:29:45 +02:00
Frank
9b6994749b Merge branch 'develop' into FF/Develop 2019-10-21 00:30:02 +02:00
Frank
9b209d6ce0 ATIS 2019-10-21 00:29:46 +02:00
Frank
ed649747bd Merge pull request #1215 from FlightControl-Master/FF/Develop
ATIS v0.4.1
2019-10-18 15:17:31 -04:00
Frank
f5f1c9043b ATIS v0.4.1
ATIS v0.4.1
- Fixed schedulers being deallocated by lua garbage collector.

RADIOQUEUE
- Added alias optional name.
- Minor.
2019-10-18 21:15:53 +02:00
Frank
c7c371dc34 Merge pull request #1212 from FlightControl-Master/FF/Develop
COORDINATE
2019-10-14 08:36:17 -04:00
Frank
1cea10f03a Update Point.lua
Fixed bug in GetClosestAirbase() function.
2019-10-14 14:34:53 +02:00
Frank
3e9f54aaa5 Merge pull request #1210 from FlightControl-Master/FF/Develop
ATIS v0.4.0
2019-10-10 19:33:48 -04:00
Frank
ab1cd41d79 Update ATIS.lua 2019-10-11 01:23:26 +02:00
Frank
dc9f730d7d ATIS v0.4.0
ATIS
UTILS
AIRBASE
2019-10-09 21:13:08 +02:00
Frank
c0e48288de ATIS v0.3.3
- Improved soud file duration.
- Added Nav point data option.
2019-10-08 00:21:41 +02:00
Frank
a13b79ced1 Merge pull request #1209 from FlightControl-Master/FF/Develop
ATIS v0.3.1
2019-10-03 06:15:02 -04:00
Frank
cd845d385f ATIS v0.3.1
- Fixed bug in NATO alphabet (entry 14 missing)
2019-10-03 12:13:38 +02:00
Frank
dea7a5674c Merge pull request #1208 from FlightControl-Master/FF/Develop
ATIS
2019-10-02 14:55:53 -04:00
Frank
8d1d2179f9 ATIS v0.3.0 2019-10-02 20:43:51 +02:00
Frank
9b96ff5978 AIRBASE
- Added runwaydata and active runway functions
2019-10-02 19:47:33 +02:00
Frank
6cc233cac7 ATIS
- COORDINATE
- RADIO
- RADIOQUEUE
- ATIS
- UTILS
- CONTROLLABLE
2019-10-02 19:45:37 +02:00
FlightControl
ad2ce8df78 Improvements to A2A and A2G Dispatchers 2019-09-30 10:35:29 +02:00
FlightControl
4ea44308bc Fix for older A2A dispatcher CAP api altitude problem during takeoff ... 2019-09-26 19:36:42 +03:00
FlightControl
5cdaf53727 - Added a NoTrace option to the scheduler, so for those schedulers that have a schedule in microseconds, you may wanna use the function NoTrace(), to avoid spamming the dcs.log.
- Started with the implementation of multiple languages of speech. Got now a Russian and English prototype working.
- Moved radio frequency settings to a squadron, so multiple squadrons can communicate in their own radio frequency.
2019-09-25 17:53:11 +03:00
FlightControl
fb875077d7 Speech optimizations for A2A dispatcher 2019-09-22 20:30:59 +02:00
FlightControl
c579aad606 API V2 of the A2A and A2G dispatchers. Now a more fine grained attack is possible. You can now tweak the altitude and speed when units engage the target. Especially useful for ground attacks when SEAD, CAS or BAI.You can model low altitude approaches. Added 2 new test missions under the IAD - A2G API v2. Please report any bugs. 2019-09-22 08:23:28 +02:00
FlightControl
8c54b7298d Tuning A2A AI dispatcher to work with new sound speech system. 2019-09-15 18:36:27 +02:00
FlightControl
b309c25619 Speech Engine Prototype 2019-09-15 18:02:45 +02:00
Frank
04a699f990 Merge pull request #1197 from FlightControl-Master/FF/Develop
Update ScheduleDispatcher.lua
2019-09-13 13:38:52 -04:00
Frank
d51fc5de8a Update ScheduleDispatcher.lua
better fix
2019-09-13 19:37:13 +02:00
Frank
e458c70a7b Merge pull request #1196 from FlightControl-Master/FF/Develop
ScheduleDispatcher Source=nil fix
2019-09-13 12:58:54 -04:00
Frank
2d4a4fb2ca Update ScheduleDispatcher.lua
Fix for Source (=Infor.source) nil problem.
2019-09-13 18:57:27 +02:00
FlightControl
e23ebd622a - Fixing repetition of Patrolling in A2G dispatcher. 2019-09-09 20:24:51 +03:00
FlightControl
98e2f2c09b - Added A2G voice overs to some of the basic events during defender flight. More to come, like multiple languages, and also more voice overs concerning some of the more detailed events, like:
- Damage
  - Firing
  - Enemy location
  - Callsigns
  - Numbers for distance and degrees.
2019-09-09 19:38:22 +03:00
FlightControl
1fd9cbec1f - Added A2G voice overs to some of the basic events during defender flight. More to come, like multiple languages, and also more voice overs concerning some of the more detailed events, like:
- Damage
  - Firing
  - Enemy location
  - Callsigns
  - Numbers for distance and degrees.
2019-09-09 11:17:45 +02:00
FlightControl
1aedcf1ae4 Radio Queue 2019-09-09 08:19:48 +02:00
FlightControl
e0075cc9ac - Added a new UTILS.kpairs iterator, which will base the iteration on a different key, which can be determined before the iteration starts. eg. the key should be a sort in ascending order on the distance of the information in the set, and it should return the distance + item in the set for each element in the set.
- Resolved for TASK_CAPTURE_ZONES the whole logic of determining correctly the player and AI defense zones, as to be displayed in the information panel, this was a very difficult exercise for me.
2019-09-08 08:38:33 +02:00
FlightControl
372dd704d2 - Added a randomization to some of the internal schedulers to ensure that the simulation does not get unnecessary hickups and guarantees more fluent play.
- Added the option SetFlashStatus() to the CommandCenter to ensure flashing of subscribed tasks automatically to inform the player while in-flight. Also extended TASK to ensure the flasing gets initialized once the player joins the task.
2019-09-03 17:25:45 +02:00
FlightControl
14b35cb069 Improvements in trace output:
- Scheduled calls are traced with ---> or +++> prefixes... This is handy to find where scheduled calls were actually called in the code. Line number and function is shown correctly now. ---> is for one schedule, and +++> for repeated schedules.
- Fsm output is now also organized better. All Fsm calls are shown with :::> prefix. The onafter, onbefore, onenter or onleave method names are also shown, and also the state transition including the function call place is shown now.
-- Now for each trace line, the class name has 30 characters space.
2019-08-31 07:24:31 +02:00
FlightControl
c1b240857f - Remove unnecessary trace lines
- Implement Scheduled Trace - Now source and line number are shown for scheduled calls.
- Info lines are now shown where appropriate.
- The width of trace for the class name is now 25 characters instead of 20.
2019-08-26 08:26:14 +02:00
Frank
0f5eb30a0f Merge pull request #1192 from FlightControl-Master/FF/Develop
DATABASE fix
2019-08-25 16:09:05 -04:00
Frank
9e0f2c22b6 DATABASE fix
- Fix bug for RED templates being registered as NEUTRAL
2019-08-25 22:06:51 +02:00
FlightControl
c0771c0096 - Bugfix when enemy plane has crashed, AttackerDetection is not existing anymore.
Implemented an important fix. Refuel airplanes also when they are engaging targets and the fuel treshold has been reached.
- Inherit ZONE_BASE from FSM instead of BASE. Opens a range of possibilities.
- Remove from ZONE_GOAL the Zone field, and make it inherit from ZONE_BASE instead of FSM!
- Rework the new inheritance tree in the code. (Remove .Zone fields).
- Implement the determination of attack and defense zones.
- Reworked the TaskInfo to include Type and ShowKey.
- Flash A2G Tasking Details. Added menu option.
2019-08-25 06:42:03 +02:00
FlightControl
c716ba16b3 - Bugfix when enemy plane has crashed, AttackerDetection is not existing anymore. 2019-08-24 22:28:44 +02:00
FlightControl
96a904c077 Implemented an important fix. Refuel airplanes also when they are engaging targets and the fuel treshold has been reached. 2019-08-24 08:56:50 +02:00
FlightControl
f951aae3ee - Inherit ZONE_BASE from FSM instead of BASE. Opens a range of possibilities.
- Remove from ZONE_GOAL the Zone field, and make it inherit from ZONE_BASE instead of FSM!
- Rework the new inheritance tree in the code. (Remove .Zone fields).
- Implement the determination of attack and defense zones.
- Reworked the TaskInfo to include Type and ShowKey.
- Flash A2G Tasking Details. Added menu option.
2019-08-21 22:04:11 +03:00
Frank
7a859f20bb Merge pull request #1190 from FlightControl-Master/FF/Develop
Ff/develop
2019-08-18 16:26:02 -04:00
Frank
3bfa8830a5 Merge branch 'develop' into FF/Develop 2019-08-18 22:21:54 +02:00
Frank
ec85b42a38 OPS
AIRBOSS v1.0.8
- Fixed bug that status reset after wave off causes a unitcorn grade.
- Optimized radio scheduler calls ==> leads to less mem consumtion according to collectgarbage("count").

RESCUEHELO v1.0.9
- Adjusted default follow time interval to 1.0 sec.
- Added SetFollowTimeInterval() function.
- Respawn after returned delayed by 5 sec.

GROUP
- Added nil check in RespawnAtCurrentAirbase() function.
2019-08-18 22:21:41 +02:00
FlightControl
a216eb4e74 Defense test for TASK_CAPTURE_ZONE 2019-08-18 16:44:20 +02:00
FlightControl
94476e418e Messaging and Tactical Menu 2019-08-17 10:25:26 +02:00
FlightControl
fd1eb5f601 AI_A2A_DISPATCHER and AI_A2G_DISPATCHER ... Fixed in-air refuelling problem. Now refuelling is executed instateneously when patrolling in the air and the fuel treshold has been reached. 2019-08-16 22:15:55 +02:00
Frank
db7ec60c86 Merge pull request #1189 from FlightControl-Master/FF/Develop
Ff/develop
2019-08-14 16:44:20 -04:00
Frank
97e1a65b52 WAREHOUSE v0.9.6
- respawn after destroyed keeps assets
2019-08-14 22:36:39 +02:00
Frank
23497dddd4 ARTY v1.1.3
Unfixed workaround for wrong markpoint coordinates. Was fixed in DCS 2.5.5.34644
2019-08-14 22:32:45 +02:00
Frank
e8a7def26e Update Airboss.lua 2019-08-14 21:51:55 +02:00
Frank
28a23c21f4 Update Airboss.lua 2019-08-14 00:35:54 +02:00
Frank
f32094aa43 up 2019-08-12 22:55:56 +02:00
Frank
c215255ae2 Merge branch 'develop' into FF/Develop 2019-08-11 20:39:31 +02:00
Frank
53b998cfab Airboss v1.0.7
- Corrected Foul Deck waveoff.
- Added LSOGrade event.
2019-08-11 20:25:33 +02:00
FlightControl
0a0a7d0fc3 Added a task message when it is engaging or not. Important for communication to the player. 2019-08-11 12:59:43 +02:00
FlightControl
d84ae236eb Added a task message when it is engaging or not. Important for communication to the player. 2019-08-11 12:59:21 +02:00
FlightControl
6dff84f435 Fixed problem with detections escalating. Too much events fired. 2019-08-11 11:00:53 +02:00
FlightControl
1bfa5b5ee0 Fixed problem with detections escalating. Too much events fired. 2019-08-11 11:00:33 +02:00
FlightControl
c3618bdc26 - Fixed Messaging of A2G defenses to players within the same coalition. Now the callsign will be used to communicate, not the user id. 2019-08-11 10:06:35 +02:00
FlightControl
eebc5d90d1 - Fixed Messaging of A2G defenses to players within the same coalition. Now the callsign will be used to communicate, not the user id. 2019-08-11 10:06:16 +02:00
FlightControl
596b3ddb4a Fixes issue with SetDetectionLimit
Optimized the detection intervals + fixed bugs with player detection.
2019-08-11 09:46:25 +02:00
FlightControl
e770af9fc9 Optimized the detection intervals + fixed bugs with player detection. 2019-08-11 09:45:28 +02:00
FlightControl
d3c76da7a2 Fixes issue with SetDetectionLimit 2019-08-11 08:32:34 +02:00
FlightControl
5f9880f17d - Implemented attack nearest target.
- Implemented attack nearest ground target.
- Implemented attack nearest air target.
- Report all targets.
- Report only ground targets.
- Report only ground targets with radar.
- Report only air targets.
- Sort targest from nearest to furthest.
- Improved menu system.
- Improved detection of ground targets and air targets and reporting.
- Added the task TASK_CAPTURE as part of the reporting framework.
- Improved the detection of targets. Now targets with specific detection methods are correctly detected. f.e. disabling DLINK will now work correctly!

All these fixes are great improvements to the AI_ESCORT framework!
2019-08-10 15:42:00 +02:00
FlightControl
d6b1018700 - Implemented attack nearest target.
- Implemented attack nearest ground target.
- Implemented attack nearest air target.
- Report all targets.
- Report only ground targets.
- Report only ground targets with radar.
- Report only air targets.
- Sort targest from nearest to furthest.
- Improved menu system.
- Improved detection of ground targets and air targets and reporting.
- Added the task TASK_CAPTURE as part of the reporting framework.
- Improved the detection of targets. Now targets with specific detection methods are correctly detected. f.e. disabling DLINK will now work correctly!

All these fixes are great improvements to the AI_ESCORT framework!
2019-08-10 15:41:34 +02:00
Frank
f934c8cb77 Update Warehouse.lua 2019-08-08 23:27:45 +02:00
Frank
db4b5383fa Merge branch 'develop' into FF/Develop 2019-08-07 22:58:07 +02:00
Frank
4982cabcfb AIRBOSS v1.0.7
Corrected foul deck wave off.
2019-08-07 22:17:16 +02:00
FlightControl
5594cd8fdf Removed the escorts to detect targets using DLINK. Now escorts will only report targets that were detected by its own onboard detection methods. 2019-08-07 16:42:08 +02:00
FlightControl
fbb32a9e45 Removed the escorts to detect targets using DLINK. Now escorts will only report targets that were detected by its own onboard detection methods. 2019-08-07 16:41:21 +02:00
FlightControl
1c559110a2 Merged 'develop'. 2019-08-07 16:08:10 +02:00
Frank
6f170db344 Update SWAPR.lua 2019-08-05 21:37:28 +02:00
Frank
27a1401ae1 SWAPR v0.0.2 2019-08-05 18:04:59 +02:00
Frank
debf784d0c SWAPR v0.0.1 2019-08-05 11:15:45 +02:00
FlightControl
5a97f68778 Escort menus are deleted once the escort crashes. 2019-08-02 21:10:56 +03:00
FlightControl
d2ef75b22c Fixed nasty bug where the same plane would be chosen all the time for request spawns. Root cause was the inherit. self variables were also copied during inherit of the chile object, resulting in a bad bad bad behaviour.
Also cleaned up the menus once the player exists the client. When rejoining, it will only see the menu of the current or requested escorts.
I still need to fix a method to renew unused (but alive and intact) escorts for gamey play.
2019-08-01 21:17:46 +03:00
FlightControl
115743263b Fixed nasty bug where the same plane would be chosen all the time for request spawns. Root cause was the inherit. self variables were also copied during inherit of the chile object, resulting in a bad bad bad behaviour. 2019-08-01 21:16:01 +03:00
FlightControl
59447999f0 Updates 2019-08-01 18:29:57 +03:00
Frank
4554ac1f3e Merge pull request #1188 from FlightControl-Master/FF/Develop
AIRBOSS v1.0.6
2019-07-28 22:49:25 +02:00
Frank
d08d8db298 AIRBOSS v1.0.6
- Added Marshal radial to skipper menu.
- Adjusted grading and groove time start for LUL issue.
2019-07-28 22:43:10 +02:00
Frank
099f95f3cc Merge pull request #1187 from FlightControl-Master/FF/Develop
Misc Updates and Fixes
2019-07-22 21:45:06 +02:00
Frank
20cf69c182 Update Airboss.lua 2019-07-22 21:20:05 +02:00
Frank
f931af2ee9 Updates 2019-07-22 21:06:12 +02:00
Frank
96fe9d51d6 Typo bug fix 2019-07-22 16:55:17 +02:00
Frank
d264c6f6a5 Update RecoveryTanker.lua 2019-07-21 23:59:35 +02:00
Frank
e17e635710 Updates 2019-07-21 23:59:05 +02:00
Frank
d4b9fc9e40 Updates 2019-07-21 23:35:09 +02:00
Frank
d66672a29c Merge pull request #1184 from FlightControl-Master/FF/Develop
Updates
2019-07-12 22:27:17 +02:00
Frank
345b0055f3 Update Fox.lua 2019-07-12 21:53:52 +02:00
Frank
7fdc049079 Update Fox.lua 2019-07-12 20:39:07 +02:00
Frank
5caab9c6f3 FOX
- adjustment of destruction distance
- tracking missiles with "unknown" target
2019-07-12 18:38:13 +02:00
Frank
03042c8282 AIRBOSS v1.0.4
One stack per flight group in CASE II/III
2019-07-12 09:41:13 +02:00
Frank
c46028ff2d FOX v0.6.0
FOX v0.6.0
- Missile target constantly updated.
- Increased safety distance to 200 m.
- Added safety distance for BIG missiles > 50 kg TNT as 400 m.
- More output to dcs.log file.

SPAWN
- enabled uncontrolled

UTILS
- LLDMS accurracy fix.

AI_A2A_DISPATCHER
- Squadron visible improvements

ZONE
- Added MarkZone function for F10 zone markings.

AI_PATROL_ZONE
- Some WP code improvements.
2019-07-10 22:29:49 +02:00
Frank
bd83344c21 Merge pull request #1179 from FlightControl-Master/FF/Develop
CAP
2019-07-06 23:43:21 +02:00
Frank
183c05bcf5 CAP
AI_A2A_PATROL
- Added CAP coordinates option.

AI_A2A_DISPATCHER
- Added CAP coordinates option.
2019-07-06 23:39:48 +02:00
Frank
a94591d811 Merge pull request #1178 from FlightControl-Master/FF/Develop
CAP
2019-07-06 20:27:50 +02:00
Frank
6973e8c028 CAP
AI_A2A_PATROL:
- Added optional race track pattern.

AI_A2A_DISPATCHER:
- Added option for CAP patrolling in a race track pattern.
2019-07-06 20:23:07 +02:00
Frank
ab0b3f9b91 Merge pull request #1177 from FlightControl-Master/FF/Develop
Fixes and Docs
2019-07-05 20:47:12 +02:00
Frank
6f140be710 Update
A2A_DISPATCHER
- Docs

CONTROLLABLE
- OptionROEOpenFireWeaponFree()
- Fixed OptionRTBBingoFuel()
2019-07-05 20:01:26 +02:00
Frank
8780ec3687 Docs
Updated docs and function argument description of DETECTION and A2A_DISPATCHER.
2019-07-03 22:13:56 +02:00
FlightControl
1afe1c48b4 Updates - Much improved version! 2019-07-01 17:29:23 +03:00
Frank
14b834e8ad Merge pull request #1175 from FlightControl-Master/FF/Develop
SPOT
2019-06-30 20:07:38 +02:00
Frank
0b53bd8f7e Merge branch 'develop' into FF/Develop 2019-06-30 17:34:22 +02:00
Frank
376a3553fa SPOT
- Fixed bug in LaseOnCoordinate.
- Minor stuff.
2019-06-30 17:34:00 +02:00
FlightControl
35653898cd WIP 2019-06-28 17:15:40 +03:00
Frank
ab1ba9595d Merge pull request #1174 from FlightControl-Master/FF/Develop
MISC Fixes and Improvements
2019-06-26 19:56:15 +02:00
Frank
2a7b9cf898 DETECTION, SET, ARTY, WAREHOUSE, AIRBOSS, A2A_DISPATCHER
DETECTION
- Fixed bug with late activated groups.

ARTY v1.1.2
- Added attack group task for ships.
- Added respawn option.

WAREHOUSE v0.9.5
- Added respawn option.

AIRBOSS v1.0.3
- Recovery time extended if flights are still in the pattern.

SET
- Added CountAlive() function.

A2A_DISPATCHER
- Bug fixes in :ParkDefender() function.
2019-06-26 19:19:17 +02:00
Frank
1224fb2d5a Merge branch 'develop' into FF/Develop 2019-06-24 23:17:32 +02:00
Frank
ac1d07ec66 updates 2019-06-24 23:17:18 +02:00
Frank
1a534108bf Updates
ARTY v1.1.0
- added attack group
- target data struckture

WAREHOUSE v0.9.4
- assignment as descriptor

AIRBOSS v1.0.3
- removed warehouse table
- no stoping of recovery window if pattern is not empty
- changes in patrol route
2019-06-24 14:19:36 +02:00
Frank
c88f7bede5 SPOT
LaseCoordinate
Alarm state ships
zonRadius in task fire at point.
2019-06-24 13:38:01 +02:00
Frank
598ef58849 Merge pull request #1172 from FlightControl-Master/FF/Develop
Rescue Helo v1.0.7
2019-06-21 20:04:00 +02:00
Frank
2a485ef85a Rescue Helo v1.0.7
RESCUEHELO v1.0.7
Compatibility fix for AI_FORMATION changes.
2019-06-21 19:57:55 +02:00
Frank
98ce42e9aa Merge branch 'develop' into FF/Develop 2019-06-21 17:26:10 +02:00
FlightControl
3612d5d171 Fixes 2019-06-20 21:50:52 +03:00
FlightControl
83f8e76584 Fixed error in SPAWN logic. Sorry. fixed now. 2019-06-20 20:42:47 +03:00
FlightControl
a108751e0c Updates 2019-06-19 18:32:39 +03:00
FlightControl
6f4d6c3daf Updates 2019-06-19 18:32:21 +03:00
FlightControl
a907369082 FlightMode 2019-06-19 18:17:12 +03:00
FlightControl
bc9060adfc Initial flight mode is formation. 2019-06-19 18:13:00 +03:00
FlightControl
4296dbade7 Small fixes 2019-06-19 06:57:40 +03:00
FlightControl
b1cee9969f Fixes 2019-06-19 05:17:51 +03:00
FlightControl
9c0c4a11b1 Finish Feature-Escort 2019-06-18 20:22:38 +03:00
FlightControl
b4324cb057 Optimized inclination for formation flying. 2019-06-18 20:18:43 +03:00
FlightControl
1d4dad0180 Updates 2019-06-18 20:03:24 +03:00
FlightControl
1e0e67c13d Updates 2019-06-18 07:06:00 +03:00
FlightControl
9c523cad52 Updates 2019-06-17 20:25:50 +03:00
FlightControl
6a1bf50700 Updated 2019-06-17 10:19:15 +03:00
FlightControl
d34cff1675 Temporary 2019-06-17 09:00:45 +02:00
FlightControl
0a027caa47 Test 2019-06-15 12:47:24 +02:00
Frank
10bfefdd06 pseudoATC
- Fixed bug for ReportBR.
2019-06-08 23:06:45 +02:00
Frank
ead1aed8d5 Merge pull request #1167 from FlightControl-Master/FF/Develop
Airboss v1.0.2
2019-06-04 17:39:33 +02:00
Frank
bc229f1a86 Update Warehouse.lua 2019-06-04 17:36:23 +02:00
Frank
94f9cb4a79 Update Airboss.lua 2019-06-03 23:18:54 +02:00
Frank
acd5b1bdca minor 2019-06-03 22:58:53 +02:00
Frank
9fed91b16f AIRBOSS v1.0.2
- Added skipper menu.
- Added turn help waypoints.
2019-06-02 23:12:08 +02:00
Frank
1627c92460 Airboss v1.0.1 2019-06-01 23:25:44 +02:00
FlightControl
7bae22248a Optimizations ROE and ROT and flight modes. 2019-06-01 08:06:18 +02:00
FlightControl
7b5b57c087 updates 2019-05-31 16:35:35 +02:00
Frank
271217cf04 Update Warehouse.lua 2019-05-28 23:58:14 +02:00
FlightControl
c5236d337e Updates 2019-05-28 20:44:51 +03:00
Frank
747bb949da Merge pull request #1164 from FlightControl-Master/FF/Develop
WH v0.9.1
2019-05-28 19:04:52 +02:00
Frank
a53bb1aac6 WH v0.9.1
- Added request as parameter in AssetSpawned event.
2019-05-28 00:24:47 +02:00
FlightControl
d3ecbac40a Updates for mission mode. 2019-05-27 17:10:19 +02:00
Frank
28fe8c5a0e Merge pull request #1162 from FlightControl-Master/FF/Develop
RANGE 2.1.2
2019-05-27 14:32:57 +02:00
Frank
6d79bbfb45 RANGE 2.1.2
fixes
2019-05-27 14:15:17 +02:00
Frank
944c76c250 Merge pull request #1161 from FlightControl-Master/FF/Develop
FOX Missile Trainer and Misc
2019-05-26 21:30:15 +02:00
Frank
256ef2c7b4 Update Warehouse.lua 2019-05-26 21:25:11 +02:00
thebgpikester
2a0ae95e3e Merge pull request #1160 from FlightControl-Master/wingthor_fixes
Wingthor fixes
2019-05-26 16:06:01 +01:00
Wingthor
604ff07933 fix issue #948 in Scoring.lua
Changed ScoreGoals to PenaltyGoals in function CORING:ReportScoreAllSummary( PlayerGroup )
2019-05-26 16:49:56 +02:00
thebgpikester
be8fc4e2e6 Merge pull request #1151 from TommyC81/patch-1
Corrected AIRBASE name comment.
2019-05-26 15:31:07 +01:00
thebgpikester
e92a625c5c Merge pull request #1142 from FlightControl-Master/wingthor_fixes
Wingthor fixes
2019-05-26 15:29:09 +01:00
Frank
e141823556 FOX Missile Trainer
FOX v0.5.0
- Added new missile trainer.
RANGE v2.1.1
- Fixed bugs.
WAREHOUSE v0.9.0
- Added events AssetSpawned and AssetLowFuel
- Improved Arrived function.
2019-05-26 12:15:21 +02:00
Frank
fbd0e9839d Merge pull request #1159 from FlightControl-Master/FF/Develop
Fixed Database OnEventNewZone function
2019-05-23 21:16:21 +02:00
Frank
889ce8f64b Fixed Database OnEventNewZone function 2019-05-23 21:10:48 +02:00
FlightControl
6be56b1b86 Progress 2019-05-23 16:33:47 +03:00
FlightControl
78a5e89928 Synth 2019-05-17 21:12:02 +02:00
FlightControl
e09a02ec14 Escort menus reworked. It took a while (sorry), was involved in many projects in RL. 2019-05-13 11:22:49 +02:00
Frank
32ff4db36a Merge pull request #1154 from FlightControl-Master/FF/Develop
MISC
2019-05-07 22:06:42 +02:00
Frank
9bb1c83999 MISC
RANGE v2.1
- Added events when players enter/exit range zone.
- misc

RADIO
- Added RADIOQUEUE class.

WAREHOUSE v0.7.0
- Fixed bug in aircraft speed.
- Decreased output if queues are empty.
2019-05-07 22:00:07 +02:00
FlightControl
93951dd4b4 WIP, not for playing. 2019-05-04 07:10:09 +02:00
FlightControl
1440f6c626 WIP, not for play. 2019-05-03 19:11:53 +02:00
Frank
aacf83fd11 Merge pull request #1153 from FlightControl-Master/FF/Develop
RANGE 2.0
2019-05-01 18:00:56 +02:00
Frank
6c35892f05 RANGE 2.0
Range v2.0.0
- Turned into FSM.
- Added radial to bomb impact info.
- Added option to add coordinates as bomb target.
- Added option to save bombing results to file.

AIRBOSS v1.0.0
- Added option to disable welcome message.
- Increased output load/save.
2019-05-01 17:56:25 +02:00
FlightControl
3657a19645 Updates of working version. 2019-04-26 18:42:42 +02:00
FlightControl
ab423b3ba4 First working version of AI_ESCORT_REQUEST. 2019-04-25 18:52:46 +02:00
FlightControl
b4d6f78e42 WIP, need to make a new class next week, AI_ESCORT_REQUEST. Need to solve the issue with the formation flying uof multiple escort classes working together. Planes may not crash into each other. 2019-04-25 08:59:07 +03:00
TommyC81
49217291ad Corrected AIRBASE name comment.
Corrected array name for Bandar-e-Jask Airfield.
2019-04-24 17:22:41 +04:00
FlightControl
315d6bf82a ParkAtAirbase new method. 2019-04-23 20:33:21 +03:00
Frank
486fc83323 Merge pull request #1149 from FlightControl-Master/FF/Develop
Ops and Range
2019-04-23 19:17:49 +02:00
Frank
3dbb013e16 Ops and Range
RANGE v1.3.0
- Generalized weapon handling.
- Decreased time before weapon tracking starts from 1.0 to 0.1 sec.

RESCUE HELO v1.0.6
- Got rid of IsAlive() check in rescue operation.

AIRBOSS v0.9.9.9.9
- Fixed bug in trap sheet output.
2019-04-23 19:13:03 +02:00
FlightControl
b21ff707ba Updates escort before plane trip. 2019-04-23 19:59:11 +03:00
FlightControl
d985d9a41d WIP 2019-04-16 19:49:46 +02:00
Frank
178ee09614 Merge pull request #1145 from FlightControl-Master/FF/Develop
Persian Gulf Airbases Update (Final)
2019-04-13 10:15:31 +02:00
Frank
95b67550eb Persian Gulf Airbases Update (Final)
Thanks to Shadowze.
2019-04-13 10:07:55 +02:00
Frank
48e0f8add6 Merge pull request #1144 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.8
2019-04-12 22:23:07 +02:00
Frank
d4f6ba2bdb Airboss v0.9.9.8 2019-04-12 21:59:53 +02:00
Frank
e26b2f4919 Merge branch 'develop' into FF/Develop 2019-04-12 17:57:15 +02:00
Frank
208761fa46 Static and Delay 2019-04-11 21:46:54 +02:00
FlightControl
03c47cc9f9 documentation 2019-04-10 18:49:05 +02:00
funkyfranky
eb62810911 Range 2019-04-10 16:24:45 +02:00
FlightControl
bf449492b5 Updated approach speed logic to prevent airplanes to fly beyond the hold position. 2019-04-09 17:45:29 +02:00
funkyfranky
4c08002d3e AB 0998 2019-04-09 14:56:20 +02:00
FlightControl
5e67861ea9 Improvements 2019-04-08 17:55:24 +02:00
FlightControl
c657ba9218 Added different formations to the menus. 2019-04-08 06:42:00 +02:00
FlightControl
ddfc22bb50 wip 2019-04-08 06:41:22 +02:00
FlightControl
f9f4b3a9fd wip 2019-04-08 05:39:57 +02:00
FlightControl
0e14624d39 wip 2019-04-07 11:07:20 +02:00
FlightControl
f1d217b6d7 wip 2019-04-07 08:31:22 +02:00
Wingthor
8126b198ba Fixed bug with SPAWN:GetLastAliveGroup() issue 1141
Changed line 2208
2019-04-07 02:56:41 +02:00
Wingthor
c2b46ceea1 Fixes GitHub issue 1140
SPAWN: GetLastAliveGroup() #1140 generating a nil value...

  "attempt to index field 'SpawnTemplatePrefixself' (a nil value)"
2019-04-07 02:04:08 +02:00
FlightControl
fcceb278ec First version 2019-04-06 16:34:00 +02:00
FlightControl
7a89960d21 Updates 2019-04-06 16:33:22 +02:00
FlightControl
4abdad5f35 First version 2019-04-06 13:04:28 +02:00
Frank
6593a262bb Update Airboss.lua 2019-04-03 23:36:22 +02:00
Frank
b2f6b141d4 Merge pull request #1138 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.7
2019-04-02 21:29:36 +02:00
funkyfranky
4bcf43122f AB 2019-04-02 16:27:41 +02:00
Frank
70f30d2c53 AIBOSS v0.9.9.7 2019-04-01 18:28:17 +02:00
Frank
a29a8d7d81 AIBOSS v0.9.9.7 2019-04-01 18:17:52 +02:00
Frank
77f1bb4fdd Merge branch 'develop' into FF/Develop 2019-03-31 21:58:28 +02:00
Frank
53166b555f AIRBOSS v0.9.9.7 2019-03-31 21:55:30 +02:00
funkyfranky
5f58fd3550 AB 0996w 2019-03-28 16:35:38 +01:00
Frank
b7b6034d76 AIRBOSS v0.9.9.6w 2019-03-27 23:29:41 +01:00
FlightControl
59974ea33e New Zone Detection module. 2019-03-27 19:18:12 +01:00
funkyfranky
e4935d8717 Merge branch 'FF/Develop' of https://github.com/FlightControl-Master/MOOSE into FF/Develop 2019-03-27 16:24:17 +01:00
funkyfranky
de0c8e3789 AB 0.996w 2019-03-27 16:23:39 +01:00
FlightControl
bd290ffdab Fixing hit problem in multi player for database when hitting static objects. 2019-03-23 11:24:04 +01:00
FlightControl
bfb11b6f79 Updates on goal logic 2019-03-23 10:45:41 +01:00
Frank
8667c98449 Merge branch 'develop' into FF/Develop 2019-03-22 22:43:57 +01:00
Frank
4c72496599 Merge pull request #1133 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.6
2019-03-22 22:42:40 +01:00
Frank
aa8498ce0b AIRBOSS v0.9.9.6 2019-03-22 22:05:25 +01:00
funkyfranky
ec155ed80c AB v0.9.9.6w 2019-03-22 16:23:22 +01:00
FlightControl
f849732f55 Various improvements 2019-03-21 07:01:24 +01:00
FlightControl
9856a8625d Goal 2019-03-21 06:57:04 +01:00
FlightControl
b1e99dc317 Goal 2019-03-21 06:31:13 +01:00
FlightControl
310ba9bb8a Reverse 2019-03-21 06:18:48 +01:00
FlightControl
565a3062cb Fixing goal state machine rule 2019-03-21 06:11:59 +01:00
FlightControl
e8b74f0fc7 handling static in scan 2019-03-21 06:03:13 +01:00
FlightControl
ca65154ecd Fixes 2019-03-21 05:49:48 +01:00
FlightControl
e996a30333 Updates 2019-03-21 05:21:16 +01:00
Frank
e33a38556e Merge pull request #1132 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.5
2019-03-20 22:17:15 +01:00
Frank
3f2b3e0e1a AIRBOSS v0.9.9.5
- Added Raynor LSO/Marshal voice over parameters.
- Added nil checks for events.
2019-03-20 21:59:32 +01:00
Frank
dab0aef1e3 AIRBOSS v0.9.9.5 2019-03-20 21:57:18 +01:00
FlightControl
d9fb9c4928 Report Mission fix 2019-03-20 18:36:55 +01:00
FlightControl
964a6d0708 Debug 2019-03-20 18:15:37 +01:00
FlightControl
985282ba55 Debug 2019-03-20 18:03:24 +01:00
funkyfranky
8534afe0e3 AB 2019-03-20 16:22:00 +01:00
Frank
a70fa066fe AB 0995 2019-03-20 00:12:04 +01:00
FlightControl
3f8468dcc7 Fixed shortest distance to airbase. 2019-03-19 21:50:34 +01:00
FlightControl
d9d9235fba Great new additions:
- Defense tactics (SetDefenseApproach) now can be:
  AI_A2G_DISPATCHER.DefenseApproach.Random -- random detected item
  AI_A2G_DISPATCHER.DefenseApproach.Distance -- shortest distance between defense point and detected item.
2019-03-19 19:34:28 +01:00
funkyfranky
5e20b244c5 AB 0995w 2019-03-19 16:32:43 +01:00
Frank
40ebc4e990 AB v0.9.9.5 2019-03-19 01:18:34 +01:00
FlightControl
70e7857b62 Implemented the linking of TASK_CAPTURE_DISPATCHER and AI_A2G_DISPATCHER. 2019-03-18 19:53:12 +01:00
FlightControl
02a486e457 In progress. 2019-03-18 18:04:08 +01:00
FlightControl
9e09a5bac9 First version of new Feature-Detection-Zones 2019-03-17 08:26:16 +01:00
FlightControl
ece7fc3ea9 Threat level report for TASK_CAPTURE_ZONE 2019-03-17 08:20:42 +01:00
FlightControl
0ea5c7fa48 Updated bug in Detection and other classes. Now only alive units are considered when broadcasting a message to a SET_GROUP. Made DESIGNATE crash in certain cases. 2019-03-17 07:33:28 +01:00
Frank
3f64a1b33f Merge pull request #1130 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.4
2019-03-16 22:38:23 +01:00
Frank
02ee12402a AIRBOSS v0.9.9.4
**AIRBOSS v0.9.9.4**
- Script now supports human RIOs.
- Added optional prefix for trap sheet files.
- Fixed invalid characters in trap sheet file name cause script to crash (untested). Data will not be saved at all.
- Inverted sign of line up error in trap sheet data to make it more intuetive.
- Player who leave are deleted from self.players table.
2019-03-16 22:34:03 +01:00
FlightControl
837361e899 Updates 2019-03-16 12:57:14 +01:00
FlightControl
cff4f60923 Improvements on reporting. 2019-03-16 12:13:39 +01:00
FlightControl
fb23ac1d55 Updates to make the tasking work correctly. 2019-03-16 09:51:09 +01:00
FlightControl
87a44f7f7f Still need to fix that index issue. 2019-03-15 13:08:48 +01:00
FlightControl
3b520ab0c4 New detection method based on zones and scanning. 2019-03-15 12:39:06 +01:00
FlightControl
044b5cba7c Updates 2019-03-15 11:01:06 +01:00
FlightControl
0f9591464a First version 2019-03-15 09:57:50 +01:00
Frank
6a301060bf Merge pull request #1129 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.3
2019-03-13 20:56:46 +01:00
Frank
1e0b51a0a9 AIRBOSS v0.9.9.3
- Added F-14 support.
2019-03-13 20:52:57 +01:00
FlightControl
e19f0402da Only auto assign tasks to planned; replanned or assigned tasks. 2019-03-10 16:48:38 +01:00
FlightControl
925ce3ad63 Auto assignment of tasks with prioritization of an auto assign method. The default assign method is random, which is set at the command center. Each task type implements a prioritization mechanism which calculates the priotity of the task based on various methods: random, distance or priority. The distance is calculated from the task coordinate from the command center location.
Also fixed in a first try the route bug on Controllables.
2019-03-10 16:41:53 +01:00
FlightControl
afc918c5e5 Updates in defense system. Added SetSquadronEngageProbability method to decide per squadron what is the probability when it will engage. 2019-03-09 17:29:18 +01:00
FlightControl
10b96f6cce Updates to decision engine of the AI_A2G_DISPATCHER 2019-03-09 10:09:03 +01:00
FlightControl
d72e89d52b Fixes in AI_A2G_DISPATCHER 2019-03-09 06:32:11 +01:00
FlightControl
ef1a9330a4 Updates, many fixes. Now also the communication to the players is working. 2019-03-08 10:03:58 +01:00
FlightControl
eee1aca14f Disabled SetSquadronVisible due to a bug fix that is required. 2019-03-07 14:02:49 +01:00
FlightControl
c8c95e063d Now the task routing is also working towards the capture zones. 2019-03-07 09:54:33 +01:00
FlightControl
0af9e57b82 Now the task routing is also working towards the capture zones. 2019-03-07 09:54:15 +01:00
FlightControl
e0899a8a9d Merge remote-tracking branch 'origin/develop' into develop 2019-03-06 22:41:38 +01:00
FlightControl
093dadc0f6 Finish Task_zone_capture_dispatcher 2019-03-06 22:40:59 +01:00
FlightControl
23f92e49dd New TASK CAPTURE DISPATCHER class.
This class allows to send capture tasks to players.
It needs a ZONE_CAPTURE_COALITION class to function.
2019-03-06 22:40:44 +01:00
Frank
d1c909e9ed Merge pull request #1128 from FlightControl-Master/FF/Develop
RAT v2.3.7
2019-03-05 23:24:26 +01:00
Frank
5dbc3b1ba1 Update RAT.lua 2019-03-05 23:09:43 +01:00
Frank
85febea3d9 RAT v2.3.7
Added EPLRS datalink option.
2019-03-05 23:07:35 +01:00
FlightControl
dca772cac6 Automatic assignment of tasks; and automatic accepts of tasks. Documented. Tested. Works. 2019-03-05 09:56:51 +01:00
FlightControl
26347ce779 Automatic assignment of tasks; and automatic accepts of tasks. Documented. Tested. Works. 2019-03-05 09:56:19 +01:00
Frank
b49f3c5b0c Merge pull request #1126 from FlightControl-Master/FF/Develop
Carrier Ops Update
2019-03-04 23:18:09 +01:00
Frank
79be7cbd2c Update Airboss.lua 2019-03-04 23:08:59 +01:00
FlightControl
6fe8667885 Some important fixes that improves the routing of the engagements of aircraft. when ground forces move, the aircraft engaging will approach correctly.
also added the squaddrons with resources in the tactical information panel.
and for patrols, now the amount of resources are correctly calculated.
2019-03-04 20:27:40 +01:00
Frank
b56d62c777 Carrier Ops Update
AIRBOSS v0.9.9.2
RECOVERYTANKER v1.0.7
2019-03-04 17:50:45 +01:00
FlightControl
6f13aa2c5b These changes will blast the hell out of A2G... this is going to be great. 2019-03-03 19:46:51 +01:00
Frank
d4510af249 Merge pull request #1125 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9.1
2019-02-28 23:15:12 +01:00
Frank
fb784a9a96 AIRBOSS v0.9.9.1 2019-02-28 23:06:03 +01:00
Frank
25c7b1ddac AIRBOSS v0.9.9.1 2019-02-28 22:38:17 +01:00
Frank
71c7e15fc3 Update Airboss.lua 2019-02-27 23:32:46 +01:00
funkyfranky
e6e0cf5977 AB 0991 2019-02-27 16:33:24 +01:00
funkyfranky
775757e657 AB Docs 2019-02-26 16:58:57 +01:00
Frank
1a303ec7c2 Merge pull request #1123 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.9
2019-02-25 20:34:16 +01:00
Frank
c16edd65b0 AIRBOSS v0.9.9 2019-02-25 20:23:03 +01:00
FlightControl
1ff41ec7ec Improvements 2019-02-25 19:16:18 +01:00
FlightControl
d874456361 Optimized attack. Prevent airborne defenders to fly over enemy zones to target areas behind enemy lines! This is an important feature. I still need to optimize the attack for SEAD; so when SEAD is behind enemy lines and crossing an enemy area that is in line of the attack; the SEAD won't be engaged. Further optimization is required, but it works already quite well! 2019-02-23 14:57:06 +01:00
Frank
6853eecffa Merge pull request #1122 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.8
2019-02-21 10:20:59 +01:00
Frank
71001adc5e AIRBOSS v0.9.8 2019-02-21 09:55:57 +01:00
Frank
b6a19d34c6 AIRBOSS v0.9.8 2019-02-20 09:41:16 +01:00
FlightControl
1364115b07 Updates in docs 2019-02-19 17:48:57 +01:00
Frank
48c7254c84 Update Airboss.lua 2019-02-19 10:41:58 +01:00
Frank
60b6d2ade0 Update Airboss.lua 2019-02-17 23:16:07 +01:00
funkyfranky
df43ff0841 AB v0.9.8w 2019-02-15 17:00:22 +01:00
Frank
5ee0ae44cd Update Airboss.lua 2019-02-15 01:23:42 +01:00
funkyfranky
9df35840fe AB v0.9.8w 2019-02-14 16:54:59 +01:00
Frank
cff9557217 AB v0.9.8 wip 2019-02-14 00:53:22 +01:00
funkyfranky
b5b6d398ae AB 0.9.8w 2019-02-13 16:46:15 +01:00
Frank
aba0c19215 AB v0.9.8wip 2019-02-13 00:15:31 +01:00
funkyfranky
5050371e0d AB v0.9.8w 2019-02-12 16:26:44 +01:00
Frank
81b36ef37d Merge pull request #1121 from FlightControl-Master/FF/Develop
AIBOSS v0.9.7
2019-02-11 13:57:48 +01:00
Frank
59e219e3d6 WAREHOUSE v0.6.9
Fixed memory issue.
2019-02-11 12:31:28 +01:00
Frank
412d9e7a82 AIRBOSS v0.9.7 2019-02-11 12:24:52 +01:00
funkyfranky
d01b55c790 AB v0.9.7w 2019-02-08 16:41:23 +01:00
Frank
5ed768065c Update Airboss.lua 2019-02-08 01:04:07 +01:00
funkyfranky
e64703de03 AB v0.96w 2019-02-07 16:16:17 +01:00
Frank
71126eb34d AIRBOSS v0.96 2019-02-06 23:50:41 +01:00
funkyfranky
6992e47a66 AB 0.95wip 2019-02-06 16:16:29 +01:00
Frank
70d1f96681 AB v0.95wip 2019-02-06 00:12:24 +01:00
funkyfranky
83ed456c29 AB 0.9.5wip 2019-02-05 16:41:20 +01:00
Frank
26607776a2 Merge pull request #1120 from FlightControl-Master/FF/Develop
AIRBOSS v0.95
2019-02-04 23:21:24 +01:00
Frank
7a075e73ca AIRBOSS v0.95
- Fixed bug in counting units
- Carrier will only turn into the wind if wind > 0.1 m/s
2019-02-04 23:15:12 +01:00
Frank
6c15091df2 Merge pull request #1119 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.4
2019-02-04 20:37:32 +01:00
Frank
e063379cd8 AIRBOSS v0.9.4
- Added AV-8B and USS Tarawa.
- Corrected A-4E AoA threshold.
- Other minor adjustments.

RADIO: Fixed frequency check.
2019-02-04 20:34:45 +01:00
Frank
3a49f2258b Merge pull request #1118 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.3
2019-01-31 21:55:42 +01:00
Frank
dddf056b20 AIRBOSS v0.9.3
- Added tower frequency.
- Added AI exclude set.
2019-01-31 21:54:44 +01:00
Frank
bdbfed13ff Merge pull request #1114 from 131stGutts/Functional-PseudoATC_Allow_Human_Group
Fix bug when 2 humans are in the same group of planes
2019-01-31 19:15:45 +01:00
Frank
9eb0800b66 Merge pull request #1113 from 131stGutts/Functional-ATC_Ground_AlertFreqCustom_PersianGulf
Functional atc ground alert freq custom persian gulf
2019-01-31 18:26:21 +01:00
Frank
713ec61031 Merge branch 'develop' into Functional-ATC_Ground_AlertFreqCustom_PersianGulf 2019-01-31 18:25:25 +01:00
Frank
264e84649e Merge pull request #1112 from FlightControl-Master/FF/Develop
Carrier Ops
2019-01-31 14:21:49 +01:00
Frank
c4016cfa4d RESCUEHELO v1.0.5
- Added Returned event.
- Fixed automatic RTB on low fuel bug.
2019-01-31 13:43:17 +01:00
Frank
1cb248692b CONTROLLABLE
Setting output of SetTask from self:I() to self:T(). AI_FORMATION class (used by RESCUEHELO) calls the function twice per second and is spamming the DCS log file with messages and causing large file I/O.
2019-01-30 20:18:47 +01:00
Frank
331315a83e Merge branch 'develop' into FF/Develop 2019-01-30 20:11:11 +01:00
Frank
e325b2192b AIRBOSS, RECOVERYTANKER, RESCUEHELO, RANGE
AIRBOSS v0.9.3
RECOVERYTANKER v1.0.6
RESCUEHELO v1.0.4
RANGE v1.2.4
SPAWN added modex
GROUP added modex respawn
2019-01-30 20:10:41 +01:00
FlightControl
86094bc6d2 Improvements in the attack methods! This is going to be great!
# Conflicts:
#	Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
2019-01-29 20:51:03 +01:00
FlightControl
f2b2b1974d Improvements in the attack methods! This is going to be great! 2019-01-29 19:58:06 +01:00
Frank
080c496cc3 Merge pull request #1111 from FlightControl-Master/FF/Develop
RECOVERYTANKER v1.0.5
2019-01-29 17:11:58 +01:00
Frank
bcfa0f9ac5 RECOVERYTANKER v1.0.5
Recoverytanker:
- Added AWACS roll
- Added function to set callsign

UTILS
- Added callsigns enumerator

CONTROLLABLE
- Added command SetCallsign
2019-01-29 17:07:48 +01:00
Frank
746c0d25f7 Merge pull request #1110 from FlightControl-Master/FF/Develop
RECOVERYTANKER v1.0.4
2019-01-28 22:07:30 +01:00
Frank
9e450ce76b RECOVERYTANKER v1.0.4
- Fixed PG airbase enumerator names
- Rescuehelo v1.0.3
- minor stuff
2019-01-28 22:03:21 +01:00
FlightControl
77bee89ea5 Updates 2019-01-27 17:19:41 +01:00
Frank
dbc80183e6 Merge pull request #1109 from FlightControl-Master/FF/Develop
CONTROLLABLE
2019-01-27 13:09:33 +01:00
Frank
ff0371faa5 CONTROLLABLE
Fixed task bombing runway.
2019-01-27 13:07:48 +01:00
Frank
ebaddb3fe4 Merge pull request #1107 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.2
2019-01-26 23:30:29 +01:00
Frank
8f3aeeb182 AIRBOSS v0.92 2019-01-26 23:12:24 +01:00
Frank
3157e4f986 Merge branch 'develop' into FF/Develop 2019-01-23 11:24:31 +01:00
Frank
0fe18ee5b4 Merge pull request #1104 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.1
2019-01-23 11:09:11 +01:00
Frank
265d6da331 AIRBOSS v0.9.1 2019-01-23 11:00:36 +01:00
FlightControl
1c063ca308 Optimizations 2019-01-22 08:51:17 +01:00
FlightControl
5a5340431e Improvements ...
-- Implementation of queueing of aircraft launches.
-- Fixed the RTB bug resulting in "lost control".
-- Communication of AI to players (first version).
-- Aircraft engage distance calculation for each wave.
2019-01-20 17:03:18 +01:00
Frank
869a8a57e1 Merge pull request #1103 from FlightControl-Master/FF/Develop
AIRBOSS v0.9.0 and other fixes/improvements
2019-01-20 02:33:05 +01:00
Frank
dd15da28b2 AIBOSS v0.9.0
SPAWN: Added delay for respawn on landing.
RAT v2.3.5: Fixed bug for Vmax cruise.
RECOVERYTANKER v1.0.3
RESCUEHELO v1.0.2
POSITIONABLE: Added GetOffsetCoordinate function
UNIT: Added Explode function.
2019-01-20 02:02:22 +01:00
131stGutts
47778c6fe7 Set the time frequency of alert with PersianGulf
Add new functions "Start" for
  ATC_GROUND_CAUCASUS,
  ATC_GROUND_NEVADA,
  ATC_GROUND_NORMANDY,
  ATC_GROUND_PERSIANGULF

 which accept none or one parameter for setting the SCHEDULER frequency.

If none defined, value set before (0.05) is used.
The SCHEDULER is not anymore in "New" functions of ATC_GROUND_XXX but in "Start"

Usages:
atcGroundCaucasus=ATC_GROUND_CAUCASUS:New()
atcGroundCausasus:Start()

or

atcGroundCaucasus=ATC_GROUND_CAUCASUS:New()
atcGroundCausasus:Start(0.5)

Relative to PullRequest #1094, #1096, #1098
Include grammar issues fix from ticket #753
2019-01-13 16:27:58 +01:00
131stGutts
0d90eed270 Implements PersianGulf ATC_Ground supervisor
All Persian Gulf airbases and runways added so ATC_Ground supervisor
Minor correction to comment for Normandy (saying Caucasus)
2019-01-13 12:45:21 +01:00
Frank
d8147348c6 Merge pull request #1097 from FlightControl-Master/FF/Develop
AIRBOSS v0.8.1
2019-01-13 00:28:42 +01:00
Frank
6b85141a39 AIRBOSS v0.8.1
* Persistence
* Fixed menu mark zones bug
* minor things
2019-01-13 00:23:27 +01:00
131stGutts
0fcc379ede Fix bug when 2 humans are in the same group of planes
If more than one human is in a plane's group, the menu bug and a lot of PSEUDOATC menu are shown.
The new menu structure is:
- F10
-  PSEUDOATC
-   <PlayerName>
-    <PSEUDOATC Functions>

For achieving that goal, the self structure has changed from self.player[GID] to self.group[GID].player[UID]. Doing that way, if the last player pit leave the group this functionality do not generate a nil error anymore.
Some text have been changed to indicate CallSign or PlayerName.
2019-01-12 21:35:05 +01:00
Frank
f22f62339f Merge pull request #1092 from FlightControl-Master/FF/Develop
AIRBOSS v0.8.0
2019-01-10 17:50:19 +01:00
Frank
934b9d9cf1 Merge branch 'develop' into FF/Develop 2019-01-10 17:26:57 +01:00
Frank
47dd068655 AIRBOSS v0.8.0 2019-01-10 17:26:37 +01:00
FlightControl
382dfd797c Optimizations A2G 2019-01-09 21:31:22 +01:00
FlightControl
8e66031afe Optimizations A2G 2019-01-09 21:30:21 +01:00
FlightControl
eea1d56468 Optimizations A2G 2019-01-09 21:29:45 +01:00
FlightControl
6edcc58b9a updates 2019-01-09 07:36:36 +01:00
Frank
7a8197208c Merge pull request #1087 from FlightControl-Master/FF/Develop
AIRBOSS v0.7.2
2019-01-05 10:33:23 +01:00
Frank
acd6e0f423 AIRBOSS v0.7.2 2019-01-05 09:51:19 +01:00
Frank
e741e0d1d6 Merge pull request #1085 from FlightControl-Master/FF/Develop
AIRBOSS v0.7.1
2019-01-04 00:25:39 +01:00
Frank
09f28a555f AIRBOSS v0.7.1
* Fixed radio transmission subtitle bug.
* Added on-the-fly LSO grading to attitude monitor.
* Added doc menu images.
2019-01-04 00:22:42 +01:00
Frank
a9590561d2 Merge pull request #1084 from FlightControl-Master/FF/Develop
Controllable SetTask debug output
2019-01-03 15:32:10 +01:00
Frank
9700776468 Controllable log output
Controllable, changed self:E to self:F/T2 since it was spamming the DCS log.
Airboss docs.
Spawn scheduler(self,...) --> scheduler(nil,...)
2019-01-03 14:09:19 +01:00
Frank
cec6940aeb Merge pull request #1083 from FlightControl-Master/FF/Develop
AIRBOSS v0.7.0
2019-01-03 01:05:24 +01:00
Frank
e082ee17e4 Merge branch 'develop' into FF/Develop 2019-01-03 00:30:27 +01:00
Frank
2327fa7e4b AIRBOSS v0.7.0
RECOVERYTANKER v1.0.1
RESCUEHELO v1.0.1
2019-01-03 00:30:03 +01:00
FlightControl
bd25a8ccb4 Fixing too small static Moose.lua 2018-12-29 23:49:52 +01:00
FlightControl
fff0c8ccd3 New dynamic loader and adapted Moose.lua creation from Modules.lua in the MOOSE root directory. 2018-12-28 09:30:49 +01:00
FlightControl
df6a10862e New dynamic loader and adapted Moose.lua creation from Modules.lua in the MOOSE root directory. 2018-12-28 09:29:33 +01:00
FlightControl
a33b7b2769 Merged 'develop'. 2018-12-27 07:34:29 +01:00
FlightControl
5e01db8809 New stuff 2018-12-27 07:33:51 +01:00
FlightControl
32c064a1be Remove Moose.lua 2018-12-26 09:10:03 +01:00
FlightControl
610938b6e8 Changes 2018-12-26 09:06:16 +01:00
FlightControl
ea93e3863b Loader 2018-12-26 07:42:47 +01:00
Frank
ab38efd69e Merge pull request #1077 from FlightControl-Master/FF/Develop
New classes, fixes and improvements.
2018-12-26 04:50:56 +01:00
Frank
8dc5642599 AIBOSS v0.6.3
Recovery Tanker v1.0.0
Rescue Helo v1.0.0
Fixed spawn after engine shutdown bug.
Added new PG airbases.
2018-12-25 18:55:35 +01:00
Frank
d8c5ab7eae AIRBOSS v0.6.2 2018-12-23 01:30:33 +01:00
Frank
49e7a24e28 AIRBOSS v0.6.1
Lineup, glide slope fixes.
2018-12-22 15:55:43 +01:00
Frank
6f2de65b64 AIRBOSS v0.6.0 2018-12-22 09:27:51 +01:00
funkyfranky
34603d69ab AIRBOSS v0.5.9w 2018-12-21 16:17:42 +01:00
Frank
afb79391dd AIRBOSS v0.5.9 2018-12-21 00:34:25 +01:00
funkyfranky
7f1dcf93d9 AIRBOSS v0.58w 2018-12-20 16:16:29 +01:00
Frank
04722a7d83 AIRBOSS v0.5.8 2018-12-20 00:49:41 +01:00
funkyfranky
fd6a319928 AIRBOSS v0.5.7w 2018-12-19 17:02:58 +01:00
FlightControl
e91a744bd9 Fixed for A2G. Default parameters and proper amount of spawnings. Now also altitude is added for engage. And RTB has min and max speed set. Defaults of speed between 50% and 75% of maximum speed of group. And altitude defaults to 1000 and 1500 meters. 2018-12-19 07:24:01 +01:00
Frank
876b369c0d AIRBOSS v0.5.7 2018-12-19 00:40:42 +01:00
Frank
75ac76a8e5 AIBOSS v0.5.6
RESCUEHELO v0.9.9
2018-12-18 14:04:31 +01:00
FlightControl
6c4bde4ceb Fixes 2018-12-17 20:59:58 +01:00
FlightControl
cfc9c655c5 Fixes for patrol. 2018-12-17 19:39:23 +01:00
Frank
d576573cf8 RECOVERYTANKER v0.9.9
RESCUEHELO v0.9.8
2018-12-16 16:59:53 +01:00
Frank
1559f14f11 RESCUEHELO v0.9.7
- Fixed spawn bug with multiple helos.
- Adjusted default parameters.
- Changed RTB to respawn (not perfect).
- Improved documentation.
2018-12-15 23:06:36 +01:00
Frank
242151eedf AIBOSS v0.5.5 2018-12-15 08:47:01 +01:00
funkyfranky
7cf10e90f8 AIRBOSS 0.5.4w 2018-12-14 16:44:00 +01:00
Frank
5201c73d35 AIRBOSS v0.5.4 2018-12-14 00:17:11 +01:00
funkyfranky
07f313a4a6 AIRBOSS v0.5.3w 2018-12-13 16:08:59 +01:00
Frank
fa08434690 AIRBOSS v0.5.3 2018-12-12 23:38:48 +01:00
funkyfranky
8969c58ca5 AIRBOS v0.5.2w 2018-12-12 16:03:37 +01:00
FlightControl
810cebfe0a Optimizations 2018-12-12 07:31:08 +01:00
Frank
d607b96021 AIRBOSS v0.5.2 2018-12-12 00:50:42 +01:00
FlightControl
d098930351 -- New SEAD, BAI and CAS files.
-- Correct handling of task assignments.
-- Added Weapon stuff in DCS.lua.
-- Dispatcher optimizations.
2018-12-11 19:20:11 +01:00
funkyfranky
143e729a5e AIRBOSS v0.5.1w 2018-12-11 16:02:45 +01:00
Frank
5ef4b593a2 AIRBOSS v0.5.1 2018-12-11 00:19:14 +01:00
funkyfranky
7eb98c05eb AIRBOSS v0.5.0w 2018-12-10 16:59:00 +01:00
Frank
43ec58fe85 RT & RH
Recovery Tanker
Rescue Helo
2018-12-09 23:11:44 +01:00
Frank
0a5f042555 Merge branch 'FF/Development' into FF/Develop 2018-12-09 21:25:47 +01:00
Frank
44f8c2a933 WAREHOUSE v0.6.7
SPAWNSTATIC
UNIT
2018-12-09 21:16:47 +01:00
Frank
ecbdbedd96 Warehouse v0.6.6
Again
2018-12-09 19:32:39 +01:00
Frank
c6a4e4c533 Warehouse v0.6.6
Again
2018-12-09 19:32:07 +01:00
Frank
afb0a8af33 Manual diff merge from FF/Development
Hopefully cleans up the mess now.
2018-12-09 19:11:46 +01:00
Frank
8d222fd0c3 Merge branch 'FF/Development' into FF/Develop 2018-12-09 12:12:32 +01:00
Frank
9795d5655f Improvements and Fixes from FF/Develop 2018-12-09 12:01:15 +01:00
Frank
db6c7f1a2c AIROSS v0.5.0 2018-12-09 01:06:01 +01:00
funkyfranky
cfdce853a3 AIRBOSS w 2018-12-07 15:36:41 +01:00
Frank
01b2f238c5 AIRBOSS v0.4.9
RECOVERYTANKER v0.9.7
2018-12-06 23:36:37 +01:00
funkyfranky
5a327b1d6b AIRBOSS v0.4.8w 2018-12-06 16:10:17 +01:00
Frank
96b60d8ac0 AIRBOSS v0.4.8 2018-12-06 00:05:41 +01:00
Frank
e7f1d98b64 AIRBOSS v0.4.7 2018-12-05 23:58:37 +01:00
Frank
c2ddf17aa2 AIRBOS v0.4.6 2018-12-05 23:35:03 +01:00
funkyfranky
302d9dfad4 AIRBOSS v0.4.5w 2018-12-05 15:57:26 +01:00
Frank
a83008aad3 AIRBOSS v0.4.5 2018-12-04 23:37:56 +01:00
funkyfranky
047df5917a AIRBOSS v0.4.4w 2018-12-04 16:05:15 +01:00
funkyfranky
6b53e9f9a8 Merge branch 'FF/Develop' of https://github.com/FlightControl-Master/MOOSE into FF/Develop 2018-12-04 11:37:14 +01:00
funkyfranky
d66028a1b9 AIRBOSS v0.4.3w 2018-12-04 11:37:07 +01:00
Frank
51de6756a7 AIRBOSS update 2018-12-03 23:07:08 +01:00
Frank
74d97cc220 AIRBOSS v0.4.3 2018-12-02 23:48:39 +01:00
Frank
cf2ad6f277 AIRBOSS v0.4.2 2018-12-02 00:30:22 +01:00
Frank
b05d3fbde2 AIRBOSS v0.4.1 2018-11-30 23:48:59 +01:00
funkyfranky
7b0db24e00 AIRBOSS v0.4.0w 2018-11-30 16:02:53 +01:00
Frank
21f0094d7c AIRBOSS v0.4.0 2018-11-29 23:41:57 +01:00
funkyfranky
99c2e76e53 AIRBOSS v0.3.9w 2018-11-29 16:19:19 +01:00
Frank
50c40cb4e9 AIRBOSS v0.3.9 2018-11-28 23:55:14 +01:00
funkyfranky
5e8b461478 AIRBOSS v0.3.6w 2018-11-28 17:17:05 +01:00
funkyfranky
73db4b365b Merge branch 'FF/Develop' of https://github.com/FlightControl-Master/MOOSE into FF/Develop 2018-11-28 11:52:06 +01:00
funkyfranky
0cf5bee09a Pics 2018-11-28 11:52:02 +01:00
Frank
3d54d78be8 AIRBOSS v0.3.7
Zones.
2018-11-27 23:37:48 +01:00
Frank
bd537ece00 Revert "Merge branch 'develop' into FF/Develop"
This reverts commit 1f282693b3, reversing
changes made to e16f306e1d.
2018-11-27 18:38:21 +01:00
Frank
1f282693b3 Merge branch 'develop' into FF/Develop 2018-11-27 18:13:58 +01:00
Frank
e16f306e1d AIRBOSS v0.3.6 2018-11-27 00:03:45 +01:00
funkyfranky
6b24673b6f AIRBOSS v0.3.5w 2018-11-26 16:35:35 +01:00
Frank
58886dc42d AIRBOSS v0.3.5 2018-11-26 00:43:38 +01:00
Frank
8cea501748 RESCUEHELO v0.9.4 2018-11-25 11:10:11 +01:00
Frank
c5171e8722 RECOVERYTANKER v0.9.4 2018-11-25 00:05:31 +01:00
Frank
5af235a345 AIRBOSS v0.3.4 2018-11-23 23:57:54 +01:00
funkyfranky
8133ef2036 AIRBOSS v0.3.3w 2018-11-23 16:30:32 +01:00
Frank
9269e1e943 AIBOSS v0.3.3
little bug fixes
2018-11-22 23:27:43 +01:00
funkyfranky
b4c82d0aac docs 2018-11-22 16:12:00 +01:00
Frank
39ffc28cb4 AIRBOSS v0.3.2 2018-11-21 23:45:24 +01:00
funkyfranky
e23488a723 AIRBOSS v0.3.1w 2018-11-21 16:01:10 +01:00
Frank
ba1cb61f4d AIRBOSS v0.3.1
Tanker v0.9.3
* added TACAN option

Helo v0.9.3
* added respawn and uncontrolled
2018-11-21 00:55:00 +01:00
FlightControl
921ec8732c A lot of optimizations ...
- Depart from runway now works again.
 - Depart from hotspot works again.
 - Depart from ramp works again.
 - Implemented the limit, it works, but it is not waterproof (yet).
2018-11-20 20:00:38 +01:00
Frank
51a1f56011 AIRBOSS v0.3.0
RESCUEHELO v0.9.2
RECOVERYTANKER v0.9.2
2018-11-20 00:15:39 +01:00
Frank
2feb293c12 AIRBOSS v0.2.9
- Added holding check (WIP)
- Many improvents and fixes.
2018-11-18 23:57:41 +01:00
Frank
9d9f233c15 AIRBOSS v0.2.8
good commit, improved and fixed a lot of stuff.
2018-11-18 00:45:18 +01:00
funkyfranky
9ad948632c AIRBOSS v0.2.7w 2018-11-16 16:24:01 +01:00
Frank
9b40ebe00c AIRBOSS v0.2.7 2018-11-15 23:16:58 +01:00
funkyfranky
dab3ed5fbe AIRBOSS v0.2.6w 2018-11-15 16:11:14 +01:00
Frank
34d7b18c26 AIRBOSS v0.2.6 2018-11-14 23:23:46 +01:00
FlightControl
dc4115d4e3 Finish 2.4.14 2018-11-14 20:09:35 +01:00
FlightControl
5cfd0aac1c Finish 2.4.14 2018-11-14 20:09:35 +01:00
FlightControl
9481cbf7e8 Fixing issue #1054 related to ZONE_CAPTURE_COALITION going into Attacked state when the zone is attacked, meaning, when a unit is hit that is in the zone. 2018-11-14 20:01:29 +01:00
FlightControl
748f9b52aa Finish 2.4.13 2018-11-14 19:22:23 +01:00
FlightControl
c5ce3342e2 Finish 2.4.13 2018-11-14 19:22:22 +01:00
FlightControl
6c4ca046a9 Fixed problem with DESIGNATE:SetFlashStatusMenu() documentation. 2018-11-14 19:22:10 +01:00
FlightControl
256697c39e Fixing problem in SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ). 2018-11-14 18:58:44 +01:00
FlightControl
4017876fe2 Fixing problem in SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ). 2018-11-14 18:58:43 +01:00
FlightControl
9651614894 Fixing problem in SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ). 2018-11-14 18:58:08 +01:00
funkyfranky
3bc2baaf9d AIRBOSS v0.2.5w 2018-11-14 16:11:54 +01:00
Frank
fa05352882 AIRBOSS v0.2.5 2018-11-13 23:39:29 +01:00
FlightControl
849ca24ac8 Finish Feature-AI-A2G-Dispatcher 2018-11-13 20:21:00 +01:00
FlightControl
84e9d225e9 Fixing problem with squadron not found for CAS tasking. 2018-11-13 20:20:42 +01:00
funkyfranky
7b53a43c5c AB v0.2.4 2018-11-13 16:24:08 +01:00
Frank
e3121781d0 AIRBOSS v0.2.4 2018-11-13 00:00:20 +01:00
FlightControl
2c788b1b38 Finish 2.4.11 2018-11-12 19:38:49 +01:00
FlightControl
369606f0f1 Finish 2.4.11 2018-11-12 19:38:48 +01:00
FlightControl
8d4ce5e486 Fixing autolase problem... now the duration is fixed.
I think I also fixed the lase duration problem ...
2018-11-12 19:38:22 +01:00
FlightControl
1b691e5887 Documentation 2018-11-12 19:35:32 +01:00
Frank
2d7d19880f AIRBOSS v0.2.3
group fixed init heading
2018-11-11 23:55:19 +01:00
FlightControl
8b06af01b0 More documentation for A2G dispatcher. 2018-11-11 21:51:11 +01:00
FlightControl
ea3899f469 More documentation for A2G dispatcher. 2018-11-11 21:50:41 +01:00
FlightControl
adb8b6cbca Fixed overhead (again), and optimized defense radius logic. 2018-11-11 08:43:39 +01:00
FlightControl
252950af73 Fixed overhead (again), and optimized defense radius logic. 2018-11-11 08:43:20 +01:00
FlightControl
14189e1bca Images 2018-11-11 07:31:13 +01:00
FlightControl
ed60c5dca4 Images 2018-11-11 07:25:47 +01:00
FlightControl
c4984a7576 Doc update 2018-11-11 07:22:22 +01:00
FlightControl
6a1052d95f Introduction documentation of A2G dispatching. 2018-11-10 16:30:31 +01:00
FlightControl
caa3bcb02b Introduction documentation of A2G dispatching. 2018-11-10 16:30:00 +01:00
FlightControl
74123d8ff3 Fixed overhead bug in dispatchiing from ground. 2018-11-10 13:21:46 +01:00
FlightControl
70b858d200 - Optimized the overhead problem in case of engage from patrol.
- Change in defense reactivity and distance calculations.
- Select correct template to engage is working now also.
2018-11-10 12:44:13 +01:00
FlightControl
3ca712a223 Optimized the overhead problem in case of engage from patrol. 2018-11-10 12:42:16 +01:00
Frank
cf4be99093 AB v0.2.2 2018-11-10 00:57:14 +01:00
FlightControl
900a55614b Change in defense reactivity and distance calculations. 2018-11-09 17:00:40 +01:00
FlightControl
f76b0dc7d3 Select correct template to engage is working now also. 2018-11-09 16:47:06 +01:00
funkyfranky
4542c96eae AB v0.2.1w 2018-11-09 16:07:30 +01:00
FlightControl
456a38022f Finish 2.4.10 2018-11-09 07:44:37 +01:00
FlightControl
238b1cb925 Finish 2.4.9 2018-11-09 06:46:24 +01:00
FlightControl
488804308b Undo my last stupid mistakes. 2018-11-09 06:27:16 +01:00
Frank
6fac8fe940 CT v0.2.1 2018-11-09 00:17:57 +01:00
funkyfranky
b53b1edd29 CT v0.2.0w 2018-11-08 16:04:51 +01:00
FlightControl
5a2bac9979 Got engage working from the air. Now I need to fix:
- Overhead
- Correct Defense Type assignment to engage.

# Conflicts:
#	Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
#	Moose Setup/Moose.files
2018-11-08 07:44:50 +01:00
FlightControl
e7181c255b Got engage working from the air. Now I need to fix:
- Overhead
- Correct Defense Type assignment to engage.
2018-11-08 07:38:04 +01:00
Frank
ac074ca23d CT v0.2.0 2018-11-07 23:26:51 +01:00
funkyfranky
b21bb3d433 CT v0.1.9w 2018-11-07 16:08:13 +01:00
FlightControl
0e656c8520 Progress, patrolling works. Engagement not yet. 2018-11-07 07:24:45 +01:00
Frank
708ff794b0 CT v0.1.9 2018-11-07 00:37:49 +01:00
FlightControl
6e34f12ac8 Finish 2.4.8 2018-11-06 19:11:26 +01:00
funkyfranky
6d45c09ea2 CTv0.18w 2018-11-06 16:04:19 +01:00
Frank
9fac65f31f CT v0.1..8 2018-11-06 00:04:52 +01:00
funkyfranky
ba4caf179a CTv0.1.7w 2018-11-05 16:18:15 +01:00
Frank
1febe765bf CT v0.1.7 2018-11-05 00:35:48 +01:00
Frank
087ac992a2 CT v0.1.6 2018-11-04 01:14:47 +01:00
funkyfranky
27bf6069d7 CT v0.1.5w 2018-11-02 16:07:40 +01:00
Frank
3153d196a2 CT v0.1.5 2018-11-02 00:29:50 +01:00
funkyfranky
8c320f729a CT v0.1.4w 2018-11-01 16:20:49 +01:00
Frank
c7fdc77ec8 CT v0.1.4
Added aircraft check.
Fixed problems with new player.
2018-11-01 00:09:58 +01:00
Frank
dfd20ced59 CT v0.1.3 2018-10-30 00:02:45 +01:00
funkyfranky
5f40feb1af CTv0.1.2w 2018-10-29 16:25:09 +01:00
Frank
ae754fe334 CT v0.1.2 2018-10-29 00:01:53 +01:00
Sven Van de Velde
74d2ee9430 Improvements 2018-03-25 20:49:49 +02:00
Sven Van de Velde
856bd56cde Improved dynamic loading 2018-03-25 20:33:43 +02:00
Sven Van de Velde
4c91880a85 Improved dynamic loader process 2018-03-25 20:27:18 +02:00
111 changed files with 65315 additions and 12254 deletions

5
.gitignore vendored
View File

@@ -221,3 +221,8 @@ _gsdata_/
.gitattributes
.gitignore
Moose Test Missions/MOOSE_Test_Template.miz
Moose Development/Moose/.vscode/launch.json
MooseCodeWS.code-workspace
.gitignore
.gitignore
/.gitignore

0
.scannerwork/.sonar_lock Normal file
View File

View File

@@ -0,0 +1,6 @@
projectKey=Test
serverUrl=http://localhost:9000
serverVersion=8.1.0.31237
dashboardUrl=http://localhost:9000/dashboard?id=Test
ceTaskId=AXAlUJO97YLjwz1VUDXR
ceTaskUrl=http://localhost:9000/api/ce/task?id=AXAlUJO97YLjwz1VUDXR

View File

@@ -1,66 +1,67 @@
--- **AI** -- (R2.2) - Models the process of Combat Air Patrol (CAP) for airplanes.
--
-- ===
--
--
-- ### Author: **FlightControl**
--
-- ===
--
-- ===
--
-- @module AI.AI_A2A_Cap
-- @image AI_Combat_Air_Patrol.JPG
--- @type AI_A2A_CAP
-- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL
-- @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 @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
--- The AI_A2A_CAP class implements the core functions to patrol a @{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.
--
--
-- ![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 treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
--
-- ![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.
@@ -73,30 +74,61 @@
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds 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,
--
-- 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.AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{Zone} can be set,
--
-- An optional @{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_A2A_CAP.SetEngageZone}() to define that Zone.
--
--
-- ===
--
--
-- @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 @{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 ) -- #AI_AIR_PATROL
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
@@ -111,174 +143,8 @@ AI_A2A_CAP = {
-- @return #AI_A2A_CAP
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType )
self.Accomplished = false
self.Engaging = false
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnAfterEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] Engage
-- @param #AI_A2A_CAP self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2A_CAP] __Engage
-- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnLeaveEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnEnterEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnBeforeFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnAfterFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] Fired
-- @param #AI_A2A_CAP self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2A_CAP] __Fired
-- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnBeforeDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnAfterDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] Destroy
-- @param #AI_A2A_CAP self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2A_CAP] __Destroy
-- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnBeforeAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnAfterAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] Abort
-- @param #AI_A2A_CAP self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2A_CAP] __Abort
-- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] OnAfterAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP] Accomplish
-- @param #AI_A2A_CAP self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] __Accomplish
-- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds.
return self
end
--- onafter State Transition for Event Patrol.
@@ -289,199 +155,58 @@ end
-- @param #string To The To State string.
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
self:GetParent( self ).onafterStart( self, 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.
--- 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
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- Set the Engage Range when the AI will engage with airborne enemies.
--- 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
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- onafter State Transition for Event Patrol.
--- Evaluate the attack and create an AttackUnitTask list.
-- @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:onafterPatrol( AICap, From, Event, To )
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrappper.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 )
-- Call the parent Start event handler
self:GetParent(self).onafterPatrol( self, AICap, From, Event, To )
self:HandleEvent( EVENTS.Dead )
local AttackUnitTasks = {}
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.AttackRoute( AICap, Fsm )
AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } )
if AICap:IsAlive() then
Fsm:__Engage( 0.5 )
end
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_CAP:onbeforeEngage( AICap, From, Event, To )
if self.Accomplished == true then
return false
end
end
--- @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:onafterAbort( AICap, From, Event, To )
AICap:ClearTasks()
self:__Route( 0.5 )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The AICap 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_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
self:F( { AICap, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
if AICap:IsAlive() then
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AICap:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
local AttackTasks = {}
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
AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
end
end
if #AttackTasks == 0 then
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
else
AICap:OptionROEOpenFire()
AICap:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks )
end
AICap:Route( EngageRoute, 0.5 )
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
else
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
end
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_CAP:onafterAccomplish( AICap, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap 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_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
--- @param #AI_A2A_CAP self
-- @param Core.Event#EVENTDATA EventData
function AI_A2A_CAP:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.Resume( AICap, Fsm )
AICap:I( { "AI_A2A_CAP.Resume:", AICap:GetName() } )
if AICap:IsAlive() then
Fsm:__Reset( 1 )
Fsm:__Route( 5 )
end
return AttackUnitTasks
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
--- **AI** -- (R2.2) - Models the process of Ground Controlled Interception (GCI) for airplanes.
--
-- This is a class used in the @{AI_A2A_Dispatcher}.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
--
-- ===
--
-- ===
--
-- @module AI.AI_A2A_GCI
-- @image AI_Ground_Control_Intercept.JPG
@@ -18,52 +18,52 @@
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
--
-- 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.
--
--
-- ![Process](..\Presentations\AI_GCI\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_GCI\Dia5.JPG)
--
--
-- This cycle will continue.
--
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
--
-- ## 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.
@@ -76,25 +76,25 @@
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
--
-- ![Range](..\Presentations\AI_GCI\Dia11.JPG)
--
-- An optional range can be set in meters,
--
-- 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.AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{Zone} can be set,
--
-- An optional @{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_A2A_GCI.SetEngageZone}() to define that Zone.
--
--
-- ===
--
--
-- @field #AI_A2A_GCI
AI_A2A_GCI = {
ClassName = "AI_A2A_GCI",
@@ -105,183 +105,39 @@ 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:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI
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.Accomplished = false
self.Engaging = false
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self.PatrolMinSpeed = EngageMinSpeed
self.PatrolMaxSpeed = EngageMaxSpeed
self.PatrolAltType = "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnAfterEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] Engage
-- @param #AI_A2A_GCI self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2A_GCI] __Engage
-- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnLeaveEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnEnterEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnBeforeFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnAfterFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] Fired
-- @param #AI_A2A_GCI self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2A_GCI] __Fired
-- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnBeforeDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnAfterDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] Destroy
-- @param #AI_A2A_GCI self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2A_GCI] __Destroy
-- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnBeforeAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnAfterAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] Abort
-- @param #AI_A2A_GCI self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2A_GCI] __Abort
-- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] OnAfterAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI] Accomplish
-- @param #AI_A2A_GCI self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] __Accomplish
-- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds.
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.
@@ -290,173 +146,29 @@ end
-- @param #string To The To State string.
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
self:GetParent( self ).onafterStart( self, AIIntercept, From, Event, To )
AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self )
self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To )
end
--- onafter State Transition for Event Patrol.
--- Evaluate the attack and create an AttackUnitTask list.
-- @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:onafterEngage( AIIntercept, From, Event, To )
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
-- @param Wrappper.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 )
self:HandleEvent( EVENTS.Dead )
local AttackUnitTasks = {}
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm )
AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } )
if AIIntercept:IsAlive() then
Fsm:__Engage( 0.5 )
--local Task = AIIntercept:TaskOrbitCircle( 4000, 400 )
--AIIntercept:SetTask( Task )
end
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_GCI:onbeforeEngage( AIIntercept, From, Event, To )
if self.Accomplished == true then
return false
end
end
--- @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:onafterAbort( AIIntercept, From, Event, To )
AIIntercept:ClearTasks()
self:Return()
self:__RTB( 0.5 )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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.
function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
self:F( { AIIntercept, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
if FirstAttackUnit and FirstAttackUnit:IsAlive() then
if AIIntercept:IsAlive() then
local EngageRoute = {}
local CurrentCoord = AIIntercept:GetCoordinate()
--- Calculate the target route point.
local CurrentCoord = AIIntercept:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToInterceptAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit )
end
end
if #AttackTasks == 0 then
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
else
AIIntercept:OptionROEOpenFire()
AIIntercept:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks )
end
AIIntercept:Route( EngageRoute, 0.5 )
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
else
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
end
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_GCI:onafterAccomplish( AIIntercept, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept 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_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
--- @param #AI_A2A_GCI self
-- @param Core.Event#EVENTDATA EventData
function AI_A2A_GCI:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
return AttackUnitTasks
end

View File

@@ -121,13 +121,13 @@ AI_A2A_PATROL = {
--- Creates a new AI_A2A_PATROL object
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol
-- @param Wrapper.Group#GROUP AIPatrol The patrol group object.
-- @param Core.Zone#ZONE_BASE PatrolZone The @{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
-- @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.
@@ -136,8 +136,14 @@ AI_A2A_PATROL = {
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL
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
@@ -145,8 +151,8 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
-- defafult PatrolAltType to "BARO" if not specified
self.PatrolAltType = PatrolAltType or "BARO"
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
@@ -281,15 +287,15 @@ function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
end
--- @param Wrapper.Group#GROUP AIPatrol
-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
--- This statis method is called from the route path within the last task at the last waaypoint 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:IsAlive() then
if AIPatrol and AIPatrol:IsAlive() then
Fsm:Route()
end
@@ -303,7 +309,6 @@ end
-- @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.
@@ -312,7 +317,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
end
if AIPatrol:IsAlive() then
if AIPatrol and AIPatrol:IsAlive() then
local PatrolRoute = {}
@@ -320,43 +325,80 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
-- Random altitude.
local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
-- Random speed in km/h.
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
-- First waypoint is current position.
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
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")
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
local Tasks = {}
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
-- 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()
AIPatrol:Route( PatrolRoute, 0.5 )
end
end
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2A_PATROL.Resume( AIPatrol, Fsm )
AIPatrol:I( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:__Reset( 1 )
Fsm:__Route( 5 )
end
-- Patrol.
AIPatrol:Route( PatrolRoute, 0.5)
end
end

View File

@@ -0,0 +1,99 @@
--- **AI** -- Models the process of air to ground BAI engagement for airplanes and helicopters.
--
-- This is a class used in the @{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
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ===
--
-- @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 @{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 @{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 Wrappper.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

View File

@@ -0,0 +1,100 @@
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{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
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ===
--
-- @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 @{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 @{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 Wrappper.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

View File

@@ -0,0 +1,152 @@
--- **AI** -- Models the process of air to ground SEAD engagement for airplanes and helicopters.
--
-- This is a class used in the @{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.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- 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.
--
-- ![Process](..\Presentations\AI_GCI\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_GCI\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
-- ## 1. AI_A2G_SEAD constructor
--
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_GCI\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.AI_GCI#AI_A2G_SEAD.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{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_A2G_SEAD.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @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 @{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 ) -- #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_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 @{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 Wrappper.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

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.2) - Models the process of air operations for airplanes.
--- **AI** - Models the process of AI air operations.
--
-- ===
--
@@ -6,102 +6,99 @@
--
-- ===
--
-- @module AI.AI_A2A
-- @image AI_Air_To_Air_Dispatching.JPG
-- @module AI.AI_Air
-- @image MOOSE.JPG
--BASE:TraceClass("AI_A2A")
--- @type AI_A2A
--- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_A2A class implements the core functions to operate an AI @{Wrapper.Group} A2A tasking.
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
--
--
-- ## AI_A2A constructor
-- # 1) AI_AIR constructor
--
-- * @{#AI_A2A.New}(): Creates a new AI_A2A object.
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
--
-- ## 2. AI_A2A is a FSM
-- # 2) AI_AIR is a Finite State Machine.
--
-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG)
-- 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.
--
-- ### 2.1. AI_A2A States
-- So, each of the rows have the following structure.
--
-- * **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.
-- * **From** => **Event** => **To**
--
-- ### 2.2. AI_A2A Events
-- 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.
--
-- * **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 tresholds have been reached, the AI will RTB.
--
-- ## 3. Set or Get the AI controllable
-- These are the different possible state transitions of this state machine implementation:
--
-- * @{#AI_A2A.SetControllable}(): Set the AIControllable.
-- * @{#AI_A2A.GetControllable}(): Get the AIControllable.
-- * Idle => Start => Monitoring
--
-- @field #AI_A2A
AI_A2A = {
ClassName = "AI_A2A",
-- ## 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.
--
-- @field #AI_AIR
AI_AIR = {
ClassName = "AI_AIR",
}
--- Creates a new AI_A2A object
-- @param #AI_A2A self
-- @param Wrapper.Group#GROUP AIGroup The GROUP object to receive the A2A Process.
-- @return #AI_A2A
function AI_A2A:New( AIGroup )
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_A2A
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
self:SetControllable( AIGroup )
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
self:SetStartState( "Stopped" )
self:AddTransition( "*", "Queue", "Queued" )
self:AddTransition( "*", "Start", "Started" )
--- Start Handler OnBefore for AI_A2A
-- @function [parent=#AI_A2A] OnBeforeStart
-- @param #AI_A2A self
--- 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_A2A
-- @function [parent=#AI_A2A] OnAfterStart
-- @param #AI_A2A self
--- 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_A2A
-- @function [parent=#AI_A2A] Start
-- @param #AI_A2A self
--- Start Trigger for AI_AIR
-- @function [parent=#AI_AIR] Start
-- @param #AI_AIR self
--- Start Asynchronous Trigger for AI_A2A
-- @function [parent=#AI_A2A] __Start
-- @param #AI_A2A 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_A2A] OnLeaveStopped
-- @param #AI_A2A self
-- @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.
@@ -109,16 +106,16 @@ function AI_A2A:New( AIGroup )
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Stopped.
-- @function [parent=#AI_A2A] OnEnterStopped
-- @param #AI_A2A self
-- @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_A2A] OnBeforeStop
-- @param #AI_A2A self
-- @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.
@@ -126,27 +123,27 @@ function AI_A2A:New( AIGroup )
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Stop.
-- @function [parent=#AI_A2A] OnAfterStop
-- @param #AI_A2A self
-- @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_A2A] Stop
-- @param #AI_A2A self
-- @function [parent=#AI_AIR] Stop
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Stop.
-- @function [parent=#AI_A2A] __Stop
-- @param #AI_A2A self
-- @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_A2A.
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event Status.
-- @function [parent=#AI_A2A] OnBeforeStatus
-- @param #AI_A2A self
-- @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.
@@ -154,27 +151,27 @@ function AI_A2A:New( AIGroup )
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Status.
-- @function [parent=#AI_A2A] OnAfterStatus
-- @param #AI_A2A self
-- @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_A2A] Status
-- @param #AI_A2A self
-- @function [parent=#AI_AIR] Status
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Status.
-- @function [parent=#AI_A2A] __Status
-- @param #AI_A2A self
-- @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_A2A.
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event RTB.
-- @function [parent=#AI_A2A] OnBeforeRTB
-- @param #AI_A2A self
-- @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.
@@ -182,25 +179,25 @@ function AI_A2A:New( AIGroup )
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event RTB.
-- @function [parent=#AI_A2A] OnAfterRTB
-- @param #AI_A2A self
-- @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_A2A] RTB
-- @param #AI_A2A self
-- @function [parent=#AI_AIR] RTB
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event RTB.
-- @function [parent=#AI_A2A] __RTB
-- @param #AI_A2A self
-- @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_A2A] OnLeaveReturning
-- @param #AI_A2A self
-- @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.
@@ -208,8 +205,8 @@ function AI_A2A:New( AIGroup )
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Returning.
-- @function [parent=#AI_A2A] OnEnterReturning
-- @param #AI_A2A self
-- @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.
@@ -217,30 +214,30 @@ function AI_A2A:New( AIGroup )
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_A2A
-- @function [parent=#AI_A2A] OnBeforeRefuel
-- @param #AI_A2A self
--- 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_A2A
-- @function [parent=#AI_A2A] OnAfterRefuel
-- @param #AI_A2A self
--- 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_A2A
-- @function [parent=#AI_A2A] Refuel
-- @param #AI_A2A self
--- Refuel Trigger for AI_AIR
-- @function [parent=#AI_AIR] Refuel
-- @param #AI_AIR self
--- Refuel Asynchronous Trigger for AI_A2A
-- @function [parent=#AI_A2A] __Refuel
-- @param #AI_A2A self
--- Refuel Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Refuel
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
@@ -266,15 +263,17 @@ function GROUP:OnEventTakeoff( EventData, Fsm )
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_A2A:SetDispatcher( Dispatcher )
function AI_AIR:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
function AI_A2A:GetDispatcher()
function AI_AIR:GetDispatcher()
return self.Dispatcher
end
function AI_A2A:SetTargetDistance( Coordinate )
function AI_AIR:SetTargetDistance( Coordinate )
local CurrentCoord = self.Controllable:GetCoordinate()
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
@@ -283,7 +282,7 @@ function AI_A2A:SetTargetDistance( Coordinate )
end
function AI_A2A:ClearTargetDistance()
function AI_AIR:ClearTargetDistance()
self.TargetDistance = nil
self.ClosestTargetDistance = nil
@@ -291,11 +290,11 @@ end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_A2A self
-- @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_A2A self
function AI_A2A:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
-- @return #AI_AIR self
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
@@ -303,12 +302,25 @@ function AI_A2A:SetSpeed( PatrolMinSpeed, 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_A2A self
-- @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_A2A self
function AI_A2A:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
-- @return #AI_AIR self
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
@@ -317,20 +329,20 @@ end
--- Sets the home airbase.
-- @param #AI_A2A self
-- @param #AI_AIR self
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
-- @return #AI_A2A self
function AI_A2A:SetHomeAirbase( 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_A2A self
-- @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_A2A self
function AI_A2A:SetTanker( TankerName )
-- @return #AI_AIR self
function AI_AIR:SetTanker( TankerName )
self:F2( { TankerName } )
self.TankerName = TankerName
@@ -338,19 +350,19 @@ 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_A2A self
-- @param #AI_AIR self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_A2A self
function AI_A2A:SetDisengageRadius( DisengageRadius )
-- @return #AI_AIR self
function AI_AIR:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_A2A self
-- @return #AI_A2A self
function AI_A2A:SetStatusOff()
-- @param #AI_AIR self
-- @return #AI_AIR self
function AI_AIR:SetStatusOff()
self:F2()
self.CheckStatus = false
@@ -359,16 +371,16 @@ end
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_A2A.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_A2A self
-- @param #number PatrolFuelThresholdPercentage The treshold 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_A2A self
function AI_A2A:SetFuelThreshold( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
-- @param #AI_AIR self
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number 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.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
self.FuelThresholdPercentage = FuelThresholdPercentage
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
@@ -381,10 +393,10 @@ end
-- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_A2A self
-- @param #AI_AIR self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_A2A self
function AI_A2A:SetDamageThreshold( PatrolDamageThreshold )
-- @return #AI_AIR self
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
@@ -392,14 +404,16 @@ function AI_A2A:SetDamageThreshold( PatrolDamageThreshold )
return self
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A self
-- @return #AI_A2A self
-- @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_A2A:onafterStart( Controllable, From, Event, To )
function AI_AIR:onafterStart( Controllable, From, Event, To )
self:__Status( 10 ) -- Check status status every 30 seconds.
@@ -411,16 +425,27 @@ function AI_A2A:onafterStart( Controllable, From, Event, To )
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_A2A self
function AI_A2A:onbeforeStatus()
--- @param #AI_AIR self
function AI_AIR:onbeforeStatus()
return self.CheckStatus
end
--- @param #AI_A2A self
function AI_A2A:onafterStatus()
--- @param #AI_AIR self
function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive() then
@@ -430,10 +455,9 @@ function AI_A2A:onafterStatus()
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
self:F({DistanceFromHomeBase=DistanceFromHomeBase})
if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
@@ -448,19 +472,23 @@ function AI_A2A:onafterStatus()
-- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then
local Fuel = self.Controllable:GetFuelMin()
self:F({Fuel=Fuel, PatrolFuelThresholdPercentage=self.PatrolFuelThresholdPercentage})
if Fuel < self.PatrolFuelThresholdPercentage then
-- If the fuel in the controllable is below the treshold percentage,
-- then send for refuel in case of a tanker, otherwise RTB.
if Fuel < self.FuelThresholdPercentage then
if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
self:I( 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 ) )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
self:Fuel()
@@ -469,18 +497,25 @@ function AI_A2A:onafterStatus()
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()
self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } )
-- 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:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:I( 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
@@ -489,11 +524,12 @@ function AI_A2A:onafterStatus()
not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and
not self:Is( "Home" ) then
if self.IdleCount >= 2 then
if self.IdleCount >= 10 then
if Damage ~= InitialLife then
self:Damaged()
else
self:E( self.Controllable:GetName() .. " control lost! " )
self:I( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
else
@@ -505,7 +541,7 @@ function AI_A2A:onafterStatus()
end
if RTB == true then
self:__RTB( 0.5 )
self:__RTB( self.TaskDelay )
end
if not self:Is("Home") then
@@ -517,22 +553,22 @@ end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBRoute( AIGroup, Fsm )
function AI_AIR.RTBRoute( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.RTBRoute:", AIGroup:GetName() } )
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:RTB()
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBHold( AIGroup, Fsm )
function AI_AIR.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.RTBHold:", AIGroup:GetName() } )
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:__RTB( Fsm.TaskDelay )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
@@ -541,74 +577,92 @@ function AI_A2A.RTBHold( AIGroup, Fsm )
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterRTB( AIGroup, From, Event, To )
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
if AIGroup and AIGroup:IsAlive() then
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
AIGroup:ClearTasks()
--AIGroup:ClearTasks()
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local FromCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
local ToAirbaseAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
local Distance = CurrentCoord:Get2DDistance( ToTargetCoord )
if not self.RTBMinSpeed and not self.RTBMaxSpeed then
local RTBSpeedMax = AIGroup:GetSpeedMax()
self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 )
end
local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle )
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) )
local Distance = FromCoord:Get2DDistance( ToTargetCoord )
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then
self:E( "RTB and near the airbase!" )
self:I( "RTB and near the airbase!" )
self:Home()
return
end
if not AIGroup:InAir() == true then
self:I( "Not anymore in the air, considered Home." )
self:Home()
return
end
--- Create a route point of type air.
local FromRTBRoutePoint = FromCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
RTBSpeed,
true
)
--- Create a route point of type air.
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
RTBSpeed,
true
)
self:F( { Angle = ToAirbaseAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
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 we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( EngageRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_A2A.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
--- NOW ROUTE THE GROUP!
AIGroup:Route( EngageRoute, 0.5 )
AIGroup:Route( EngageRoute, self.TaskDelay )
end
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterHome( AIGroup, From, Event, To )
function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
end
@@ -617,22 +671,22 @@ end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime )
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_A2A.RTBHold", self )
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_A2A", self )
--AIGroup:SetState( AIGroup, "AI_AIR", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
@@ -640,98 +694,112 @@ function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime )
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.Resume( AIGroup, Fsm )
function AI_AIR.Resume( AIGroup, Fsm )
AIGroup:I( { "AI_A2A.Resume:", AIGroup:GetName() } )
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:__RTB( Fsm.TaskDelay )
end
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterRefuel( AIGroup, From, Event, To )
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
-- Get tanker group.
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then
if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then
self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
local RefuelRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local FromRefuelCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
--- Create a route point of type air. NOT used!
local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
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( self:GetClassName() .. ".Resume", self )
Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, 0.5 )
AIGroup:Route( RefuelRoute, self.TaskDelay )
else
-- No tanker defined ==> RTB!
self:RTB()
end
end
end
--- @param #AI_A2A self
function AI_A2A:onafterDead()
--- @param #AI_AIR self
function AI_AIR:onafterDead()
self:SetStatusOff()
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_A2A:OnCrash( EventData )
function AI_AIR:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData )
self:__Crash( self.TaskDelay, EventData )
end
end
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_A2A:OnEjection( EventData )
function AI_AIR:OnEjection( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__Eject( 1, EventData )
self:__Eject( self.TaskDelay, EventData )
end
end
--- @param #AI_A2A self
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_A2A:OnPilotDead( EventData )
function AI_AIR:OnPilotDead( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__PilotDead( 1, EventData )
self:__PilotDead( self.TaskDelay, EventData )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,597 @@
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{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.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- 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.
--
-- ![Process](..\Presentations\AI_GCI\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_GCI\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
-- ## 1. AI_AIR_ENGAGE constructor
--
-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_GCI\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.AI_GCI#AI_AIR_ENGAGE.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{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.
--
-- ===
--
-- @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:I(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.
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:I( { 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:Count()
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:GetFirst():GetPointVec3()
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:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
self:__Engage( 0.1, AttackSetUnit )
else
self:I(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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
end
end
--- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
Fsm:I(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.
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:Count()
self:I({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:GetFirst():GetPointVec3()
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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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:I( 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:I(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:I( 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

View File

@@ -0,0 +1,398 @@
--- **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 @{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.
--
-- ![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 treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![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 tresholds 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.AI_CAP#AI_AIR_PATROL.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{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_PATROL.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @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 @{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 @{Process_PatrolZone} 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 statis method is called from the route path within the last task at the last waaypoint 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 @{Process_PatrolZone} 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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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
--- @param Wrapper.Group#GROUP AIPatrol
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

View File

@@ -0,0 +1,289 @@
--- **AI** - Models squadrons for airplanes and helicopters.
--
-- This is a class used in the @{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.
--
-- ===
--
-- @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:I( { 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

View File

@@ -196,8 +196,12 @@ function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, A
SetGroup:Flush( self )
end
--- @param #AI_BALANCER self
--- 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 )
@@ -213,10 +217,13 @@ function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
--[[
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
AIGroupTemplate.route = RTBRoute
AIGroup:Respawn( AIGroupTemplate )
]]
AIGroup:RouteRTB(ClosestAirbase)
end
end

View File

@@ -421,7 +421,7 @@ end
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
if Controllable:IsAlive() then
if Controllable and Controllable:IsAlive() then
local EngageRoute = {}

View File

@@ -141,6 +141,17 @@ function AI_CARGO:New( Carrier, CargoSet )
-- @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
@@ -256,9 +267,10 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
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 Carrier_Weight[CarrierUnit] > CargoWeight then --and CargoBayFreeVolume > CargoVolume then
if CarrierSpace > CargoWeight then
Carrier:RouteStop()
--Cargo:Ungroup()
Cargo:__Board( -LoadDelay, CarrierUnit )
@@ -275,6 +287,8 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
-- 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
@@ -554,6 +568,7 @@ end
-- @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 } )

View File

@@ -76,25 +76,31 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
-- @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 Airbase where troops are picked up.
-- @param #number Speed in km/h for travelling to pickup base.
-- @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 Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
-- @param #number Speed in km/h for travelling to pickup base.
-- @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 Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
-- @param #number Speed in km/h for travelling to pickup base.
-- @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
@@ -111,24 +117,30 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
-- @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.
-- @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 Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
-- @param #number Speed Speed in km/h for travelling to deploy base.
-- @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 Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
-- @param #number Speed Speed in km/h for travelling to deploy base.
-- @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
@@ -137,6 +149,16 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
-- @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 )
@@ -259,15 +281,17 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed in km/h for travelling to pickup base.
-- @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 (optional) PickupZone The zone where the cargo will be picked up.
-- @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
self.PickupZone = PickupZone
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()
@@ -280,11 +304,10 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate,
end
-- Set pickup airbase.
local Airbase = PickupZone:GetAirbase()
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())
--env.info("Distance closest to pickup airbase = "..Dist)
if Airplane:InAir() or Dist>500 then
@@ -305,7 +328,7 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate,
end
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone )
end
@@ -318,15 +341,19 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed in km/h for travelling to pickup base.
-- @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 zone where the cargo will be deployed.
-- @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 = DeployZone:GetAirbase()
local Airbase = Coordinate:GetClosestAirbase()
if DeployZone then
Airbase=DeployZone:GetAirbase()
end
-- Activate uncontrolled airplane.
if Airplane:IsAlive()==false then
@@ -354,6 +381,7 @@ end
-- @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

View File

@@ -1151,6 +1151,10 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
self.PickupCargo[Carrier] = CargoCoordinate
PickupCargo = Cargo
break
else
local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.",
tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName()))
self:I(text)
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
--- **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.
--
-- ===
--
-- @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:I({EscortAirbase= self.EscortAirbase } )
self:I({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:I({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:I({EscortAirbase= self.EscortAirbase } )
self:I({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:I({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:I({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

View File

@@ -0,0 +1,146 @@
--- **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.
--
-- ===
--
-- @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

View File

@@ -0,0 +1,316 @@
--- **Functional** -- 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/ESC%20-%20Escorting)
--
-- ===
--
-- Allows you to interact with escorting AI on your flight and take the lead.
--
-- 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.
--
-- ===
--
-- ### Authors: **FlightControl**
--
-- ===
--
-- @module AI.AI_Escort
-- @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

File diff suppressed because it is too large Load Diff

View File

@@ -178,8 +178,8 @@ function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltit
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
-- defafult PatrolAltType to "BARO" if not specified
self.PatrolAltType = PatrolAltType or "BARO"
self:SetRefreshTimeInterval( 30 )
@@ -636,7 +636,7 @@ function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
self.Controllable:OnReSpawn(
function( PatrolGroup )
self:E( "ReSpawn" )
self:T( "ReSpawn" )
self:__Reset( 1 )
self:__Route( 5 )
end
@@ -667,21 +667,27 @@ function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then
local TargetUnit = UNIT:Find( TargetObject )
local TargetUnitName = TargetUnit:GetName()
if self.DetectionZone then
if TargetUnit:IsInZone( self.DetectionZone ) then
self:T( {"Detected ", TargetUnit } )
-- 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
Detected = true
end
else
if self.DetectedUnits[TargetUnit] == nil then
self.DetectedUnits[TargetUnit] = true
end
Detected = true
end
end
end
@@ -735,7 +741,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
-- This will make the plane fly immediately to the patrol zone.
if self.Controllable:InAir() == false then
self:E( "Not in the air, finding route path within PatrolZone" )
self:T( "Not in the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
@@ -750,7 +756,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
)
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
else
self:E( "In the air, finding route path within PatrolZone" )
self:T( "In the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
@@ -825,7 +831,7 @@ function AI_PATROL_ZONE:onafterStatus()
local Fuel = self.Controllable:GetFuelMin()
if Fuel < self.PatrolFuelThresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
self:I( 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 )
@@ -839,7 +845,7 @@ function AI_PATROL_ZONE:onafterStatus()
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true
end
@@ -900,7 +906,6 @@ end
function AI_PATROL_ZONE:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData )
end

View File

@@ -1,34 +1,34 @@
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Wrapper.Unit}s.
--
--
-- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG)
--
-- ===
--
--
-- ===
--
-- @module Actions.Account
-- @image MOOSE.JPG
do -- ACT_ACCOUNT
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ## ACT_ACCOUNT state machine:
--
--
-- ## 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,
-- 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
--
--
-- ### ACT_ACCOUNT States
--
-- * **Asigned**: 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
--
--
-- ### ACT_ACCOUNT Events
--
-- * **Start**: Start the process.
-- * **Wait**: Wait for an event.
-- * **Report**: Report the status of the accounting.
@@ -36,32 +36,32 @@ do -- ACT_ACCOUNT
-- * **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**.
--
-- * **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,
-- 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**.
--
-- * **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.
--
--
-- @type ACT_ACCOUNT
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Core.Fsm#FSM_PROCESS
ACT_ACCOUNT = {
ACT_ACCOUNT = {
ClassName = "ACT_ACCOUNT",
TargetSetUnit = nil,
}
--- Creates a new DESTROY process.
-- @param #ACT_ACCOUNT self
-- @return #ACT_ACCOUNT
@@ -69,7 +69,7 @@ do -- ACT_ACCOUNT
-- 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" )
@@ -79,16 +79,16 @@ do -- ACT_ACCOUNT
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" )
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddEndState( "Failed" )
self:SetStartState( "Assigned" )
self:SetStartState( "Assigned" )
return self
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -104,7 +104,7 @@ do -- ACT_ACCOUNT
self:__Wait( 1 )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -112,17 +112,17 @@ do -- ACT_ACCOUNT
-- @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
@@ -130,30 +130,30 @@ do -- ACT_ACCOUNT
-- @param #string From
-- @param #string To
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event )
self:__NoMore( 1 )
end
end -- ACT_ACCOUNT
do -- ACT_ACCOUNT_DEADS
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Core.Fsm.Account#ACT_ACCOUNT}
--
--
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
-- The process is given a @{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 = {
ACT_ACCOUNT_DEADS = {
ClassName = "ACT_ACCOUNT_DEADS",
}
@@ -165,24 +165,24 @@ do -- ACT_ACCOUNT_DEADS
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.Task = self:GetTask()
self.TaskName = self.Task:GetName()
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -190,12 +190,12 @@ do -- ACT_ACCOUNT_DEADS
-- @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
@@ -206,7 +206,7 @@ do -- ACT_ACCOUNT_DEADS
-- @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]
@@ -228,14 +228,14 @@ do -- ACT_ACCOUNT_DEADS
-- @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 )
@@ -256,10 +256,10 @@ do -- ACT_ACCOUNT_DEADS
-- @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 )
@@ -270,9 +270,9 @@ do -- ACT_ACCOUNT_DEADS
end
end
--- DCS Events
--- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:OnEventHit( EventData )
@@ -282,8 +282,8 @@ do -- ACT_ACCOUNT_DEADS
self.PlayerHits = self.PlayerHits or {}
self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName
end
end
end
--- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
@@ -295,7 +295,7 @@ do -- ACT_ACCOUNT_DEADS
end
--- DCS Events
--- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )

View File

@@ -1,82 +1,82 @@
--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments.
--
--
-- ===
--
--
-- # @{#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,
-- 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.
--
-- * **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**.
--
-- * **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,
-- 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**.
--
-- * **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_ASSIGN_ACCEPT} class, extends @{Core.Fsm.Assign#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.Assign#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.Assign
-- @image MOOSE.JPG
@@ -89,11 +89,11 @@ do -- ACT_ASSIGN
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends Core.Fsm#FSM_PROCESS
ACT_ASSIGN = {
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.
@@ -106,16 +106,16 @@ do -- ACT_ASSIGN
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" )
self:SetStartState( "UnAssigned" )
return self
end
end -- ACT_ASSIGN
@@ -128,26 +128,26 @@ do -- ACT_ASSIGN_ACCEPT
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIGN
ACT_ASSIGN_ACCEPT = {
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
self.TaskBriefing = FsmAssign.TaskBriefing
end
--- StateMachine callback function
@@ -157,8 +157,8 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
self:__Assign( 1 )
self:__Assign( 1 )
end
--- StateMachine callback function
@@ -167,11 +167,11 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To )
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
end -- ACT_ASSIGN_ACCEPT
@@ -183,7 +183,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIGN
ACT_ASSIGN_MENU_ACCEPT = {
ACT_ASSIGN_MENU_ACCEPT = {
ClassName = "ACT_ASSIGN_MENU_ACCEPT",
}
@@ -197,7 +197,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
self.TaskBriefing = TaskBriefing
return self
end
@@ -207,12 +207,12 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @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
@@ -222,30 +222,30 @@ do -- ACT_ASSIGN_MENU_ACCEPT
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()
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
@@ -253,10 +253,10 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @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
@@ -265,7 +265,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @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
@@ -279,7 +279,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @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

View File

@@ -1,65 +1,65 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
--
-- ## 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,
-- 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.
--
-- * **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**.
--
-- * **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,
-- 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**.
--
-- * **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 @{Core.Fsm.Route#ACT_ASSIST}
--
--
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}.
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
-- 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.
--
--
-- ===
--
--
-- @module Actions.Assist
-- @image MOOSE.JPG
@@ -69,7 +69,7 @@ do -- ACT_ASSIST
--- ACT_ASSIST class
-- @type ACT_ASSIST
-- @extends Core.Fsm#FSM_PROCESS
ACT_ASSIST = {
ACT_ASSIST = {
ClassName = "ACT_ASSIST",
}
@@ -86,15 +86,15 @@ do -- ACT_ASSIST
self:AddTransition( "Smoking", "Next", "AwaitSmoke" )
self:AddTransition( "*", "Stop", "Success" )
self:AddTransition( "*", "Fail", "Failed" )
self:AddEndState( "Failed" )
self:AddEndState( "Success" )
self:SetStartState( "None" )
self:SetStartState( "None" )
return self
end
--- Task Events
--- StateMachine callback function
@@ -104,17 +104,17 @@ do -- ACT_ASSIST
-- @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 } )
@@ -130,10 +130,10 @@ do -- ACT_ASSIST
-- @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
@@ -143,17 +143,17 @@ do -- 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 = {
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
@@ -163,29 +163,29 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
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
@@ -193,7 +193,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
-- @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 )
@@ -203,12 +203,12 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
if SmokeUnit:IsAlive() then
SmokeUnit:Smoke( self.SmokeColor, 150 )
end
end, {}, math.random( 10, 60 )
end, {}, math.random( 10, 60 )
)
end
end
)
end
end
end

View File

@@ -1,20 +1,20 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
--
-- ===
--
--
-- # @{#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,
-- 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.
@@ -22,56 +22,56 @@
-- * **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.
--
-- * **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.
-- * **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**.
--
-- * **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,
-- 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**.
--
-- * **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 @{Core.Fsm.Route#ACT_ROUTE}
--
--
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Zone}.
-- The player receives on perioding times messages with the coordinates of the route to follow.
-- 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.
--
--
-- ===
--
--
-- @module Actions.Route
-- @image MOOSE.JPG
@@ -85,11 +85,11 @@ do -- ACT_ROUTE
-- @field Core.Zone#ZONE_BASE Zone
-- @field Core.Point#COORDINATE Coordinate
-- @extends Core.Fsm#FSM_PROCESS
ACT_ROUTE = {
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
@@ -97,7 +97,7 @@ do -- ACT_ROUTE
-- 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", "*" )
@@ -109,23 +109,23 @@ do -- ACT_ROUTE
self:AddTransition( "*", "Fail", "Failed" )
self:AddTransition( "", "", "" )
self:AddTransition( "", "", "" )
self:AddEndState( "Arrived" )
self:AddEndState( "Failed" )
self:AddEndState( "Cancelled" )
self:SetStartState( "None" )
self:SetRouteMode( "C" )
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,
@@ -135,47 +135,47 @@ do -- ACT_ROUTE
):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
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()
@@ -209,7 +209,7 @@ do -- ACT_ROUTE
return RouteText
end
function ACT_ROUTE:MenuCancel()
self:F("Cancelled")
self.CancelMenuGroupCommand:Remove()
@@ -225,11 +225,11 @@ do -- ACT_ROUTE
-- @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
@@ -237,7 +237,7 @@ do -- ACT_ROUTE
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
return false
end
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -245,7 +245,7 @@ do -- ACT_ROUTE
-- @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
@@ -257,18 +257,18 @@ do -- ACT_ROUTE
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
@@ -280,12 +280,12 @@ do -- ACT_ROUTE_POINT
-- @type ACT_ROUTE_POINT
-- @field Tasking.Task#TASK TASK
-- @extends #ACT_ROUTE
ACT_ROUTE_POINT = {
ACT_ROUTE_POINT = {
ClassName = "ACT_ROUTE_POINT",
}
--- Creates a new routing state machine.
--- 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.
@@ -296,29 +296,29 @@ do -- 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.
--- 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
end
--- Set Coordinate
-- @param #ACT_ROUTE_POINT self
@@ -326,7 +326,7 @@ do -- ACT_ROUTE_POINT
function ACT_ROUTE_POINT:SetCoordinate( Coordinate )
self:F2( { Coordinate } )
self.Coordinate = Coordinate
end
end
--- Get Coordinate
-- @param #ACT_ROUTE_POINT self
@@ -334,7 +334,7 @@ do -- ACT_ROUTE_POINT
function ACT_ROUTE_POINT:GetCoordinate()
self:F2( { self.Coordinate } )
return self.Coordinate
end
end
--- Set Range around Coordinate
-- @param #ACT_ROUTE_POINT self
@@ -342,16 +342,16 @@ do -- ACT_ROUTE_POINT
function ACT_ROUTE_POINT:SetRange( Range )
self:F2( { Range } )
self.Range = Range or 10000
end
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
end
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -360,7 +360,7 @@ do -- ACT_ROUTE_POINT
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 )
@@ -370,9 +370,9 @@ do -- ACT_ROUTE_POINT
return false
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -380,9 +380,9 @@ do -- ACT_ROUTE_POINT
-- @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
@@ -397,7 +397,7 @@ do -- ACT_ROUTE_ZONE
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Zone#ZONE_BASE Zone
-- @extends #ACT_ROUTE
ACT_ROUTE_ZONE = {
ACT_ROUTE_ZONE = {
ClassName = "ACT_ROUTE_ZONE",
}
@@ -409,25 +409,25 @@ do -- ACT_ROUTE_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
end
--- Set Zone
-- @param #ACT_ROUTE_ZONE self
-- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to.
@@ -437,14 +437,14 @@ do -- ACT_ROUTE_ZONE
self.Zone = Zone
self.Altitude = Altitude
self.Heading = Heading
end
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
return self.Zone
end
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE self
@@ -459,9 +459,9 @@ do -- ACT_ROUTE_ZONE
return ProcessUnit:IsInZone( self.Zone )
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE_ZONE self
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -470,7 +470,7 @@ do -- ACT_ROUTE_ZONE
-- @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

View File

@@ -26,8 +26,6 @@
-- @module Core.Base
-- @image Core_Base.JPG
local _TraceOnOff = true
local _TraceLevel = 1
local _TraceAll = false
@@ -256,6 +254,8 @@ end
-- @param #BASE Parent is the Parent class that the Child inherits from.
-- @return #BASE Child
function BASE:Inherit( Child, Parent )
-- Create child.
local Child = routines.utils.deepCopy( Child )
if Child ~= nil then
@@ -271,6 +271,7 @@ function BASE:Inherit( Child, Parent )
--Child:_SetDestructor()
end
return Child
end
@@ -298,7 +299,8 @@ end
--
--
-- @param #BASE self
-- @param #BASE Child is the Child class from which the Parent class needs to be retrieved.
-- @param #BASE Child This is the Child class from which the Parent class needs to be retrieved.
-- @param #BASE FromClass (Optional) The class from which to get the parent.
-- @return #BASE
function BASE:GetParent( Child, FromClass )
@@ -432,7 +434,7 @@ do -- Event Handling
-- reflecting the order of the classes subscribed to the Event to be processed.
-- @param #BASE self
-- @param #number EventPriority The @{Event} processing Priority.
-- @return self
-- @return #BASE self
function BASE:SetEventPriority( EventPriority )
self._.EventPriority = EventPriority
end
@@ -595,18 +597,85 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns.
-- initiator : The unit that is doing the shooing.
-- initiator : The unit that is doing the shooting.
-- target: The unit that is being targeted.
-- @function [parent=#BASE] OnEventShootingStart
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event.
-- initiator : The unit that was doing the shooing.
-- initiator : The unit that was doing the shooting.
-- @function [parent=#BASE] OnEventShootingEnd
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a new mark was added.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkAdded
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark was removed.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkRemoved
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark text was changed.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkChange
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Unknown precisely what creates this event, likely tied into newer damage model. Will update this page when new information become available.
--
-- * initiator: The unit that had the failure.
--
-- @function [parent=#BASE] OnEventDetailedFailure
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs.
--
-- * initiator: The unit that killed the target
-- * target: Target Object
-- * weapon: Weapon Object
--
-- @function [parent=#BASE] OnEventKill
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when the game thinks an object is destroyed.
--
-- * initiator: The unit that is was destroyed.
--
-- @function [parent=#BASE] OnEventUnitLost
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs shortly after the landing animation of an ejected pilot touching the ground and standing up. Event does not occur if the pilot lands in the water and sub combs to Davey Jones Locker.
--
-- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from.
-- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists.
-- * subplace: is always 0 for unknown reasons.
--
-- @function [parent=#BASE] OnEventLandingAfterEjection
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
end
@@ -746,9 +815,7 @@ do -- Scheduling
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
self.Scheduler.SchedulerObject = self.Scheduler
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
@@ -786,16 +853,15 @@ do -- Scheduling
self.Scheduler = SCHEDULER:New( self )
end
self.Scheduler.SchedulerObject = self.Scheduler
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
local ScheduleID = self.Scheduler:Schedule(
self,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop
Stop,
4
)
self._.Schedules[#self._.Schedules+1] = ScheduleID
@@ -809,8 +875,10 @@ do -- Scheduling
function BASE:ScheduleStop( SchedulerFunction )
self:F3( { "ScheduleStop:" } )
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
if self.Scheduler then
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
end
end
end
@@ -869,6 +937,26 @@ end
-- Log a trace (only shown when trace is on)
-- TODO: Make trace function using variable parameters.
--- Set trace on.
-- @param #BASE self
-- @usage
-- -- Switch the tracing On
-- BASE:TraceOn()
function BASE:TraceOn()
self:TraceOnOff( true )
end
--- Set trace off.
-- @param #BASE self
-- @usage
-- -- Switch the tracing Off
-- BASE:TraceOff()
function BASE:TraceOff()
self:TraceOnOff( false )
end
--- Set trace on or off
-- Note that when trace is off, no BASE.Debug statement is performed, increasing performance!
-- When Moose is loaded statically, (as one file), tracing is switched off by default.
@@ -883,7 +971,13 @@ end
-- -- Switch the tracing Off
-- BASE:TraceOnOff( false )
function BASE:TraceOnOff( TraceOnOff )
_TraceOnOff = TraceOnOff
if TraceOnOff==false then
self:I( "Tracing in MOOSE is OFF" )
_TraceOnOff = false
else
self:I( "Tracing in MOOSE is ON" )
_TraceOnOff = true
end
end
@@ -903,8 +997,8 @@ end
-- @param #BASE self
-- @param #number Level
function BASE:TraceLevel( Level )
_TraceLevel = Level
self:E( "Tracing level " .. Level )
_TraceLevel = Level or 1
self:I( "Tracing level " .. _TraceLevel )
end
--- Trace all methods in MOOSE
@@ -912,12 +1006,16 @@ end
-- @param #boolean TraceAll true = trace all methods in MOOSE.
function BASE:TraceAll( TraceAll )
_TraceAll = TraceAll
if TraceAll==false then
_TraceAll=false
else
_TraceAll = true
end
if _TraceAll then
self:E( "Tracing all methods in MOOSE " )
self:I( "Tracing all methods in MOOSE " )
else
self:E( "Switched off tracing all methods in MOOSE" )
self:I( "Switched off tracing all methods in MOOSE" )
end
end
@@ -927,7 +1025,7 @@ end
function BASE:TraceClass( Class )
_TraceClass[Class] = true
_TraceClassMethod[Class] = {}
self:E( "Tracing class " .. Class )
self:I( "Tracing class " .. Class )
end
--- Set tracing for a specific method of class
@@ -940,7 +1038,7 @@ function BASE:TraceClassMethod( Class, Method )
_TraceClassMethod[Class].Method = {}
end
_TraceClassMethod[Class].Method[Method] = true
self:E( "Tracing method " .. Method .. " of class " .. Class )
self:I( "Tracing method " .. Method .. " of class " .. Class )
end
--- Trace a function call. This function is private.
@@ -967,7 +1065,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
end
end
end
@@ -1042,7 +1140,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end
end
end
@@ -1113,9 +1211,9 @@ function BASE:E( Arguments )
LineFrom = DebugInfoFrom.currentline
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else
env.info( string.format( "%1s:%20s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%1s:%30s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end
end
@@ -1141,9 +1239,9 @@ function BASE:I( Arguments )
LineFrom = DebugInfoFrom.currentline
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else
env.info( string.format( "%1s:%20s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
env.info( string.format( "%1s:%30s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end
end

View File

@@ -1,9 +1,9 @@
--- **Core** - Manages several databases containing templates, mission objects, and mission information.
--
--- **Core** - Manages several databases containing templates, mission objects, and mission information.
--
-- ===
--
--
-- ## Features:
--
--
-- * During mission startup, scan the mission environment, and create / instantiate intelligently the different objects as defined within the mission.
-- * Manage database of DCS Group templates (as modelled using the mission editor).
-- - Group templates.
@@ -20,14 +20,14 @@
-- * Manage database of hits to units and statics.
-- * Manage database of destroys of units and statics.
-- * Manage database of @{Core.Zone#ZONE_BASE} objects.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ### Contributions:
--
-- ===
--
--
-- @module Core.Database
-- @image Core_Database.JPG
@@ -36,9 +36,9 @@
-- @extends Core.Base#BASE
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
--
--
-- Mission designers can use the DATABASE class to refer to:
--
--
-- * STATICS
-- * UNITS
-- * GROUPS
@@ -47,12 +47,12 @@
-- * PLAYERSJOINED
-- * PLAYERS
-- * CARGOS
--
--
-- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
--
--
-- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission.
-- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required.
--
--
-- @field #DATABASE
DATABASE = {
ClassName = "DATABASE",
@@ -81,6 +81,10 @@ DATABASE = {
HITS = {},
DESTROYS = {},
ZONES = {},
ZONES_GOAL = {},
WAREHOUSES = {},
FLIGHTGROUPS = {},
FLIGHTCONTROLS = {},
}
local _DATABASECoalition =
@@ -112,7 +116,7 @@ function DATABASE:New()
local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE
self:SetEventPriority( 1 )
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
@@ -122,11 +126,11 @@ function DATABASE:New()
self:HandleEvent( EVENTS.DeleteCargo )
self:HandleEvent( EVENTS.NewZone )
self:HandleEvent( EVENTS.DeleteZone )
-- Follow alive players and clients
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
self:_RegisterTemplates()
self:_RegisterGroupsAndUnits()
self:_RegisterClients()
@@ -135,16 +139,16 @@ function DATABASE:New()
self:_RegisterAirbases()
self.UNITS_Position = 0
--- @param #DATABASE self
local function CheckPlayers( self )
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )}
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
--self:E( { "CoalitionData:", CoalitionData } )
for UnitId, UnitData in pairs( CoalitionData ) do
if UnitData and UnitData:isExist() then
local UnitName = UnitData:getName()
local PlayerName = UnitData:getPlayerName()
local PlayerUnit = UNIT:Find( UnitData )
@@ -163,10 +167,10 @@ function DATABASE:New()
end
end
end
--self:E( "Scheduling" )
--PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
return self
end
@@ -186,12 +190,13 @@ end
function DATABASE:AddUnit( DCSUnitName )
if not self.UNITS[DCSUnitName] then
self:T( { "Add UNIT:", DCSUnitName } )
local UnitRegister = UNIT:Register( DCSUnitName )
self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName )
table.insert( self.UNITS_Index, DCSUnitName )
end
return self.UNITS[DCSUnitName]
end
@@ -200,7 +205,7 @@ end
-- @param #DATABASE self
function DATABASE:DeleteUnit( DCSUnitName )
self.UNITS[DCSUnitName] = nil
self.UNITS[DCSUnitName] = nil
end
--- Adds a Static based on the Static Name in the DATABASE.
@@ -211,7 +216,7 @@ function DATABASE:AddStatic( DCSStaticName )
self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName )
return self.STATICS[DCSStaticName]
end
return nil
end
@@ -220,7 +225,7 @@ end
-- @param #DATABASE self
function DATABASE:DeleteStatic( DCSStaticName )
--self.STATICS[DCSStaticName] = nil
--self.STATICS[DCSStaticName] = nil
end
--- Finds a STATIC based on the StaticName.
@@ -245,12 +250,15 @@ end
--- Adds a Airbase based on the Airbase Name in the DATABASE.
-- @param #DATABASE self
-- @param #string AirbaseName The name of the airbase
-- @param #string AirbaseName The name of the airbase.
-- @return Wrapper.Airbase#AIRBASE Airbase object.
function DATABASE:AddAirbase( AirbaseName )
if not self.AIRBASES[AirbaseName] then
self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName )
end
return self.AIRBASES[AirbaseName]
end
@@ -259,7 +267,7 @@ end
-- @param #string AirbaseName The name of the airbase
function DATABASE:DeleteAirbase( AirbaseName )
self.AIRBASES[AirbaseName] = nil
self.AIRBASES[AirbaseName] = nil
end
--- Finds an AIRBASE based on the AirbaseName.
@@ -280,39 +288,29 @@ do -- Zones
-- @param #string ZoneName The name of the zone.
-- @return Core.Zone#ZONE_BASE The found ZONE.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
end
--- Adds a @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @param Core.Zone#ZONE_BASE Zone The zone.
function DATABASE:AddZone( ZoneName, Zone )
if not self.ZONES[ZoneName] then
self.ZONES[ZoneName] = Zone
end
end
--- Deletes a @{Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteZone( ZoneName )
self.ZONES[ZoneName] = nil
end
--- Finds an @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName
-- @return Core.Zone#ZONE_BASE The found @{Zone}.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
self.ZONES[ZoneName] = nil
end
@@ -329,57 +327,89 @@ do -- Zones
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone )
end
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("#ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } )
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone_Polygon )
end
end
end
end -- zone
do -- Zone_Goal
--- Finds a @{Zone} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Core.Zone#ZONE_BASE The found ZONE.
function DATABASE:FindZoneGoal( ZoneName )
local ZoneFound = self.ZONES_GOAL[ZoneName]
return ZoneFound
end
--- Adds a @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @param Core.Zone#ZONE_BASE Zone The zone.
function DATABASE:AddZoneGoal( ZoneName, Zone )
if not self.ZONES_GOAL[ZoneName] then
self.ZONES_GOAL[ZoneName] = Zone
end
end
--- Deletes a @{Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteZoneGoal( ZoneName )
self.ZONES_GOAL[ZoneName] = nil
end
end -- Zone_Goal
do -- cargo
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
end
end
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
self.CARGOS[CargoName] = nil
self.CARGOS[CargoName] = nil
end
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
--- Checks if the Template name has a #CARGO tag.
-- If yes, the group is a cargo.
-- @param #DATABASE self
@@ -388,10 +418,10 @@ do -- cargo
function DATABASE:IsCargo( TemplateName )
TemplateName = env.getValueDictByKey( TemplateName )
local Cargo = TemplateName:match( "#(CARGO)" )
return Cargo and Cargo == "CARGO"
return Cargo and Cargo == "CARGO"
end
--- Private method that registers new Static Templates within the DATABASE Object.
@@ -400,7 +430,7 @@ do -- cargo
function DATABASE:_RegisterCargos()
local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug!
for CargoGroupName, CargoGroup in pairs( Groups ) do
self:I( { Cargo = CargoGroupName } )
if self:IsCargo( CargoGroupName ) then
@@ -413,12 +443,12 @@ do -- cargo
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
end
end
for CargoStaticName, CargoStatic in pairs( self.STATICS ) do
if self:IsCargo( CargoStaticName ) then
local CargoInfo = CargoStaticName:match("#CARGO(.*)")
@@ -429,7 +459,7 @@ do -- cargo
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
if Category == "SLING" then
self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
@@ -441,7 +471,7 @@ do -- cargo
end
end
end
end
end -- cargo
@@ -485,11 +515,11 @@ end
function DATABASE:AddGroup( GroupName )
if not self.GROUPS[GroupName] then
self:I( { "Add GROUP:", GroupName } )
self:T( { "Add GROUP:", GroupName } )
self.GROUPS[GroupName] = GROUP:Register( GroupName )
end
return self.GROUPS[GroupName]
end
return self.GROUPS[GroupName]
end
--- Adds a player based on the Player Name in the DATABASE.
@@ -497,7 +527,7 @@ end
function DATABASE:AddPlayer( UnitName, PlayerName )
if PlayerName then
self:I( { "Add player for unit:", UnitName, PlayerName } )
self:T( { "Add player for unit:", UnitName, PlayerName } )
self.PLAYERS[PlayerName] = UnitName
self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName )
self.PLAYERSJOINED[PlayerName] = PlayerName
@@ -509,7 +539,7 @@ end
function DATABASE:DeletePlayer( UnitName, PlayerName )
if PlayerName then
self:I( { "Clean player:", PlayerName } )
self:T( { "Clean player:", PlayerName } )
self.PLAYERS[PlayerName] = nil
self.PLAYERUNITS[PlayerName] = nil
end
@@ -591,7 +621,7 @@ function DATABASE:Spawn( SpawnTemplate )
for UnitID, UnitData in pairs( SpawnTemplate.units ) do
self:AddUnit( UnitData.name )
end
return SpawnGroup
end
@@ -623,21 +653,21 @@ end
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name )
if not self.Templates.Groups[GroupTemplateName] then
self.Templates.Groups[GroupTemplateName] = {}
self.Templates.Groups[GroupTemplateName].Status = nil
end
-- Delete the spans from the route, it is not needed and takes memory.
if GroupTemplate.route and GroupTemplate.route.spans then
if GroupTemplate.route and GroupTemplate.route.spans then
GroupTemplate.route.spans = nil
end
GroupTemplate.CategoryID = CategoryID
GroupTemplate.CoalitionID = CoalitionSide
GroupTemplate.CountryID = CountryID
self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName
self.Templates.Groups[GroupTemplateName].Template = GroupTemplate
self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId
@@ -652,7 +682,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do
UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name)
self.Templates.Units[UnitTemplate.name] = {}
self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name
self.Templates.Units[UnitTemplate.name].Template = UnitTemplate
@@ -670,15 +700,15 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
end
self:I( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
Country = self.Templates.Groups[GroupTemplateName].CountryID,
Units = UnitNames
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
Country = self.Templates.Groups[GroupTemplateName].CountryID,
Units = UnitNames
}
)
end
@@ -700,13 +730,13 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
local StaticTemplate = UTILS.DeepCopy( StaticTemplate )
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
StaticTemplate.CategoryID = CategoryID
StaticTemplate.CoalitionID = CoalitionID
StaticTemplate.CountryID = CountryID
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName
self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1]
@@ -717,12 +747,12 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
Category = self.Templates.Statics[StaticTemplateName].CategoryID,
Country = self.Templates.Statics[StaticTemplateName].CountryID
Country = self.Templates.Statics[StaticTemplateName].CountryID
}
)
self:AddStatic( StaticTemplateName )
end
@@ -790,7 +820,7 @@ function DATABASE:_RegisterPlayers()
end
end
end
return self
end
@@ -806,12 +836,12 @@ function DATABASE:_RegisterGroupsAndUnits()
if DCSGroup:isExist() then
local DCSGroupName = DCSGroup:getName()
self:I( { "Register Group:", DCSGroupName } )
self:AddGroup( DCSGroupName )
for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do
local DCSUnitName = DCSUnit:getName()
self:I( { "Register Unit:", DCSUnitName } )
self:AddUnit( DCSUnitName )
@@ -819,13 +849,13 @@ function DATABASE:_RegisterGroupsAndUnits()
else
self:E( { "Group does not exist: ", DCSGroup } )
end
end
end
self:I("Groups:")
self:T("Groups:")
for GroupName, Group in pairs( self.GROUPS ) do
self:I( { "Group:", GroupName } )
self:T( { "Group:", GroupName } )
end
return self
@@ -837,10 +867,10 @@ end
function DATABASE:_RegisterClients()
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
self:I( { "Register Client:", ClientName } )
self:T( { "Register Client:", ClientName } )
self:AddClient( ClientName )
end
return self
end
@@ -854,8 +884,8 @@ function DATABASE:_RegisterStatics()
if DCSStatic:isExist() then
local DCSStaticName = DCSStatic:getName()
self:I( { "Register Static:", DCSStaticName } )
self:T( { "Register Static:", DCSStaticName } )
self:AddStatic( DCSStaticName )
else
self:E( { "Static does not exist: ", DCSStatic } )
@@ -866,18 +896,25 @@ function DATABASE:_RegisterStatics()
return self
end
--- @param #DATABASE self
--- Register all world airbases.
-- @param #DATABASE self
-- @return #DATABASE self
function DATABASE:_RegisterAirbases()
local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
-- Get the airbase name.
local DCSAirbaseName = DCSAirbase:getName()
local DCSAirbaseName = DCSAirbase:getName()
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=DCSAirbase:getID()
self:I( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } )
self:AddAirbase( DCSAirbaseName )
end
-- Add and register airbase.
local airbase=self:AddAirbase( DCSAirbaseName )
-- Debug output.
self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true)))
end
return self
@@ -890,15 +927,21 @@ end
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:_EventOnBirth( Event )
self:F2( { Event } )
self:F( { Event } )
if Event.IniDCSUnit then
if Event.IniObjectCategory == 3 then
self:AddStatic( Event.IniDCSUnitName )
self:AddStatic( Event.IniDCSUnitName )
else
if Event.IniObjectCategory == 1 then
self:AddUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName )
-- Add airbase if it was spawned later in the mission.
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
end
end
if Event.IniObjectCategory == 1 then
@@ -907,6 +950,7 @@ function DATABASE:_EventOnBirth( Event )
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName then
self:I( { "Player Joined:", PlayerName } )
self:AddClient( Event.IniDCSUnitName )
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniUnitName, PlayerName )
end
@@ -929,7 +973,7 @@ function DATABASE:_EventOnDeadOrCrash( Event )
if Event.IniObjectCategory == 3 then
if self.STATICS[Event.IniDCSUnitName] then
self:DeleteStatic( Event.IniDCSUnitName )
end
end
else
if Event.IniObjectCategory == 1 then
if self.UNITS[Event.IniDCSUnitName] then
@@ -938,7 +982,7 @@ function DATABASE:_EventOnDeadOrCrash( Event )
end
end
end
self:AccountDestroys( Event )
end
@@ -992,7 +1036,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set )
self:F2( arg )
local function CoRoutine()
local Count = 0
for ObjectID, Object in pairs( Set ) do
@@ -1001,20 +1045,20 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set )
Count = Count + 1
-- if Count % 100 == 0 then
-- coroutine.yield( false )
-- end
-- end
end
return true
end
-- local co = coroutine.create( CoRoutine )
local co = CoRoutine
local function Schedule()
-- local status, res = coroutine.resume( co )
local status, res = co()
self:T3( { status, res } )
if status == false then
error( res )
end
@@ -1027,8 +1071,9 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set )
return false
end
local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 )
--local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 )
Schedule()
return self
end
@@ -1039,7 +1084,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS )
return self
@@ -1052,7 +1097,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS )
return self
@@ -1065,7 +1110,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.GROUPS )
return self
@@ -1078,9 +1123,9 @@ end
-- @return #DATABASE self
function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERS )
return self
end
@@ -1091,9 +1136,9 @@ end
-- @return #DATABASE self
function DATABASE:ForEachPlayerJoined( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERSJOINED )
return self
end
@@ -1103,9 +1148,9 @@ end
-- @return #DATABASE self
function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERUNITS )
return self
end
@@ -1116,7 +1161,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEachClient( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.CLIENTS )
return self
@@ -1128,7 +1173,7 @@ end
-- @return #DATABASE self
function DATABASE:ForEachCargo( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.CARGOS )
return self
@@ -1166,7 +1211,7 @@ function DATABASE:OnEventNewZone( EventData )
self:F2( { EventData } )
if EventData.Zone then
self:AddZone( EventData.Zone )
self:AddZone( EventData.Zone.ZoneName, EventData.Zone )
end
end
@@ -1204,8 +1249,44 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings )
self.PLAYERSETTINGS[PlayerName] = Settings
end
--- Add a flight group to the data base.
-- @param #DATABASE self
-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup
function DATABASE:AddFlightGroup(flightgroup)
self:I({NewFlightGroup=flightgroup.groupname})
self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup
end
--- Get a flight group from the data base.
-- @param #DATABASE self
-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object.
-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object.
function DATABASE:GetFlightGroup(groupname)
-- Get group and group name.
if type(groupname)=="string" then
else
groupname=groupname:GetName()
end
return self.FLIGHTGROUPS[groupname]
end
--- Add a flight control to the data base.
-- @param #DATABASE self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol
function DATABASE:AddFlightControl(flightcontrol)
self:F2( { flightcontrol } )
self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol
end
--- Get a flight control object from the data base.
-- @param #DATABASE self
-- @param #string airbasename Name of the associated airbase.
-- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.s
function DATABASE:GetFlightControl(airbasename)
return self.FLIGHTCONTROLS[airbasename]
end
--- @param #DATABASE self
function DATABASE:_RegisterTemplates()
@@ -1219,10 +1300,10 @@ function DATABASE:_RegisterTemplates()
if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then
--self.Units[coa_name] = {}
local CoalitionSide = coalition.side[string.upper(CoalitionName)]
if CoalitionName=="red" then
CoalitionSide=coalition.side.NEUTRAL
CoalitionSide=coalition.side.RED
elseif CoalitionName=="blue" then
CoalitionSide=coalition.side.BLUE
else
@@ -1252,10 +1333,10 @@ function DATABASE:_RegisterTemplates()
local CountryName = string.upper(cntry_data.name)
local CountryID = cntry_data.id
self.COUNTRY_ID[CountryName] = CountryID
self.COUNTRY_NAME[CountryID] = CountryName
--self.Units[coa_name][countryName] = {}
--self.Units[coa_name][countryName]["countryId"] = cntry_data.id
@@ -1274,18 +1355,18 @@ function DATABASE:_RegisterTemplates()
for group_num, Template in pairs(obj_type_data.group) do
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
self:_RegisterGroupTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
self:_RegisterGroupTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
)
else
self:_RegisterStaticTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
self:_RegisterStaticTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
)
end --if GroupTemplate and GroupTemplate.units then
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
@@ -1306,35 +1387,35 @@ end
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountHits( Event )
self:F( { Event } )
if Event.IniPlayerName ~= nil then -- It is a player that is hitting something
self:T( "Hitting Something" )
-- What is he hitting?
if Event.TgtCategory then
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.IniPlayerName] = true
end
end
-- 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
if Event.WeaponPlayerName ~= nil then
self:T( "Hitting Scenery" )
-- What is he hitting?
if Event.TgtCategory then
if Event.IniCoalition then -- A coalition object was hit, probably a static.
if Event.WeaponCoalition then -- A coalition object was hit, probably a static.
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.WeaponPlayerName] = true
else -- A scenery object was hit.
@@ -1342,13 +1423,13 @@ end
end
end
end
--- Account the destroys.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountDestroys( Event )
self:F( { Event } )
local TargetUnit = nil
local TargetGroup = nil
local TargetUnitName = ""
@@ -1360,26 +1441,26 @@ end
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
TargetUnit = Event.IniUnit
TargetUnitName = Event.IniDCSUnitName
TargetGroup = Event.IniDCSGroup
TargetGroupName = Event.IniDCSGroupName
TargetPlayerName = Event.IniPlayerName
TargetCoalition = Event.IniCoalition
--TargetCategory = TargetUnit:getCategory()
--TargetCategory = TargetUnit:getDesc().category -- Workaround
TargetCategory = Event.IniCategory
TargetType = Event.IniTypeName
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
local Destroyed = false
-- What is the player destroying?
@@ -1388,8 +1469,3 @@ end
self.DESTROYS[Event.IniUnitName] = true
end
end

View File

@@ -189,7 +189,9 @@ world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001
world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002
world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1004
world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
--- The different types of events supported by MOOSE.
@@ -219,14 +221,24 @@ EVENTS = {
PlayerComment = world.event.S_EVENT_PLAYER_COMMENT,
ShootingStart = world.event.S_EVENT_SHOOTING_START,
ShootingEnd = world.event.S_EVENT_SHOOTING_END,
-- Added with DCS 2.5.1
MarkAdded = world.event.S_EVENT_MARK_ADDED,
MarkChange = world.event.S_EVENT_MARK_CHANGE,
MarkRemoved = world.event.S_EVENT_MARK_REMOVED,
-- Moose Events
NewCargo = world.event.S_EVENT_NEW_CARGO,
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
NewZone = world.event.S_EVENT_NEW_ZONE,
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
-- Added with DCS 2.5.6
DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier
Kill = world.event.S_EVENT_KILL or -1,
Score = world.event.S_EVENT_SCORE or -1,
UnitLost = world.event.S_EVENT_UNIT_LOST or -1,
LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1,
}
--- The Event structure
@@ -272,10 +284,16 @@ EVENTS = {
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
-- @field #string PlaceName The name of the airbase.
--
-- @field weapon The weapon used during the event.
-- @field Weapon
-- @field WeaponName
-- @field WeaponTgtDCSUnit
-- @field #table weapon The weapon used during the event.
-- @field #table Weapon
-- @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.
@@ -456,11 +474,47 @@ local _EVENTMETA = {
Event = "OnEventDeleteZone",
Text = "S_EVENT_DELETE_ZONE"
},
[EVENTS.NewZoneGoal] = {
Order = 1,
Event = "OnEventNewZoneGoal",
Text = "S_EVENT_NEW_ZONE_GOAL"
},
[EVENTS.DeleteZoneGoal] = {
Order = 1,
Event = "OnEventDeleteZoneGoal",
Text = "S_EVENT_DELETE_ZONE_GOAL"
},
[EVENTS.RemoveUnit] = {
Order = -1,
Event = "OnEventRemoveUnit",
Text = "S_EVENT_REMOVE_UNIT"
},
-- Added with DCS 2.5.6
[EVENTS.DetailedFailure] = {
Order = 1,
Event = "OnEventDetailedFailure",
Text = "S_EVENT_DETAILED_FAILURE"
},
[EVENTS.Kill] = {
Order = 1,
Event = "OnEventKill",
Text = "S_EVENT_KILL"
},
[EVENTS.Score] = {
Order = 1,
Event = "OnEventScore",
Text = "S_EVENT_SCORE"
},
[EVENTS.UnitLost] = {
Order = 1,
Event = "OnEventUnitLost",
Text = "S_EVENT_UNIT_LOST"
},
[EVENTS.LandingAfterEjection] = {
Order = 1,
Event = "OnEventLandingAfterEjection",
Text = "S_EVENT_LANDING_AFTER_EJECTION"
},
}
@@ -468,6 +522,9 @@ local _EVENTMETA = {
-- @type EVENT.Events
-- @field #number IniUnit
--- Create new event handler.
-- @param #EVENT self
-- @return #EVENT self
function EVENT:New()
local self = BASE:Inherit( self, BASE:New() )
self:F2()
@@ -498,6 +555,8 @@ function EVENT:Init( EventID, EventClass )
if not self.Events[EventID][EventPriority][EventClass] then
self.Events[EventID][EventPriority][EventClass] = {}
end
return self.Events[EventID][EventPriority][EventClass]
end
@@ -584,7 +643,7 @@ end
-- @param EventID
-- @return #EVENT
function EVENT:OnEventGeneric( EventFunction, EventClass, EventID )
self:F2( { EventID } )
self:F2( { EventID, EventClass, EventFunction } )
local EventData = self:Init( EventID, EventClass )
EventData.EventFunction = EventFunction
@@ -794,6 +853,38 @@ do -- Event Creation
world.onEvent( Event )
end
--- Creation of a New ZoneGoal Event.
-- @param #EVENT self
-- @param Core.Functional#ZONE_GOAL ZoneGoal The ZoneGoal created.
function EVENT:CreateEventNewZoneGoal( ZoneGoal )
self:F( { ZoneGoal } )
local Event = {
id = EVENTS.NewZoneGoal,
time = timer.getTime(),
ZoneGoal = ZoneGoal,
}
world.onEvent( Event )
end
--- Creation of a ZoneGoal Deletion Event.
-- @param #EVENT self
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
function EVENT:CreateEventDeleteZoneGoal( ZoneGoal )
self:F( { ZoneGoal } )
local Event = {
id = EVENTS.DeleteZoneGoal,
time = timer.getTime(),
ZoneGoal = ZoneGoal,
}
world.onEvent( Event )
end
--- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event.
-- @param #EVENT self
-- @param Wrapper.Unit#UNIT PlayerUnit.
@@ -826,260 +917,237 @@ function EVENT:onEvent( Event )
end
-- Get event meta data.
local EventMeta = _EVENTMETA[Event.id]
--self:E( { EventMeta.Text, Event } ) -- Activate the see all incoming events ...
if self and
self.Events and
self.Events[Event.id] and
self.MissionEnd == false and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true
end
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
if Event.IniObjectCategory == Object.Category.UNIT then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
if not Event.IniUnit then
-- Unit can be a CLIENT. Most likely this will be the case ...
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
end
Event.IniDCSGroupName = ""
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
if Event.IniGroup then
Event.IniGroupName = Event.IniDCSGroupName
end
end
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
end
if Event.IniObjectCategory == Object.Category.STATIC then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.CARGO then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.SCENERY then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1!
end
end
if Event.target then
Event.TgtObjectCategory = Event.target:getCategory()
if Event.TgtObjectCategory == Object.Category.UNIT then
Event.TgtDCSUnit = Event.target
Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup()
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = UNIT:FindByName( Event.TgtDCSUnitName )
Event.TgtDCSGroupName = ""
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then
Event.TgtDCSGroupName = Event.TgtDCSGroup:getName()
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
if Event.TgtGroup then
Event.TgtGroupName = Event.TgtDCSGroupName
end
end
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
if Event.TgtObjectCategory == Object.Category.STATIC then
Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
if Event.TgtObjectCategory == Object.Category.SCENERY then
Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
end
if Event.weapon then
Event.Weapon = Event.weapon
Event.WeaponName = Event.Weapon:getTypeName()
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
end
-- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase.
if Event.place then
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
end
-- @FC: something like this should be added.
--[[
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition
Event.MarkGroupID = Event.groupID
end
]]
if Event.cargo then
Event.Cargo = Event.cargo
Event.CargoName = Event.cargo.Name
end
if Event.zone then
Event.Zone = Event.zone
Event.ZoneName = Event.zone.ZoneName
end
local PriorityOrder = EventMeta.Order
local PriorityBegin = PriorityOrder == -1 and 5 or 1
local PriorityEnd = PriorityOrder == -1 and 1 or 5
if Event.IniObjectCategory ~= Object.Category.STATIC then
self:T( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
end
for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do
if self.Events[Event.id][EventPriority] then
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
--if Event.IniObjectCategory ~= Object.Category.STATIC then
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
--end
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
-- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT.
if EventData.EventUnit then
-- So now the EventClass must be a UNIT class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
local UnitName = EventClass:GetName()
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:RemoveEvent( EventClass, Event.id )
end
else
-- If the EventData is for a GROUP, the call directly the EventClass EventFunction for the UNIT in that GROUP.
if EventData.EventGroup then
-- So now the EventClass must be a GROUP class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
-- We can get the name of the EventClass, which is now always a GROUP object.
local GroupName = EventClass:GetName()
if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or
( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then
-- Check if this is a known event?
if EventMeta then
if self and
self.Events and
self.Events[Event.id] and
self.MissionEnd == false and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true
end
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
if Event.IniObjectCategory == Object.Category.UNIT then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
if not Event.IniUnit then
-- Unit can be a CLIENT. Most likely this will be the case ...
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
end
Event.IniDCSGroupName = ""
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
--if Event.IniGroup then
Event.IniGroupName = Event.IniDCSGroupName
--end
end
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
end
if Event.IniObjectCategory == Object.Category.STATIC then
if Event.id==31 then
--env.info("FF event 31")
-- Event.initiator is a Static object representing the pilot. But getName() error due to DCS bug.
Event.IniDCSUnit = Event.initiator
local ID=Event.initiator.id_
Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID))
Event.IniUnitName = Event.IniDCSUnitName
Event.IniCoalition = 0
Event.IniCategory = 0
Event.IniTypeName = "Ejected Pilot"
else
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
end
if Event.IniObjectCategory == Object.Category.CARGO then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.SCENERY then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1!
end
if Event.IniObjectCategory == Object.Category.BASE then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
end
if Event.target then
Event.TgtObjectCategory = Event.target:getCategory()
if Event.TgtObjectCategory == Object.Category.UNIT then
Event.TgtDCSUnit = Event.target
Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup()
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = UNIT:FindByName( Event.TgtDCSUnitName )
Event.TgtDCSGroupName = ""
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then
Event.TgtDCSGroupName = Event.TgtDCSGroup:getName()
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
--if Event.TgtGroup then
Event.TgtGroupName = Event.TgtDCSGroupName
--end
end
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
if Event.TgtObjectCategory == Object.Category.STATIC then
Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
if Event.TgtObjectCategory == Object.Category.SCENERY then
Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
end
end
if Event.weapon then
Event.Weapon = Event.weapon
Event.WeaponName = Event.Weapon:getTypeName()
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
end
-- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase.
if Event.place then
if Event.id==EVENTS.LandingAfterEjection then
-- Place is here the UNIT of which the pilot ejected.
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
--Event.Place=UNIT:Find(Event.place)
else
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
end
end
-- Mark points.
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition
Event.MarkGroupID = Event.groupID
end
if Event.cargo then
Event.Cargo = Event.cargo
Event.CargoName = Event.cargo.Name
end
if Event.zone then
Event.Zone = Event.zone
Event.ZoneName = Event.zone.ZoneName
end
local PriorityOrder = EventMeta.Order
local PriorityBegin = PriorityOrder == -1 and 5 or 1
local PriorityEnd = PriorityOrder == -1 and 1 or 5
if Event.IniObjectCategory ~= Object.Category.STATIC then
self:F( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
end
for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do
if self.Events[Event.id][EventPriority] then
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
--if Event.IniObjectCategory ~= Object.Category.STATIC then
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
--end
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
-- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT.
if EventData.EventUnit then
-- So now the EventClass must be a UNIT class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
local UnitName = EventClass:GetName()
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
@@ -1090,74 +1158,130 @@ function EVENT:onEvent( Event )
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
--self:RemoveEvent( EventClass, Event.id )
self:RemoveEvent( EventClass, Event.id )
end
else
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
if not EventData.EventUnit then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
-- There is an EventFunction defined, so call the EventFunction.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
else
--- If the EventData is for a GROUP, the call directly the EventClass EventFunction for the UNIT in that GROUP.
if EventData.EventGroup then
-- So now the EventClass must be a GROUP class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
-- We can get the name of the EventClass, which is now always a GROUP object.
local GroupName = EventClass:GetName()
if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or
( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
--self:RemoveEvent( EventClass, Event.id )
end
else
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
if not EventData.EventUnit then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
-- There is an EventFunction defined, so call the EventFunction.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
local Result, Value = EventFunction( EventClass, Event )
return Result, Value
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
local Result, Value = EventFunction( EventClass, Event )
return Result, Value
end, ErrorHandler )
end
end
end
end
end
end
end
end
end
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_CARGOs.
-- To prevent this from happening, the Cargo object has a flag NoDestroy.
-- When true, the SET_CARGO won't Remove the Cargo object from the set.
-- But we need to switch that flag off after the event handlers have been called.
if Event.id == EVENTS.DeleteCargo then
Event.Cargo.NoDestroy = nil
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_CARGOs.
-- To prevent this from happening, the Cargo object has a flag NoDestroy.
-- When true, the SET_CARGO won't Remove the Cargo object from the set.
-- But we need to switch that flag off after the event handlers have been called.
if Event.id == EVENTS.DeleteCargo then
Event.Cargo.NoDestroy = nil
end
else
self:T( { EventMeta.Text, Event } )
end
else
self:T( { EventMeta.Text, Event } )
self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring(Event.id)))
end
Event = nil
@@ -1173,7 +1297,7 @@ EVENTHANDLER = {
--- The EVENTHANDLER constructor
-- @param #EVENTHANDLER self
-- @return #EVENTHANDLER
-- @return #EVENTHANDLER self
function EVENTHANDLER:New()
self = BASE:Inherit( self, BASE:New() ) -- #EVENTHANDLER
return self

View File

@@ -71,7 +71,7 @@
--
--
-- ### Author: **FlightControl**
-- ### Contributions:
-- ### Contributions: **funkyfranky**
--
-- ===
--
@@ -81,6 +81,12 @@
do -- FSM
--- @type FSM
-- @field #string ClassName Name of the class.
-- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler.
-- @field #table options Options.
-- @field #table subs Subs.
-- @field #table Scores Scores.
-- @field #string current Current state name.
-- @extends Core.Base#BASE
@@ -369,8 +375,7 @@ do -- FSM
self._EventSchedules = {}
self.CallScheduler = SCHEDULER:New( self )
return self
end
@@ -379,7 +384,6 @@ do -- FSM
-- @param #FSM self
-- @param #string State A string defining the start state.
function FSM:SetStartState( State )
self._StartState = State
self.current = State
end
@@ -389,7 +393,6 @@ do -- FSM
-- @param #FSM self
-- @return #string A string containing the start state.
function FSM:GetStartState()
return self._StartState or {}
end
@@ -406,6 +409,7 @@ do -- FSM
Transition.Event = Event
Transition.To = To
-- Debug message.
self:T2( Transition )
self._Transitions[Transition] = Transition
@@ -414,9 +418,9 @@ do -- FSM
--- Returns a table of the transition rules defined within the FSM.
-- @return #table
function FSM:GetTransitions()
-- @param #FSM self
-- @return #table Transitions.
function FSM:GetTransitions()
return self._Transitions or {}
end
@@ -448,7 +452,8 @@ do -- FSM
--- Returns a table of the SubFSM rules defined within the FSM.
-- @return #table
-- @param #FSM self
-- @return #table Sub processes.
function FSM:GetProcesses()
self:F( { Processes = self._Processes } )
@@ -480,15 +485,17 @@ do -- FSM
end
--- Adds an End state.
function FSM:AddEndState( State )
-- @param #FSM self
-- @param #string State The FSM state.
function FSM:AddEndState( State )
self._EndStates[State] = State
self.endstates[State] = State
end
--- Returns the End states.
function FSM:GetEndStates()
-- @param #FSM self
-- @return #table End states.
function FSM:GetEndStates()
return self._EndStates or {}
end
@@ -532,18 +539,22 @@ do -- FSM
end
--- Returns a table with the scores defined.
function FSM:GetScores()
-- @param #FSM self
-- @param #table Scores.
function FSM:GetScores()
return self._Scores or {}
end
--- Returns a table with the Subs defined.
function FSM:GetSubs()
-- @param #FSM self
-- @return #table Sub processes.
function FSM:GetSubs()
return self.options.subs
end
--- Load call backs.
-- @param #FSM self
-- @param #table CallBackTable Table of call backs.
function FSM:LoadCallBacks( CallBackTable )
for name, callback in pairs( CallBackTable or {} ) do
@@ -551,21 +562,34 @@ do -- FSM
end
end
--- Event map.
-- @param #FSM self
-- @param #table Events Events.
-- @param #table EventStructure Event structure.
function FSM:_eventmap( Events, EventStructure )
local Event = EventStructure.Event
local __Event = "__" .. EventStructure.Event
self[Event] = self[Event] or self:_create_transition(Event)
self[__Event] = self[__Event] or self:_delayed_transition(Event)
-- Debug message.
self:T2( "Added methods: " .. Event .. ", " .. __Event )
Events[Event] = self.Events[Event] or { map = {} }
self:_add_to_map( Events[Event].map, EventStructure )
end
--- Sub maps.
-- @param #FSM self
-- @param #table subs Subs.
-- @param #table sub Sub.
-- @param #string name Name.
function FSM:_submap( subs, sub, name )
--self:F( { sub = sub, name = name } )
subs[sub.From] = subs[sub.From] or {}
subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {}
@@ -578,30 +602,61 @@ do -- FSM
subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop.
subs[sub.From][sub.Event][sub].name = name
subs[sub.From][sub.Event][sub].fsmparent = self
end
--- Call handler.
-- @param #FSM self
-- @param #string step Step "onafter", "onbefore", "onenter", "onleave".
-- @param #string trigger Trigger.
-- @param #table params Parameters.
-- @param #string EventName Event name.
-- @return Value.
function FSM:_call_handler( step, trigger, params, EventName )
--env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName))
local handler = step .. trigger
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
if self[handler] then
--[[
if step == "onafter" or step == "OnAfter" then
self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] )
elseif step == "onbefore" or step == "OnBefore" then
self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. step .. params[2] .. "()" .. ">" .. params[2] .. " >> " .. params[3] )
elseif step == "onenter" or step == "OnEnter" then
self:T( ":::>" .. step .. params[3] .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. step .. params[3] .. "()" .. ">" .. params[3] )
elseif step == "onleave" or step == "OnLeave" then
self:T( ":::>" .. step .. params[1] .. " : " .. params[1] .. ">" .. step .. params[1] .. "()" .. " >> " .. params[2] .. " >> " .. params[3] )
else
self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] )
end
]]
self._EventSchedules[EventName] = nil
-- Error handler.
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
end
return errmsg
end
return errmsg
end
if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
return Value
--return self[handler](self, unpack( params ))
-- Protected call.
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
return Value
end
end
--- @param #FSM self
--- Handler.
-- @param #FSM self
-- @param #string EventName Event name.
-- @param ... Arguments.
function FSM._handler( self, EventName, ... )
local Can, To = self:can( EventName )
@@ -611,7 +666,11 @@ do -- FSM
end
if Can then
-- From state.
local From = self.current
-- Parameters.
local Params = { From, EventName, To, ... }
@@ -622,8 +681,8 @@ do -- FSM
self["onafter".. EventName] or
self["OnAfter".. EventName] or
self["onenter".. To] or
self["OnEnter".. To]
then
self["OnEnter".. To] then
if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName )
return false
@@ -643,8 +702,11 @@ do -- FSM
end
end
end
else
local ClassName = self:GetClassName()
if ClassName == "FSM" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To )
end
@@ -662,46 +724,56 @@ do -- FSM
end
end
-- New current state.
self.current = To
local execute = true
local subtable = self:_gosub( From, EventName )
for _, sub in pairs( subtable ) do
--if sub.nextevent then
-- self:F2( "nextevent = " .. sub.nextevent )
-- self[sub.nextevent]( self )
--end
self:T( "*** FSM *** Sub *** " .. sub.StartEvent )
sub.fsm.fsmparent = self
sub.fsm.ReturnEvents = sub.ReturnEvents
sub.fsm[sub.StartEvent]( sub.fsm )
execute = false
end
local fsmparent, Event = self:_isendstate( To )
if fsmparent and Event then
self:T( "*** FSM *** End *** " .. Event )
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
self:_call_handler("onstate", "change", Params, EventName )
fsmparent[Event]( fsmparent )
execute = false
end
if execute then
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
-- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute!
--if from ~= to then
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
--end
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
self:_call_handler("onstate", "change", Params, EventName )
end
else
self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " )
@@ -709,34 +781,68 @@ do -- FSM
return nil
end
--- Delayed transition.
-- @param #FSM self
-- @param #string EventName Event name.
-- @return #function Function.
function FSM:_delayed_transition( EventName )
return function( self, DelaySeconds, ... )
-- Debug.
self:T2( "Delayed Event: " .. EventName )
local CallID = 0
if DelaySeconds ~= nil then
if DelaySeconds < 0 then -- Only call the event ONCE!
DelaySeconds = math.abs( DelaySeconds )
if not self._EventSchedules[EventName] then
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 )
-- Call _handler.
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
-- Set call ID.
self._EventSchedules[EventName] = CallID
-- Debug output.
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
else
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
-- reschedule
end
else
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 )
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
end
else
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
end
-- Debug.
self:T2( { CallID = CallID } )
end
end
--- Create transition.
-- @param #FSM self
-- @param #string EventName Event name.
-- @return #function Function.
function FSM:_create_transition( EventName )
return function( self, ... ) return self._handler( self, EventName , ... ) end
end
--- Go sub.
-- @param #FSM self
-- @param #string ParentFrom Parent from state.
-- @param #string ParentEvent Parent event name.
-- @return #table Subs.
function FSM:_gosub( ParentFrom, ParentEvent )
local fsmtable = {}
if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then
@@ -746,9 +852,15 @@ do -- FSM
return {}
end
end
--- Is end state.
-- @param #FSM self
-- @param #string Current Current state name.
-- @return #table FSM parent.
-- @return #string Event name.
function FSM:_isendstate( Current )
local FSMParent = self.fsmparent
if FSMParent and self.endstates[Current] then
--self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
FSMParent.current = Current
@@ -765,9 +877,14 @@ do -- FSM
return nil
end
--- Add to map.
-- @param #FSM self
-- @param #table Map Map.
-- @param #table Event Event table.
function FSM:_add_to_map( Map, Event )
self:F3( { Map, Event } )
if type(Event.From) == 'string' then
Map[Event.From] = Event.To
else
@@ -775,33 +892,60 @@ do -- FSM
Map[From] = Event.To
end
end
self:T3( { Map, Event } )
end
--- Get current state.
-- @param #FSM self
-- @return #string Current FSM state.
function FSM:GetState()
return self.current
end
--- Get current state.
-- @param #FSM self
-- @return #string Current FSM state.
function FSM:GetCurrentState()
return self.current
end
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
function FSM:Is( State )
return self.current == State
end
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
function FSM:is(state)
return self.current == state
end
--- Check if can do an event.
-- @param #FSM self
-- @param #string e Event name.
-- @return #boolean If true, FSM can do the event.
-- @return #string To state.
function FSM:can(e)
local Event = self.Events[e]
self:F3( { self.current, Event } )
--self:F3( { self.current, Event } )
local To = Event and Event.map[self.current] or Event.map['*']
return To ~= nil, To
end
--- Check if cannot do an event.
-- @param #FSM self
-- @param #string e Event name.
-- @return #boolean If true, FSM cannot do the event.
function FSM:cannot(e)
return not self:can(e)
end

View File

@@ -15,6 +15,7 @@
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky**
--
-- ===
--
@@ -142,6 +143,7 @@ do -- Goal
-- @param #GOAL self
-- @param #string PlayerName The name of the player.
function GOAL:AddPlayerContribution( PlayerName )
self:F({PlayerName})
self.Players[PlayerName] = self.Players[PlayerName] or 0
self.Players[PlayerName] = self.Players[PlayerName] + 1
self.TotalContributions = self.TotalContributions + 1

View File

@@ -238,7 +238,7 @@ do -- MENU_BASE
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
self.Menus = {}
self.MenuCount = 0
self.MenuTime = timer.getTime()
self.MenuStamp = timer.getTime()
self.MenuRemoveParent = false
if self.ParentMenu then
@@ -285,13 +285,31 @@ do -- MENU_BASE
function MENU_BASE:GetMenu( MenuText )
return self.Menus[MenuText]
end
--- Sets a menu stamp for later prevention of menu removal.
-- @param #MENU_BASE self
-- @param MenuStamp
-- @return #MENU_BASE
function MENU_BASE:SetStamp( MenuStamp )
self.MenuStamp = MenuStamp
return self
end
--- Gets a menu stamp for later prevention of menu removal.
-- @param #MENU_BASE self
-- @return MenuStamp
function MENU_BASE:GetStamp()
return timer.getTime()
end
--- Sets a time stamp for later prevention of menu removal.
-- @param #MENU_BASE self
-- @param MenuTime
-- @param MenuStamp
-- @return #MENU_BASE
function MENU_BASE:SetTime( MenuTime )
self.MenuTime = MenuTime
function MENU_BASE:SetTime( MenuStamp )
self.MenuStamp = MenuStamp
return self
end
@@ -443,7 +461,7 @@ do -- MENU_MISSION
--- Removes the main menu and the sub menus recursively of this MENU_MISSION.
-- @param #MENU_MISSION self
-- @return #nil
function MENU_MISSION:Remove( MenuTime, MenuTag )
function MENU_MISSION:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
@@ -451,7 +469,7 @@ do -- MENU_MISSION
if MissionMenu == self then
self:RemoveSubMenus()
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
@@ -537,7 +555,7 @@ do -- MENU_MISSION_COMMAND
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
@@ -666,7 +684,7 @@ do -- MENU_COALITION
--- Removes the main menu and the sub menus recursively of this MENU_COALITION.
-- @param #MENU_COALITION self
-- @return #nil
function MENU_COALITION:Remove( MenuTime, MenuTag )
function MENU_COALITION:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
@@ -674,7 +692,7 @@ do -- MENU_COALITION
if CoalitionMenu == self then
self:RemoveSubMenus()
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
@@ -758,14 +776,14 @@ do -- MENU_COALITION_COMMAND
--- Removes a radio command item for a coalition
-- @param #MENU_COALITION_COMMAND self
-- @return #nil
function MENU_COALITION_COMMAND:Remove( MenuTime, MenuTag )
function MENU_COALITION_COMMAND:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
if CoalitionMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
@@ -907,13 +925,13 @@ do
--- Removes the sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP self
function MENU_GROUP:RemoveSubMenus( MenuTime, MenuTag )
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuTime, MenuTag )
Menu:Remove( MenuStamp, MenuTag )
end
self.Menus = nil
@@ -923,18 +941,18 @@ do
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP:Remove( MenuTime, MenuTag )
function MENU_GROUP:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
self:RemoveSubMenus( MenuTime, MenuTag )
if not MenuTime or self.MenuTime ~= MenuTime then
self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
@@ -1014,17 +1032,17 @@ do
--- Removes a menu structure for a group.
-- @param #MENU_GROUP_COMMAND self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_COMMAND:Remove( MenuTime, MenuTag )
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
@@ -1136,13 +1154,13 @@ do
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
-- @param #MENU_GROUP_DELAYED self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP_DELAYED self
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuTime, MenuTag )
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuTime, MenuTag )
Menu:Remove( MenuStamp, MenuTag )
end
self.Menus = nil
@@ -1152,18 +1170,18 @@ do
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP_DELAYED self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_DELAYED:Remove( MenuTime, MenuTag )
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
self:RemoveSubMenus( MenuTime, MenuTag )
if not MenuTime or self.MenuTime ~= MenuTime then
self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
@@ -1263,17 +1281,17 @@ do
--- Removes a menu structure for a group.
-- @param #MENU_GROUP_COMMAND_DELAYED self
-- @param MenuTime
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuTime, MenuTag )
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )

File diff suppressed because it is too large Load Diff

View File

@@ -9,37 +9,37 @@
--
-- The Radio contains 2 classes : RADIO and BEACON
--
-- What are radio communications in DCS ?
-- What are radio communications in DCS?
--
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
--
-- How to supply DCS my own Sound Files ?
-- How to supply DCS my own Sound Files?
--
-- * Your sound files need to be encoded in **.ogg** or .wav,
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
-- * For simplicty sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
--
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
--
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
--
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
-- If a FC3 airacraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircaft isn't compatible,
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
-- you won't hear/be able to use the TACAN beacon informations.
--
-- ===
--
-- ### Author: Hugues "Grey_Echo" Bousquet
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Radio
-- @image Core_Radio.JPG
--- Models the radio capabilty.
--- Models the radio capability.
--
-- ## RADIO usage
--
@@ -56,34 +56,35 @@
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
--
-- Additional Methods to set relevant parameters if the transmiter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
--
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- Additional Methods to set relevant parameters if the transmiter is any other @{Wrapper.Positionable#POSITIONABLE}
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- What is this power thing ?
-- What is this power thing?
--
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmiter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on,
-- * For reference, a standard VOR station has a 100W antenna, a standard AA TACAN has a 120W antenna, and civilian ATC's antenna usually range between 300 and 500W,
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- @type RADIO
-- @field Positionable#POSITIONABLE Positionable The transmiter
-- @field #string FileName Name of the sound file
-- @field #number Frequency Frequency of the transmission in Hz
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM)
-- @field #string Subtitle Subtitle of the transmission
-- @field #number SubtitleDuration Duration of the Subtitle in seconds
-- @field #number Power Power of the antenna is Watts
-- @field #boolean Loop (default true)
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file played.
-- @field #number Frequency Frequency of the transmission in Hz.
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
-- @field #string Subtitle Subtitle of the transmission.
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
-- @field #number Power Power of the antenna is Watts.
-- @field #boolean Loop Transmission is repeated (default true).
-- @field #string alias Name of the radio transmitter.
-- @extends Core.Base#BASE
RADIO = {
ClassName = "RADIO",
@@ -93,19 +94,19 @@ RADIO = {
Subtitle = "",
SubtitleDuration = 0,
Power = 100,
Loop = true,
Loop = false,
alias=nil,
}
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
-- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #RADIO Radio
-- @return #nil If Positionable is invalid
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
function RADIO:New(Positionable)
-- Inherit base
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self.Loop = true -- default Loop to true (not sure the above RADIO definition actually is working)
self:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
@@ -113,11 +114,27 @@ function RADIO:New(Positionable)
return self
end
self:E({"The passed positionable is invalid, no RADIO created", Positionable})
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil
end
--- Check validity of the filename passed and sets RADIO.FileName
--- Set alias of the transmitter.
-- @param #RADIO self
-- @param #string alias Name of the radio transmitter.
-- @return #RADIO self
function RADIO:SetAlias(alias)
self.alias=tostring(alias)
return self
end
--- Get alias of the transmitter.
-- @param #RADIO self
-- @return #string Name of the transmitter.
function RADIO:GetAlias()
return tostring(self.alias)
end
--- Set the file name for the radio transmission.
-- @param #RADIO self
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
-- @return #RADIO self
@@ -125,49 +142,63 @@ function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
self.FileName = FileName
return self
end
end
self:E({"File name invalid. Maybe something wrong with the extension ?", self.FileName})
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
return self
end
--- Check validity of the frequency passed and sets RADIO.Frequency
--- Set the frequency for the radio transmission.
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
-- @param #RADIO self
-- @param #number Frequency in MHz (Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz)
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz.
-- @return #RADIO self
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency) == "number" then
-- If frequency is in range
if (Frequency >= 30 and Frequency < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then
self.Frequency = Frequency * 1000000 -- Conversion in Hz
if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
-- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
local commandSetFrequency={
id = "SetFrequency",
params = {
frequency = self.Frequency,
frequency = self.Frequency,
modulation = self.Modulation,
}
})
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
end
end
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", self.Frequency})
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
return self
end
--- Check validity of the frequency passed and sets RADIO.Modulation
--- Set AM or FM modulation of the radio transmitter.
-- @param #RADIO self
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
-- @return #RADIO self
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
@@ -183,23 +214,24 @@ end
--- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self
-- @param #number Power in W
-- @param #number Power Power in W.
-- @return #RADIO self
function RADIO:SetPower(Power)
self:F2(Power)
if type(Power) == "number" then
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
return self
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
self:E({"Power is invalid. Power unchanged.", self.Power})
return self
end
--- Check validity of the loop passed and sets RADIO.Loop
--- Set message looping on or off.
-- @param #RADIO self
-- @param #boolean Loop
-- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self
-- @usage
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop) == "boolean" then
@@ -232,13 +264,12 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end
if type(SubtitleDuration) == "number" then
if math.floor(math.abs(SubtitleDuration)) == SubtitleDuration then
self.SubtitleDuration = SubtitleDuration
return self
end
self.SubtitleDuration = SubtitleDuration
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
@@ -246,10 +277,10 @@ end
-- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self
-- @param #string FileName
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #string FileName Name of the sound file that will be transmitted.
-- @param #number Frequency Frequency in MHz.
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
-- @param #number Power Power in W.
-- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power})
@@ -269,31 +300,43 @@ end
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @param #string FileName
-- @param #string Subtitle
-- @param #number SubtitleDuration in s
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop
-- @param #string FileName Name of sound file.
-- @param #string Subtitle Subtitle to be displayed with sound file.
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
-- @param #number Frequency Frequency in MHz.
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop If true, loop message.
-- @return #RADIO self
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
-- Set file name.
self:SetFileName(FileName)
local Duration = 5
if SubtitleDuration then Duration = SubtitleDuration end
-- SubtitleDuration argument was missing, adding it
if Subtitle then self:SetSubtitle(Subtitle, Duration) end
-- self:SetSubtitleDuration is non existent, removing faulty line
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Loop then self:SetLoop(Loop) end
-- Set modulation AM/FM.
if Modulation then
self:SetModulation(Modulation)
end
-- Set frequency.
if Frequency then
self:SetFrequency(Frequency)
end
-- Set subtitle.
if Subtitle then
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
end
-- Set Looping.
if Loop then
self:SetLoop(Loop)
end
return self
end
--- Actually Broadcast the transmission
--- Broadcast the transmission.
-- * The Radio has to be populated with the new transmission before broadcasting.
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
@@ -302,31 +345,38 @@ end
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
-- @param #RADIO self
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
-- @return #RADIO self
function RADIO:Broadcast()
self:F()
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self:T2("Broadcasting from a UNIT or a GROUP")
self.Positionable:SetCommand({
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
self:T("Broadcasting from a UNIT or a GROUP")
local commandTransmitMessage={
id = "TransmitMessage",
params = {
file = self.FileName,
duration = self.SubtitleDuration,
subtitle = self.Subtitle,
loop = self.Loop,
}
})
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
-- If the POSITIONABLE is anything else, we revert to the general singleton function
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
self:T2("Broadcasting from a POSITIONABLE")
self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
end
return self
end
--- Stops a transmission
-- This function is especially usefull to stop the broadcast of looped transmissions
-- @param #RADIO self
@@ -335,10 +385,10 @@ function RADIO:StopBroadcast()
self:F()
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
id = "StopTransmission",
params = {}
})
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID))
@@ -364,24 +414,120 @@ end
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE
BEACON = {
ClassName = "BEACON",
Positionable = nil,
name=nil,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic}
--- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN TACtical Air Navigation system.
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number PRMG_LOCALIZER
-- @field #number PRMG_GLIDESLOPE
-- @field #number ICLS Same as ICLS glideslope.
-- @field #number ICLS_LOCALIZER
-- @field #number ICLS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 128,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16424,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
PRMG_LOCALIZER = 33024,
PRMG_GLIDESLOPE = 33280,
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
ICLS_LOCALIZER = 131328,
ICLS_GLIDESLOPE = 131584,
NAUTICAL_HOMER = 65536,
}
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
-- @type BEACON.System
-- @field #number PAR_10 ?
-- @field #number RSBN_5 Russian VOR/DME system.
-- @field #number TACAN TACtical Air Navigation system on ground.
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
-- @field #number VOR Very High Frequency Omni-Directional Range
-- @field #number ILS_LOCALIZER ILS localizer
-- @field #number ILS_GLIDESLOPE ILS glideslope.
-- @field #number PRGM_LOCALIZER PRGM localizer.
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
-- @field #number BROADCAST_STATION Broadcast station.
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
-- @field #number ICLS_LOCALIZER Carrier landing system.
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER_X = 4,
TACAN_TANKER_Y = 5,
VOR = 6,
ILS_LOCALIZER = 7,
ILS_GLIDESLOPE = 8,
PRMG_LOCALIZER = 9,
PRMG_GLIDESLOPE = 10,
BROADCAST_STATION = 11,
VORTAC = 12,
TACAN_AA_MODE_X = 13,
TACAN_AA_MODE_Y = 14,
VORDME = 15,
ICLS_LOCALIZER = 16,
ICLS_GLIDESLOPE = 17,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon
-- @return #nil If Positionable is invalid
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
local self = BASE:Inherit(self, BASE:New())
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s", tostring(self.name)))
return self
end
@@ -390,44 +536,95 @@ function BEACON:New(Positionable)
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
-- Beacon type.
local Type=BEACON.Type.TACAN
if TACANChannel < 64 then
B = 1
end
-- Beacon system.
local System=BEACON.System.TACAN
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" then
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
end
end
return (A + TACANChannel - B) * 1000000
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
@@ -480,7 +677,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
@@ -591,4 +788,45 @@ function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
end
return self
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

@@ -0,0 +1,579 @@
--- **Core** - Queues Radio Transmissions.
--
-- ===
--
-- ## Features:
--
-- * Managed Radio Transmissions.
--
-- ===
--
-- ### Authors: funkyfranky
--
-- @module Core.RadioQueue
-- @image Core_Radio.JPG
--- Manages radio transmissions.
--
-- @type RADIOQUEUE
-- @field #string ClassName Name of the class "RADIOQUEUE".
-- @field #boolean Debugmode Debug mode. More info.
-- @field #string lid ID for dcs.log.
-- @field #number frequency The radio frequency in Hz.
-- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM.
-- @field Core.Scheduler#SCHEDULER scheduler The scheduler.
-- @field #string RQid The radio queue scheduler ID.
-- @field #table queue The queue of transmissions.
-- @field #string alias Name of the radio.
-- @field #number dt Time interval in seconds for checking the radio queue.
-- @field #number delay Time delay before starting the radio queue.
-- @field #number Tlast Time (abs) when the last transmission finished.
-- @field Core.Point#COORDINATE sendercoord Coordinate from where transmissions are broadcasted.
-- @field #number sendername Name of the sending unit or static.
-- @field #boolean senderinit Set frequency was initialized.
-- @field #number power Power of radio station in Watts. Default 100 W.
-- @field #table numbers Table of number transmission parameters.
-- @field #boolean checking Scheduler is checking the radio queue.
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
-- @extends Core.Base#BASE
RADIOQUEUE = {
ClassName = "RADIOQUEUE",
Debugmode = nil,
lid = nil,
frequency = nil,
modulation = nil,
scheduler = nil,
RQid = nil,
queue = {},
alias = nil,
dt = nil,
delay = nil,
Tlast = nil,
sendercoord = nil,
sendername = nil,
senderinit = nil,
power = nil,
numbers = {},
checking = nil,
schedonce = false,
}
--- Radio queue transmission data.
-- @type RADIOQUEUE.Transmission
-- @field #string filename Name of the file to be transmitted.
-- @field #string path Path in miz file where the file is located.
-- @field #number duration Duration in seconds.
-- @field #string subtitle Subtitle of the transmission.
-- @field #number subduration Duration of the subtitle being displayed.
-- @field #number Tstarted Mission time (abs) in seconds when the transmission started.
-- @field #boolean isplaying If true, transmission is currently playing.
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
-- @field #number interval Interval in seconds before next transmission.
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
-- @param #RADIOQUEUE self
-- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
-- @param #string alias (Optional) Name of the radio queue.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation, alias)
-- Inherit base
local self=BASE:Inherit(self, BASE:New()) -- #RADIOQUEUE
self.alias=alias or "My Radio"
self.lid=string.format("RADIOQUEUE %s | ", self.alias)
if frequency==nil then
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
return nil
end
-- Frequency in Hz.
self.frequency=frequency*1000000
-- Modulation.
self.modulation=modulation or radio.modulation.AM
-- Set radio power.
self:SetRadioPower()
-- Scheduler.
self.scheduler=SCHEDULER:New()
self.scheduler:NoTrace()
return self
end
--- Start the radio queue.
-- @param #RADIOQUEUE self
-- @param #number delay (Optional) Delay in seconds, before the radio queue is started. Default 1 sec.
-- @param #number dt (Optional) Time step in seconds for checking the queue. Default 0.01 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Start(delay, dt)
-- Delay before start.
self.delay=delay or 1
-- Time interval for queue check.
self.dt=dt or 0.01
-- Debug message.
self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)", self.alias, self.frequency/1000000, self.modulation, self.delay, self.dt))
-- Start Scheduler.
if self.schedonce then
self:_CheckRadioQueueDelayed(delay)
else
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt)
end
return self
end
--- Stop the radio queue. Stop scheduler and delete queue.
-- @param #RADIOQUEUE self
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Stop()
self:I(self.lid.."Stopping RADIOQUEUE.")
self.scheduler:Stop(self.RQid)
self.queue={}
return self
end
--- Set coordinate from where the transmission is broadcasted.
-- @param #RADIOQUEUE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the sender.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderCoordinate(coordinate)
self.sendercoord=coordinate
return self
end
--- Set name of unit or static from which transmissions are made.
-- @param #RADIOQUEUE self
-- @param #string name Name of the unit or static used for transmissions.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderUnitName(name)
self.sendername=name
return self
end
--- Set radio power. Note that this only applies if no relay unit is used.
-- @param #RADIOQUEUE self
-- @param #number power Radio power in Watts. Default 100 W.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetRadioPower(power)
self.power=power or 100
return self
end
--- Set parameters of a digit.
-- @param #RADIOQUEUE self
-- @param #number digit The digit 0-9.
-- @param #string filename The name of the sound file.
-- @param #number duration The duration of the sound file in seconds.
-- @param #string path The directory within the miz file where the sound is located. Default "l10n/DEFAULT/".
-- @param #string subtitle Subtitle of the transmission.
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetDigit(digit, filename, duration, path, subtitle, subduration)
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
transmission.subtitle=nil
transmission.subduration=nil
-- Convert digit to string in case it is given as a number.
if type(digit)=="number" then
digit=tostring(digit)
end
-- Set transmission.
self.numbers[digit]=transmission
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
-- Init.
transmission.isplaying=false
transmission.Tstarted=nil
-- Add to queue.
table.insert(self.queue, transmission)
-- Start checking.
if self.schedonce and not self.checking then
self:_CheckRadioQueueDelayed()
end
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
-- @param #number duration Duration in seconds the file lasts.
-- @param #number path Directory path inside the miz file where the sound file is located. Default "l10n/DEFAULT/".
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @param #string subtitle Subtitle of the transmission.
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
-- Sanity checks.
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
return nil
end
if type(filename)~="string" then
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
return nil
end
if not duration then
self:E(self.lid.."ERROR: No duration of transmission specified.")
return nil
end
if type(duration)~="number" then
self:E(self.lid.."ERROR: Duration specified is NOT a number.")
return nil
end
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
if transmission.subtitle then
transmission.subduration=subduration or 5
else
transmission.subduration=nil
end
-- Add transmission to queue.
self:AddTransmission(transmission)
return self
end
--- Convert a number (as string) into a radio transmission.
-- E.g. for board number or headings.
-- @param #RADIOQUEUE self
-- @param #string number Number string, e.g. "032" or "183".
-- @param #number delay Delay before transmission in seconds.
-- @param #number interval Interval between the next call.
-- @return #number Duration of the call in seconds.
function RADIOQUEUE:Number2Transmission(number, delay, interval)
--- Split string into characters.
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars, c)
end
return chars
end
-- Split string into characters.
local numbers=_split(number)
local wait=0
for i=1,#numbers do
-- Current number
local n=numbers[i]
-- Radio call.
local transmission=UTILS.DeepCopy(self.numbers[n]) --#RADIOQUEUE.Transmission
transmission.Tplay=timer.getAbsTime()+(delay or 0)
if interval and i==1 then
transmission.interval=interval
end
self:AddTransmission(transmission)
-- Add up duration of the number.
wait=wait+transmission.duration
end
-- Return the total duration of the call.
return wait
end
--- Broadcast radio message.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission.
function RADIOQUEUE:Broadcast(transmission)
-- Get unit sending the transmission.
local sender=self:_GetRadioSender()
-- Construct file name.
local filename=string.format("%s%s", transmission.path, transmission.filename)
if sender then
-- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message.
self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName()))
if not self.senderinit then
-- Command to set the Frequency for the transmission.
local commandFrequency={
id="SetFrequency",
params={
frequency=self.frequency, -- Frequency in Hz.
modulation=self.modulation,
}}
-- Set commend for frequency
sender:SetCommand(commandFrequency)
self.senderinit=true
end
-- Set subtitle only if duration>0 sec.
local subtitle=nil
local duration=nil
if transmission.subtitle and transmission.subduration and transmission.subduration>0 then
subtitle=transmission.subtitle
duration=transmission.subduration
end
-- Command to tranmit the call.
local commandTransmit={
id = "TransmitMessage",
params = {
file=filename,
duration=duration,
subtitle=subtitle,
loop=false,
}}
-- Set command for radio transmission.
sender:SetCommand(commandTransmit)
-- Debug message.
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll()
end
else
-- Broadcasting from carrier. No subtitle possible. Need to send messages to players.
self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission()."))
-- Position from where to transmit.
local vec3=nil
-- Try to get positon from sender unit/static.
if self.sendername then
vec3=self:_GetRadioSenderCoord()
end
-- Try to get fixed positon.
if self.sendercoord and not vec3 then
vec3=self.sendercoord:GetVec3()
end
-- Transmit via trigger.
if vec3 then
self:T("Sending")
self:T( { filename = filename, vec3 = vec3, modulation = self.modulation, frequency = self.frequency, power = self.power } )
-- Trigger transmission.
trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power)
-- Debug message.
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll()
end
end
end
end
--- Start checking the radio queue.
-- @param #RADIOQUEUE self
-- @param #number delay Delay in seconds before checking.
function RADIOQUEUE:_CheckRadioQueueDelayed(delay)
self.checking=true
self:ScheduleOnce(delay or self.dt, RADIOQUEUE._CheckRadioQueue, self)
end
--- Check radio queue for transmissions to be broadcasted.
-- @param #RADIOQUEUE self
function RADIOQUEUE:_CheckRadioQueue()
--env.info("FF check radio queue "..self.alias)
-- Check if queue is empty.
if #self.queue==0 then
-- Queue is now empty. Nothing to else to do.
self.checking=false
return
end
-- Get current abs time.
local time=timer.getAbsTime()
local playing=false
local next=nil --#RADIOQUEUE.Transmission
local remove=nil
for i,_transmission in ipairs(self.queue) do
local transmission=_transmission --#RADIOQUEUE.Transmission
-- Check if transmission time has passed.
if time>=transmission.Tplay then
-- Check if transmission is currently playing.
if transmission.isplaying then
-- Check if transmission is finished.
if time>=transmission.Tstarted+transmission.duration then
-- Transmission over.
transmission.isplaying=false
-- Remove ith element in queue.
remove=i
-- Store time last transmission finished.
self.Tlast=time
else -- still playing
-- Transmission is still playing.
playing=true
end
else -- not playing yet
local Tlast=self.Tlast
if transmission.interval==nil then
-- Not playing ==> this will be next.
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
-- We got a transmission or one with an interval that is not due yet. No need for anything else.
if next or Tlast then
break
end
end
else
-- Transmission not due yet.
end
end
-- Found a new transmission.
if next~=nil and not playing then
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
end
-- Remove completed calls from queue.
if remove then
table.remove(self.queue, remove)
end
-- Check queue.
if self.schedonce then
self:_CheckRadioQueueDelayed()
end
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive.
function RADIOQUEUE:_GetRadioSender()
-- Check if we have a sending aircraft.
local sender=nil --Wrapper.Unit#UNIT
-- Try the general default.
if self.sendername then
-- First try to find a unit
sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() and sender:IsAir() then
return sender
end
end
return nil
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return DCS#Vec3 Vector 3D.
function RADIOQUEUE:_GetRadioSenderCoord()
local vec3=nil
-- Try the general default.
if self.sendername then
-- First try to find a unit
local sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() then
return sender:GetVec3()
end
-- Now try a static.
local sender=STATIC:FindByName( self.sendername, false )
-- Check that sender is alive and an aircraft.
if sender then
return sender:GetVec3()
end
end
return nil
end

View File

@@ -0,0 +1,405 @@
--- **Core** - Makes the radio talk.
--
-- ===
--
-- ## Features:
--
-- * Send text strings using a vocabulary that is converted in spoken language.
-- * Possiblity to implement multiple language.
--
-- ===
--
-- ### Authors: FlightControl
--
-- @module Core.RadioSpeech
-- @image Core_Radio.JPG
--- Makes the radio speak.
--
-- # RADIOSPEECH usage
--
--
-- @type RADIOSPEECH
-- @extends Core.RadioQueue#RADIOQUEUE
RADIOSPEECH = {
ClassName = "RADIOSPEECH",
Vocabulary = {
EN = {},
DE = {},
RU = {},
}
}
RADIOSPEECH.Vocabulary.EN = {
["1"] = { "1", 0.25 },
["2"] = { "2", 0.25 },
["3"] = { "3", 0.30 },
["4"] = { "4", 0.35 },
["5"] = { "5", 0.35 },
["6"] = { "6", 0.42 },
["7"] = { "7", 0.38 },
["8"] = { "8", 0.20 },
["9"] = { "9", 0.32 },
["10"] = { "10", 0.35 },
["11"] = { "11", 0.40 },
["12"] = { "12", 0.42 },
["13"] = { "13", 0.38 },
["14"] = { "14", 0.42 },
["15"] = { "15", 0.42 },
["16"] = { "16", 0.52 },
["17"] = { "17", 0.59 },
["18"] = { "18", 0.40 },
["19"] = { "19", 0.47 },
["20"] = { "20", 0.38 },
["30"] = { "30", 0.29 },
["40"] = { "40", 0.35 },
["50"] = { "50", 0.32 },
["60"] = { "60", 0.44 },
["70"] = { "70", 0.48 },
["80"] = { "80", 0.26 },
["90"] = { "90", 0.36 },
["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 },
["chevy"] = { "chevy", 0.35 },
["colt"] = { "colt", 0.35 },
["springfield"] = { "springfield", 0.65 },
["dodge"] = { "dodge", 0.35 },
["enfield"] = { "enfield", 0.5 },
["ford"] = { "ford", 0.32 },
["pontiac"] = { "pontiac", 0.55 },
["uzi"] = { "uzi", 0.28 },
["degrees"] = { "degrees", 0.5 },
["kilometers"] = { "kilometers", 0.65 },
["km"] = { "kilometers", 0.65 },
["miles"] = { "miles", 0.45 },
["meters"] = { "meters", 0.41 },
["mi"] = { "miles", 0.45 },
["feet"] = { "feet", 0.29 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["returning to base"] = { "returning_to_base", 0.85 },
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
["intercepting bogeys"] = { "intercepting_bogeys", 1.00 },
["engaging ground target"] = { "engaging_ground_target", 1.20 },
["engaging bogeys"] = { "engaging_bogeys", 0.81 },
["wheels up"] = { "wheels_up", 0.42 },
["landing at base"] = { "landing at base", 0.8 },
["patrolling"] = { "patrolling", 0.55 },
["for"] = { "for", 0.31 },
["and"] = { "and", 0.31 },
["at"] = { "at", 0.3 },
["dot"] = { "dot", 0.26 },
["defender"] = { "defender", 0.45 },
}
RADIOSPEECH.Vocabulary.RU = {
["1"] = { "1", 0.34 },
["2"] = { "2", 0.30 },
["3"] = { "3", 0.23 },
["4"] = { "4", 0.51 },
["5"] = { "5", 0.31 },
["6"] = { "6", 0.44 },
["7"] = { "7", 0.25 },
["8"] = { "8", 0.43 },
["9"] = { "9", 0.45 },
["10"] = { "10", 0.53 },
["11"] = { "11", 0.66 },
["12"] = { "12", 0.70 },
["13"] = { "13", 0.66 },
["14"] = { "14", 0.80 },
["15"] = { "15", 0.65 },
["16"] = { "16", 0.75 },
["17"] = { "17", 0.74 },
["18"] = { "18", 0.85 },
["19"] = { "19", 0.80 },
["20"] = { "20", 0.58 },
["30"] = { "30", 0.51 },
["40"] = { "40", 0.51 },
["50"] = { "50", 0.67 },
["60"] = { "60", 0.76 },
["70"] = { "70", 0.68 },
["80"] = { "80", 0.84 },
["90"] = { "90", 0.71 },
["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["степени"] = { "degrees", 0.5 },
["километров"] = { "kilometers", 0.65 },
["km"] = { "kilometers", 0.65 },
["миль"] = { "miles", 0.45 },
["mi"] = { "miles", 0.45 },
["метры"] = { "meters", 0.41 },
["m"] = { "meters", 0.41 },
["ноги"] = { "feet", 0.37 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
["колеса вверх"] = { "wheels_up", 0.92 },
["посадка на базу"] = { "landing at base", 1.04 },
["патрулирующий"] = { "patrolling", 0.96 },
["за"] = { "for", 0.27 },
["и"] = { "and", 0.17 },
["в"] = { "at", 0.19 },
["dot"] = { "dot", 0.51 },
["defender"] = { "defender", 0.45 },
}
--- Create a new RADIOSPEECH object for a given radio frequency/modulation.
-- @param #RADIOSPEECH self
-- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
-- @return #RADIOSPEECH self The RADIOSPEECH object.
function RADIOSPEECH:New(frequency, modulation)
-- Inherit base
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
self.Language = "EN"
self:BuildTree()
return self
end
function RADIOSPEECH:SetLanguage( Langauge )
self.Language = Langauge
end
--- Add Sentence to the Speech collection.
-- @param #RADIOSPEECH self
-- @param #string RemainingSentence The remaining sentence during recursion.
-- @param #table Speech The speech node.
-- @param #string Sentence The full sentence.
-- @param #string Data The speech data.
-- @return #RADIOSPEECH self The RADIOSPEECH object.
function RADIOSPEECH:AddSentenceToSpeech( RemainingSentence, Speech, Sentence, Data )
self:I( { RemainingSentence, Speech, Sentence, Data } )
local Token, RemainingSentence = RemainingSentence:match( "^ *([^ ]+)(.*)" )
self:I( { Token = Token, RemainingSentence = RemainingSentence } )
-- Is there a Token?
if Token then
-- We check if the Token is already in the Speech collection.
if not Speech[Token] then
-- There is not yet a vocabulary registered for this.
Speech[Token] = {}
if RemainingSentence and RemainingSentence ~= "" then
-- We use recursion to iterate through the complete Sentence, and make a chain of Tokens.
-- The last Speech node in the collection contains the Sentence and the Data to be spoken.
-- This to ensure that during the actual speech:
-- - Complete sentences are being understood.
-- - Words without speech are ignored.
-- - Incorrect sequence of words are ignored.
Speech[Token].Next = {}
self:AddSentenceToSpeech( RemainingSentence, Speech[Token].Next, Sentence, Data )
else
-- There is no remaining sentence, so we add speech to the Sentence.
-- The recursion stops here.
Speech[Token].Sentence = Sentence
Speech[Token].Data = Data
end
end
end
end
--- Build the tree structure based on the language words, in order to find the correct sentences and to ignore incomprehensible words.
-- @param #RADIOSPEECH self
-- @return #RADIOSPEECH self The RADIOSPEECH object.
function RADIOSPEECH:BuildTree()
self.Speech = {}
for Language, Sentences in pairs( self.Vocabulary ) do
self:I( { Language = Language, Sentences = Sentences })
self.Speech[Language] = {}
for Sentence, Data in pairs( Sentences ) do
self:I( { Sentence = Sentence, Data = Data } )
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
end
end
self:I( { Speech = self.Speech } )
return self
end
--- Speak a sentence.
-- @param #RADIOSPEECH self
-- @param #string Sentence The sentence to be spoken.
function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
local OriginalSentence = Sentence
-- lua does not parse UTF-8, so the match statement will fail on cyrillic using %a.
-- therefore, the only way to parse the statement is to use blank, comma or dot as a delimiter.
-- and then check if the character can be converted to a number or not.
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
if Word then
if Word ~= "" and tonumber(Word) == nil then
-- Construct of words
Word = Word:lower()
if Speech[Word] then
-- The end of the sentence has been reached. Now Speech.Next should be nil, otherwise there is an error.
if Speech[Word].Next == nil then
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
else
if RemainderSentence and RemainderSentence ~= "" then
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
end
end
end
return RemainderSentence
end
return OriginalSentence
else
return ""
end
end
--- Speak a sentence.
-- @param #RADIOSPEECH self
-- @param #string Sentence The sentence to be spoken.
function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
local OriginalSentence = Sentence
-- lua does not parse UTF-8, so the match statement will fail on cyrillic using %a.
-- therefore, the only way to parse the statement is to use blank, comma or dot as a delimiter.
-- and then check if the character can be converted to a number or not.
local Digits, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
self:I( { Digits = Digits, Speech = Speech[Digits], RemainderSentence = RemainderSentence } )
if Digits then
if Digits ~= "" and tonumber( Digits ) ~= nil then
-- Construct numbers
local Number = tonumber( Digits )
local Multiple = nil
while Number >= 0 do
if Number > 1000 then
Multiple = math.floor( Number / 1000 ) * 1000
elseif Number > 100 then
Multiple = math.floor( Number / 100 ) * 100
elseif Number > 20 then
Multiple = math.floor( Number / 10 ) * 10
elseif Number >= 0 then
Multiple = Number
end
Sentence = tostring( Multiple )
if Speech[Sentence] then
self:I( { Speech = Speech[Sentence].Sentence, Data = Speech[Sentence].Data } )
self:NewTransmission( Speech[Sentence].Data[1] .. ".wav", Speech[Sentence].Data[2], Langauge .. "/" )
end
Number = Number - Multiple
Number = ( Number == 0 ) and -1 or Number
end
return RemainderSentence
end
return OriginalSentence
else
return ""
end
end
--- Speak a sentence.
-- @param #RADIOSPEECH self
-- @param #string Sentence The sentence to be spoken.
function RADIOSPEECH:Speak( Sentence, Language )
self:I( { Sentence, Language } )
local Language = Language or "EN"
self:I( { Language = Language } )
-- If there is no node for Speech, then we start at the first nodes of the language.
local Speech = self.Speech[Language]
self:I( { Speech = Speech, Language = Language } )
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
repeat
Sentence = self:SpeakWords( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
Sentence = self:SpeakDigits( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
-- Sentence = self:SpeakSymbols( Sentence, Speech )
--
-- self:I( { Sentence = Sentence } )
until not Sentence or Sentence == ""
self:NewTransmission( "_Out.wav", 0.28, Language .. "/" )
end

View File

@@ -70,11 +70,12 @@ function REPORT:Add( Text )
return self
end
--- Add a new line to a REPORT.
--- Add a new line to a REPORT, but indented. A separator character can be specified to separate the reported lines visually.
-- @param #REPORT self
-- @param #string Text
-- @param #string Text The report text.
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
-- @return #REPORT
function REPORT:AddIndent( Text, Separator ) --R2.1
function REPORT:AddIndent( Text, Separator )
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
return self
end

View File

@@ -4,7 +4,7 @@
--
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
--
-- This class is tricky and needs some thorought explanation.
-- This class is tricky and needs some thorough explanation.
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
-- The SCHEDULEDISPATCHER class ensures that:
--
@@ -13,9 +13,10 @@
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
--
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER
-- object is _persistent_ within memory.
--
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
--
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
@@ -33,13 +34,41 @@
-- @module Core.ScheduleDispatcher
-- @image Core_Schedule_Dispatcher.JPG
--- SCHEDULEDISPATCHER class.
-- @type SCHEDULEDISPATCHER
-- @field #string ClassName Name of the class.
-- @field #number CallID Call ID counter.
-- @field #table PersistentSchedulers Persistant schedulers.
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
-- @extends Core.Base#BASE
--- The SCHEDULEDISPATCHER structure
-- @type SCHEDULEDISPATCHER
SCHEDULEDISPATCHER = {
ClassName = "SCHEDULEDISPATCHER",
CallID = 0,
ClassName = "SCHEDULEDISPATCHER",
CallID = 0,
PersistentSchedulers = {},
ObjectSchedulers = {},
Schedule = nil,
}
--- Player data table holding all important parameters of each player.
-- @type SCHEDULEDISPATCHER.ScheduleData
-- @field #function Function The schedule function to be called.
-- @field #table Arguments Schedule function arguments.
-- @field #number Start Start time in seconds.
-- @field #number Repeat Repeat time intervall in seconds.
-- @field #number Randomize Randomization factor [0,1].
-- @field #number Stop Stop time in seconds.
-- @field #number StartTime Time in seconds when the scheduler is created.
-- @field #number ScheduleID Schedule ID.
-- @field #function CallHandler Function to be passed to the DCS timer.scheduleFunction().
-- @field #boolean ShowTrace If true, show tracing info.
--- Create a new schedule dispatcher object.
-- @param #SCHEDULEDISPATCHER self
-- @return #SCHEDULEDISPATCHER self
function SCHEDULEDISPATCHER:New()
local self = BASE:Inherit( self, BASE:New() )
self:F3()
@@ -51,15 +80,28 @@ end
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified.
-- Nothing of this code should be modified without testing it thoroughly.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop )
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } )
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #function ScheduleFunction Scheduler function.
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
-- @param #number Start Start time in seconds.
-- @param #number Repeat Repeat interval in seconds.
-- @param #number Randomize Radomization factor [0,1].
-- @param #number Stop Stop time in seconds.
-- @param #number TraceLevel Trace level [0,3].
-- @param Core.Fsm#FSM Fsm Finite state model.
-- @return #string Call ID or nil.
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm )
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm } )
-- Increase counter.
self.CallID = self.CallID + 1
-- Create ID.
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
-- Initialize PersistentSchedulers
self.PersistentSchedulers = self.PersistentSchedulers or {}
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
@@ -76,19 +118,60 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
self.Schedule[Scheduler][CallID] = {}
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + .1
self.Schedule[Scheduler][CallID].Start = Start + 0.1
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
-- This section handles the tracing of the scheduled calls.
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
-- The call stack has many levels, and the correct semantical function call depends on where in the code AddSchedule was "used".
-- - Using SCHEDULER:New()
-- - Using Schedule:AddSchedule()
-- - Using Fsm:__Func()
-- - Using Class:ScheduleOnce()
-- - Using Class:ScheduleRepeat()
-- - ...
-- So for each of these scheduled call variations, AddSchedule is the workhorse which will schedule the call.
-- But the correct level with the correct semantical function location will differ depending on the above scheduled call invocation forms.
-- That's where the field TraceLevel contains optionally the level in the call stack where the call information is obtained.
-- The TraceLevel field indicates the correct level where the semantical scheduled call was invoked within the source, ensuring that function name, line number and source name are correct.
-- There is one quick ...
-- The FSM class models scheduled calls using the __Func syntax. However, these functions are "tailed".
-- There aren't defined anywhere within the source code, but rather implemented as triggers within the FSM logic,
-- and using the onbefore, onafter, onenter, onleave prefixes. (See the FSM for details).
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
local Info = {}
if debug then
TraceLevel = TraceLevel or 2
Info = debug.getinfo( TraceLevel, "nlS" )
local name_fsm = debug.getinfo( TraceLevel - 1, "n" ).name -- #string
if name_fsm then
Info.name = name_fsm
end
end
self:T3( self.Schedule[Scheduler][CallID] )
self.Schedule[Scheduler][CallID].CallHandler = function( CallID )
--self:E( CallID )
--- Function passed to the DCS timer.scheduleFunction()
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
local CallID = Params.CallID
local Info = Params.Info or {}
local Source = Info.source or "?"
local Line = Info.currentline or "?"
local Name = Info.name or "?"
local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg )
@@ -98,7 +181,8 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
return errmsg
end
local Scheduler = self.ObjectSchedulers[CallID]
-- Get object or persistant scheduler object.
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
if not Scheduler then
Scheduler = self.PersistentSchedulers[CallID]
end
@@ -107,30 +191,42 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if Scheduler then
local MasterObject = tostring(Scheduler.MasterObject)
local Schedule = self.Schedule[Scheduler][CallID]
local MasterObject = tostring(Scheduler.MasterObject)
-- Schedule object.
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
--self:T3( { Schedule = Schedule } )
local SchedulerObject = Scheduler.SchedulerObject
--local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID()
local ScheduleFunction = Schedule.Function
local ScheduleArguments = Schedule.Arguments
local Start = Schedule.Start
local Repeat = Schedule.Repeat or 0
local Randomize = Schedule.Randomize or 0
local Stop = Schedule.Stop or 0
local ScheduleID = Schedule.ScheduleID
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
local ShowTrace = Scheduler.ShowTrace
local ScheduleFunction = Schedule.Function
local ScheduleArguments = Schedule.Arguments or {}
local Start = Schedule.Start
local Repeat = Schedule.Repeat or 0
local Randomize = Schedule.Randomize or 0
local Stop = Schedule.Stop or 0
local ScheduleID = Schedule.ScheduleID
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
local Status, Result
--self:E( { SchedulerObject = SchedulerObject } )
if SchedulerObject then
local function Timer()
if ShowTrace then
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
else
local function Timer()
if ShowTrace then
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return ScheduleFunction( unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
@@ -139,39 +235,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local CurrentTime = timer.getTime()
local StartTime = Schedule.StartTime
self:F3( { Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
-- Debug info.
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
local ScheduleTime =
CurrentTime +
Repeat +
math.random(
- ( Randomize * Repeat / 2 ),
( Randomize * Repeat / 2 )
) +
0.01
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
return ScheduleTime -- returns the next time the function needs to be called.
else
self:Stop( Scheduler, CallID )
end
else
self:Stop( Scheduler, CallID )
end
else
self:E( "Scheduled obsolete call for CallID: " .. CallID )
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return nil
end
self:Start( Scheduler, CallID )
self:Start( Scheduler, CallID, Info )
return CallID
end
--- Remove schedule.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID Call ID.
function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
self:F2( { Remove = CallID, Scheduler = Scheduler } )
@@ -181,46 +277,80 @@ function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
end
end
function SCHEDULEDISPATCHER:Start( Scheduler, CallID )
--- Start dispatcher.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID (Optional) Call ID.
-- @param #string Info (Optional) Debug info.
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
self:F2( { Start = CallID, Scheduler = Scheduler } )
if CallID then
local Schedule = self.Schedule[Scheduler]
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
-- Only start when there is no ScheduleID defined!
-- This prevents to "Start" the scheduler twice with the same CallID...
if not Schedule[CallID].ScheduleID then
Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started.
Schedule[CallID].ScheduleID = timer.scheduleFunction(
Schedule[CallID].CallHandler,
CallID,
timer.getTime() + Schedule[CallID].Start
)
if not Schedule.ScheduleID then
-- Current time in seconds.
local Tnow=timer.getTime()
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
end
else
-- Recursive.
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Start( Scheduler, CallID ) -- Recursive
self:Start( Scheduler, CallID, Info ) -- Recursive
end
end
end
--- Stop dispatcher.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID Call ID.
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
self:F2( { Stop = CallID, Scheduler = Scheduler } )
if CallID then
local Schedule = self.Schedule[Scheduler]
-- Only stop when there is a ScheduleID defined for the CallID.
-- So, when the scheduler was stopped before, do nothing.
if Schedule[CallID].ScheduleID then
timer.removeFunction( Schedule[CallID].ScheduleID )
Schedule[CallID].ScheduleID = nil
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
if Schedule.ScheduleID then
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
timer.removeFunction(Schedule.ScheduleID)
Schedule.ScheduleID = nil
else
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
end
else
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Stop( Scheduler, CallID ) -- Recursive
end
end
end
--- Clear all schedules by stopping all dispatchers.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
function SCHEDULEDISPATCHER:Clear( Scheduler )
self:F2( { Scheduler = Scheduler } )
@@ -229,5 +359,19 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
end
end
--- Shopw tracing info.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
self:F2( { Scheduler = Scheduler } )
Scheduler.ShowTrace = true
end
--- No tracing info.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
function SCHEDULEDISPATCHER:NoTrace( Scheduler )
self:F2( { Scheduler = Scheduler } )
Scheduler.ShowTrace = false
end

View File

@@ -43,7 +43,9 @@
--- The SCHEDULER class
-- @type SCHEDULER
-- @field #number ScheduleID the ID of the scheduler.
-- @field #table Schedules Table of schedules.
-- @field #table MasterObject Master object.
-- @field #boolean ShowTrace Trace info if true.
-- @extends Core.Base#BASE
@@ -69,53 +71,53 @@
--
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
--
-- SchedulerObject = SCHEDULER:New()
-- SchedulerID = SchedulerObject:Schedule( nil, ScheduleFunction, {} )
-- MasterObject = SCHEDULER:New()
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
--
-- The above example creates a new SchedulerObject, but does not schedule anything.
-- A separate schedule is created by using the SchedulerObject using the method :Schedule..., which returns a ScheduleID
-- The above example creates a new MasterObject, but does not schedule anything.
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
--
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
--
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject = SCHEDULER:New( ZoneObject )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- MasterObject = SCHEDULER:New( ZoneObject )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ...
-- ZoneObject = nil
-- garbagecollect()
--
-- The above example creates a new SchedulerObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
-- A separate schedule is created by using the SchedulerObject using the method :Schedule()..., which returns a ScheduleID
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the ScheduleObject will cancel any planned schedule.
-- As a result, the MasterObject will cancel any planned schedule.
--
-- ### Construct a SCHEDULER object with a persistent schedule.
--
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- SchedulerObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
--
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
--
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
--
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- MasterObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ...
-- ZoneObject = nil
-- garbagecollect()
--
-- The above example creates a new SchedulerObject, and schedules a method call (ScheduleFunction),
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
-- Both a ScheduleObject and a SchedulerID variable are returned.
-- Both a MasterObject and a SchedulerID variable are returned.
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the ScheduleObject will cancel the planned schedule.
-- As a result, the MasterObject will cancel the planned schedule.
--
-- ## SCHEDULER timer stopping and (re-)starting.
--
@@ -125,15 +127,15 @@
-- * @{#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 10 )
-- MasterObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 10 )
-- ...
-- SchedulerObject:Stop( SchedulerID )
-- MasterObject:Stop( SchedulerID )
-- ...
-- SchedulerObject:Start( SchedulerID )
-- MasterObject:Start( SchedulerID )
--
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
--
@@ -145,32 +147,32 @@
-- Consider the following code fragment of the SCHEDULER object creation.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject = SCHEDULER:New( ZoneObject )
-- MasterObject = SCHEDULER:New( ZoneObject )
--
-- Several parameters can be specified that influence the behaviour of a Schedule.
--
-- ### A single schedule, immediately executed
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
--
-- ### A single schedule, planned over time
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
--
-- ### A schedule with a repeating time interval, planned over time
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- and repeating 60 every seconds ...
--
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- and repeating 60 seconds, with a 50% time interval randomization ...
@@ -180,7 +182,7 @@
--
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- The schedule will repeat every 60 seconds.
@@ -191,13 +193,15 @@
--
-- @field #SCHEDULER
SCHEDULER = {
ClassName = "SCHEDULER",
Schedules = {},
ClassName = "SCHEDULER",
Schedules = {},
MasterObject = nil,
ShowTrace = nil,
}
--- SCHEDULER constructor.
-- @param #SCHEDULER self
-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #table MasterObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
@@ -205,50 +209,51 @@ SCHEDULER = {
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @return #SCHEDULER self.
-- @return #number The ScheduleID of the planned schedule.
function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
-- @return #table The ScheduleID of the planned schedule.
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
local ScheduleID = nil
self.MasterObject = SchedulerObject
self.MasterObject = MasterObject
self.ShowTrace = false
if SchedulerFunction then
ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
end
return self, ScheduleID
end
--function SCHEDULER:_Destructor()
-- --self:E("_Destructor")
--
-- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID )
--end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #SCHEDULER self
-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #table MasterObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @return #number The ScheduleID of the planned schedule.
function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
-- @param #number TraceLevel Trace level [0,3]. Default 3.
-- @param Core.Fsm#FSM Fsm Finite state model.
-- @return #table The ScheduleID of the planned schedule.
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
self:T3( { SchedulerArguments } )
-- Debug info.
local ObjectName = "-"
if SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then
ObjectName = SchedulerObject.ClassName .. SchedulerObject.ClassID
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
end
self:F3( { "Schedule :", ObjectName, tostring( SchedulerObject ), Start, Repeat, RandomizeFactor, Stop } )
self.SchedulerObject = SchedulerObject
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
-- Set master object.
self.MasterObject = MasterObject
-- Add schedule.
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
@@ -256,7 +261,9 @@ function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArgume
Start,
Repeat,
RandomizeFactor,
Stop
Stop,
TraceLevel or 3,
Fsm
)
self.Schedules[#self.Schedules+1] = ScheduleID
@@ -266,49 +273,47 @@ end
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Start( ScheduleID )
self:F3( { ScheduleID } )
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
end
--- Stops the schedules or a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Stop( ScheduleID )
self:F3( { ScheduleID } )
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
end
--- Removes a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Remove( ScheduleID )
self:F3( { ScheduleID } )
_SCHEDULEDISPATCHER:Remove( self, ScheduleID )
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
end
--- Clears all pending schedules.
-- @param #SCHEDULER self
function SCHEDULER:Clear()
self:F3( )
self:T(string.format("Clearing scheduler"))
_SCHEDULEDISPATCHER:Clear( self )
end
--- Show tracing for this scheduler.
-- @param #SCHEDULER self
function SCHEDULER:ShowTrace()
_SCHEDULEDISPATCHER:ShowTrace( self )
end
--- No tracing for this scheduler.
-- @param #SCHEDULER self
function SCHEDULER:NoTrace()
_SCHEDULEDISPATCHER:NoTrace( self )
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,104 +1,156 @@
--- **Core** - Spawn new statics in your running missions.
--- **Core** - Spawn statics.
--
-- ===
--
-- ## Features:
--
-- * Spawn new statics from a static already defined using the mission editor.
-- * Spawn new statics from a static already defined in the mission editor.
-- * Spawn new statics from a given template.
-- * Spawn new statics from a given type.
-- * Spawn with a custom heading and location.
-- * Spawn within a zone.
-- * Spawn statics linked to units, .e.g on aircraft carriers.
--
-- ===
--
-- # Demo Missions
--
-- ### [SPAWNSTATIC Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPS - Spawning Statics)
--
-- ### [SPAWNSTATIC Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
--
-- # YouTube Channel
--
-- ### [SPAWNSTATIC YouTube Channel]()
-- ## [SPAWNSTATIC YouTube Channel]() [No videos yet!]
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
-- ### Contributions: **funkyfranky**
--
-- ===
--
-- @module Core.SpawnStatic
-- @image Core_Spawnstatic.JPG
--- @type SPAWNSTATIC
-- @field #string SpawnTemplatePrefix Name of the template group.
-- @field #number CountryID Country ID.
-- @field #number CoalitionID Coalition ID.
-- @field #number CategoryID Category ID.
-- @field #number SpawnIndex Running number increased with each new Spawn.
-- @field Wrapper.Unit#UNIT InitLinkUnit The unit the static is linked to.
-- @field #number InitOffsetX Link offset X coordinate.
-- @field #number InitOffsetY Link offset Y coordinate.
-- @field #number InitOffsetAngle Link offset angle in degrees.
-- @field #number InitStaticHeading Heading of the static.
-- @field #string InitStaticLivery Livery for aircraft.
-- @field #string InitStaticShape Shape of teh static.
-- @field #string InitStaticType Type of the static.
-- @field #string InitStaticCategory Categrory of the static.
-- @field #string InitStaticName Name of the static.
-- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static.
-- @field #boolean InitDead Set static to be dead if true.
-- @field #boolean InitCargo If true, static can act as cargo.
-- @field #number InitCargoMass Mass of cargo in kg.
-- @extends Core.Base#BASE
--- Allows to spawn dynamically new @{Static}s.
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME),
-- SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy"
-- these properties to create a new static object and place it at the desired coordinate.
--- Allows to spawn dynamically new @{Static}s into your mission.
--
-- New spawned @{Static}s get **the same name** as the name of the template Static,
-- or gets the given name when a new name is provided at the Spawn method.
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
-- and "copy" these properties to create a new static object and place it at the desired coordinate.
--
-- New spawned @{Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
-- By default, spawned @{Static}s will follow a naming convention at run-time:
--
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**,
-- and _nnn_ is a **counter from 0 to 99999**.
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**.
--
--
-- ## SPAWNSTATIC construction methods
-- # SPAWNSTATIC Constructors
--
-- Create a new SPAWNSTATIC object with the @{#SPAWNSTATIC.NewFromStatic}():
-- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this.
--
-- * @{#SPAWNSTATIC.NewFromStatic}(): Creates a new SPAWNSTATIC object given a name that is used as the base of the naming of each spawned Static.
-- ## Use another Static
--
-- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized
-- from the static.
--
-- ## From a Template
--
-- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template.
--
-- ## From a Type
--
-- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values
-- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map.
--
-- # Setting Parameters
--
-- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command!
--
-- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground.
-- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static.
-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this.
-- * @{#SPAWNSTATIC.InitType}(StaticType) sets the type of the static.
-- * @{#SPAWNSTATIC.InitShape}(StaticType) sets the shape of the static. Not all statics have this parameter.
-- * @{#SPAWNSTATIC.InitNamePrefix}(NamePrefix) sets the name prefix of the spawned statics.
-- * @{#SPAWNSTATIC.InitCountry}(CountryID) sets the country and therefore the coalition of the spawned statics.
-- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier.
--
-- ## **Spawn** methods
-- # Spawning the Statics
--
-- Groups can be spawned at different times and methods:
-- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters
-- such as position and heading.
--
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(): Spawn a new group from a POINT_VEC2 coordinate.
-- (The group will be spawned at land height ).
-- * @{#SPAWNSTATIC.SpawnFromZone}(): Spawn a new group in a @{Zone}.
-- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**!
-- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**!
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**!
-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Zone}. Optionally, heading and name can be given. The name **must be unique**!
--
-- @field #SPAWNSTATIC SPAWNSTATIC
--
SPAWNSTATIC = {
ClassName = "SPAWNSTATIC",
ClassName = "SPAWNSTATIC",
SpawnIndex = 0,
}
--- Static template table data.
-- @type SPAWNSTATIC.TemplateData
-- @field #string name Name of the static.
-- @field #string type Type of the static.
-- @field #string category Category of the static.
-- @field #number x X-coordinate of the static.
-- @field #number y Y-coordinate of teh static.
-- @field #number heading Heading in rad.
-- @field #boolean dead Static is dead if true.
-- @field #string livery_id Livery name.
-- @field #number unitId Unit ID.
-- @field #number groupId Group ID.
-- @field #table offsets Offset parameters when linked to a unit.
-- @field #number mass Cargo mass in kg.
-- @field #boolean canCargo Static can be a cargo.
--- @type SPAWNSTATIC.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone
--- Creates the main object to spawn a @{Static} defined in the ME.
--- Creates the main object to spawn a @{Static} defined in the mission editor (ME).
-- @param #SPAWNSTATIC self
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
-- @param DCS#country.id SpawnCountryID The ID of the country.
-- @param DCS#coalition.side SpawnCoalitionID The ID of the coalition.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID, SpawnCoalitionID )
-- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix.
-- @param DCS#country.id SpawnCountryID (Optional) The ID of the country.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTemplatePrefix } )
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( SpawnTemplatePrefix )
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = SpawnCoalitionID or CoalitionID
self.SpawnIndex = 0
self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end
self:SetEventPriority( 5 )
@@ -106,155 +158,284 @@ function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID, SpawnCo
return self
end
--- Creates the main object to spawn a @{Static} based on a type name.
--- Creates the main object to spawn a @{Static} given a template table.
-- @param #SPAWNSTATIC self
-- @param #string SpawnTypeName is the name of the type.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, SpawnCountryID, SpawnCoalitionID )
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTypeName } )
self.SpawnTypeName = SpawnTypeName
self.CountryID = SpawnCountryID
self.CoalitionID = SpawnCoalitionID
self.SpawnIndex = 0
-- @param #table SpawnTemplate Template used for spawning.
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID)
self:SetEventPriority( 5 )
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate)
self.SpawnTemplatePrefix = SpawnTemplate.name
self.CountryID = CountryID or country.id.USA
return self
end
--- Creates the main object to spawn a @{Static} from a given type.
-- NOTE that you have to init many other parameters as spawn coordinate etc.
-- @param #SPAWNSTATIC self
-- @param #string StaticType Type of the static.
-- @param #string StaticCategory Category of the static, e.g. "Planes".
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self.InitStaticType=StaticType
self.InitStaticCategory=StaticCategory
self.CountryID=CountryID or country.id.USA
self.SpawnTemplatePrefix=self.InitStaticType
self.InitStaticCoordinate=COORDINATE:New(0, 0, 0)
self.InitStaticHeading=0
return self
end
--- Creates a new @{Static} at the original position.
--- Initialize heading of the spawned static.
-- @param #SPAWNSTATIC self
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3
self:F( { Heading, NewName } )
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local StaticUnitTemplate = StaticTemplate.units[1]
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
self.SpawnIndex = self.SpawnIndex + 1
return _DATABASE:FindStatic(Static:getName())
end
return nil
-- @param Core.Point#COORDINATE Coordinate Position where the static is spawned.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCoordinate(Coordinate)
self.InitStaticCoordinate=Coordinate
return self
end
--- Initialize heading of the spawned static.
-- @param #SPAWNSTATIC self
-- @param #number Heading The heading in degrees.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitHeading(Heading)
self.InitStaticHeading=Heading
return self
end
--- Initialize livery of the spawned static.
-- @param #SPAWNSTATIC self
-- @param #string LiveryName Name of the livery to use.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitLivery(LiveryName)
self.InitStaticLivery=LiveryName
return self
end
--- Initialize type of the spawned static.
-- @param #SPAWNSTATIC self
-- @param #string StaticType Type of the static, e.g. "FA-18C_hornet".
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitType(StaticType)
self.InitStaticType=StaticType
return self
end
--- Initialize shape of the spawned static. Required by some but not all statics.
-- @param #SPAWNSTATIC self
-- @param #string StaticShape Shape of the static, e.g. "carrier_tech_USA".
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitShape(StaticShape)
self.InitStaticShape=StaticShape
return self
end
--- Initialize cargo mass.
-- @param #SPAWNSTATIC self
-- @param #number Mass Mass of the cargo in kg.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargoMass(Mass)
self.InitCargoMass=Mass
return self
end
--- Initialize as cargo.
-- @param #SPAWNSTATIC self
-- @param #boolean IsCargo If true, this static can act as cargo.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargo(IsCargo)
self.InitCargo=IsCargo
return self
end
--- Initialize country of the spawned static. This determines the category.
-- @param #SPAWNSTATIC self
-- @param #string CountryID The country ID, e.g. country.id.USA.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCountry(CountryID)
self.CountryID=CountryID
return self
end
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
-- @param #SPAWNSTATIC self
-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitNamePrefix(NamePrefix)
self.SpawnTemplatePrefix=NamePrefix
return self
end
--- Init link to a unit.
-- @param #SPAWNSTATIC self
-- @param Wrapper.Unit#UNIT Unit The unit to which the static is linked.
-- @param #number OffsetX Offset in X.
-- @param #number OffsetY Offset in Y.
-- @param #number OffsetAngle Offset angle in degrees.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle)
self.InitLinkUnit=Unit
self.InitOffsetX=OffsetX or 0
self.InitOffsetY=OffsetY or 0
self.InitOffsetAngle=OffsetAngle or 0
return self
end
--- Spawn a new STATIC object.
-- @param #SPAWNSTATIC self
-- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template.
-- @param #string NewName (Optional) The name of the new static.
-- @return Wrapper.Static#STATIC The static spawned.
function SPAWNSTATIC:Spawn(Heading, NewName)
if Heading then
self.InitStaticHeading=Heading
end
if NewName then
self.InitStaticName=NewName
end
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
end
--- Creates a new @{Static} from a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
self:F( { PointVec2, Heading, NewName } )
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local StaticUnitTemplate = StaticTemplate.units[1]
StaticUnitTemplate.x = PointVec2.x
StaticUnitTemplate.y = PointVec2.z
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticUnitTemplate.name = StaticTemplate.name
StaticUnitTemplate.heading = ( Heading / 180 ) * math.pi
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID)
self:F({StaticTemplate = StaticTemplate})
-- @param #string NewName (Optional) The name of the new static.
-- @return Wrapper.Static#STATIC The static spawned.
function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName)
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
self.SpawnIndex = self.SpawnIndex + 1
return _DATABASE:FindStatic(Static:getName())
end
local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()}
local Coordinate=COORDINATE:NewFromVec2(vec2)
return nil
return self:SpawnFromCoordinate(Coordinate, Heading, NewName)
end
--- Respawns the original @{Static}.
--- Creates a new @{Static} from a COORDINATE.
-- @param #SPAWNSTATIC self
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawn()
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
-- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees.
-- @param #string NewName (Optional) The name of the new static.
-- @return Wrapper.Static#STATIC The spawned STATIC object.
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
local StaticUnitTemplate = StaticTemplate.units[1]
StaticTemplate.route = nil
StaticTemplate.groupId = nil
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
return _DATABASE:FindStatic(Static:getName())
-- Set up coordinate.
self.InitStaticCoordinate=Coordinate
if Heading then
self.InitStaticHeading=Heading
end
return nil
end
--- Creates the original @{Static} at a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#COORDINATE Coordinate The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawnAt( Coordinate, Heading )
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local StaticUnitTemplate = StaticTemplate.units[1]
StaticUnitTemplate.x = Coordinate.x
StaticUnitTemplate.y = Coordinate.z
StaticUnitTemplate.heading = Heading and ( ( Heading / 180 ) * math.pi ) or StaticTemplate.heading
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
return _DATABASE:FindStatic(Static:getName())
if NewName then
self.InitStaticName=NewName
end
return nil
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
end
--- Creates a new @{Static} from a @{Zone}.
-- @param #SPAWNSTATIC self
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
self:F( { Zone, Heading, NewName } )
-- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template.
-- @param #string NewName (Optional) The name of the new static.
-- @return Wrapper.Static#STATIC The static spawned.
function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
-- Spawn the new static at the center of the zone.
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
return Static
end
--- Spawns a new static using a given template. Additionally, the country ID needs to be specified, which also determines the coalition of the spawned static.
-- @param #SPAWNSTATIC self
-- @param #SPAWNSTATIC.TemplateData Template Spawn unit template.
-- @param #number CountryID The country ID.
-- @return Wrapper.Static#STATIC The static spawned.
function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template=Template or {}
local CountryID=CountryID or self.CountryID
if self.InitStaticType then
Template.type=self.InitStaticType
end
if self.InitStaticCategory then
Template.category=self.InitStaticCategory
end
if self.InitStaticCoordinate then
Template.x = self.InitStaticCoordinate.x
Template.y = self.InitStaticCoordinate.z
Template.alt = self.InitStaticCoordinate.y
end
if self.InitStaticHeading then
Template.heading = math.rad(self.InitStaticHeading)
end
if self.InitStaticShape then
Template.shape_name=self.InitStaticShape
end
if self.InitStaticLivery then
Template.livery_id=self.InitStaticLivery
end
if self.InitDead~=nil then
Template.dead=self.InitDead
end
if self.InitCargo~=nil then
Template.isCargo=self.InitCargo
end
if self.InitLinkUnit then
Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true
Template.offsets={}
Template.offsets.y=self.InitOffsetY
Template.offsets.x=self.InitOffsetX
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
end
-- Increase spawn index counter.
self.SpawnIndex = self.SpawnIndex + 1
-- Name of the spawned static.
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
-- Register the new static.
--_DATABASE:_RegisterStaticTemplate(Template, self.CoalitionID, self.CategoryID, CountryID)
_DATABASE:AddStatic(Template.name)
-- Debug output.
self:T(Template)
-- Add static to the game.
local Static=coalition.addStaticObject(CountryID, Template)
return _DATABASE:FindStatic(Static:getName())
end

View File

@@ -126,6 +126,39 @@ do
-- @param Wrapper.Positionable#POSITIONABLE Target
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
self:AddTransition( "Off", "LaseOnCoordinate", "On" )
--- LaseOnCoordinate Handler OnBefore for SPOT.
-- @function [parent=#SPOT] OnBeforeLaseOnCoordinate
-- @param #SPOT self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- LaseOnCoordinate Handler OnAfter for SPOT.
-- @function [parent=#SPOT] OnAfterLaseOnCoordinate
-- @param #SPOT self
-- @param #string From
-- @param #string Event
-- @param #string To
--- LaseOnCoordinate Trigger for SPOT.
-- @function [parent=#SPOT] LaseOnCoordinate
-- @param #SPOT self
-- @param Core.Point#COORDINATE Coordinate The coordinate to lase.
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
--- LaseOn Asynchronous Trigger for SPOT
-- @function [parent=#SPOT] __LaseOn
-- @param #SPOT self
-- @param #number Delay
-- @param Wrapper.Positionable#POSITIONABLE Target
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
self:AddTransition( "On", "Lasing", "On" )
@@ -194,7 +227,8 @@ do
return self
end
--- @param #SPOT self
--- On after LaseOn event. Activates the laser spot.
-- @param #SPOT self
-- @param From
-- @param Event
-- @param To
@@ -226,6 +260,40 @@ do
self:__Lasing( -1 )
end
--- On after LaseOnCoordinate event. Activates the laser spot.
-- @param #SPOT self
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate The coordinate at which the laser is pointing.
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration)
self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
local function StopLase( self )
self:LaseOff()
end
self.Target = nil
self.TargetCoord=Coordinate
self.LaserCode = LaserCode
self.Lasing = true
local RecceDcsUnit = self.Recce:GetDCSObject()
self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3() )
self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3(), LaserCode )
if Duration then
self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration )
end
self:__Lasing(-1)
end
--- @param #SPOT self
-- @param Core.Event#EVENTDATA EventData
@@ -246,10 +314,20 @@ do
-- @param To
function SPOT:onafterLasing( From, Event, To )
if self.Target:IsAlive() then
if self.Target and self.Target:IsAlive() then
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
self:__Lasing( -0.2 )
elseif self.TargetCoord then
-- Wiggle the IR spot a bit.
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3)
self:__Lasing(-0.25)
else
self:F( { "Target is not alive", self.Target:IsAlive() } )
end

View File

@@ -0,0 +1,274 @@
--- **Core** - Timer scheduler.
--
-- **Main Features:**
--
-- * Delay function calls
-- * Easy set up and little overhead
-- * Set start, stop and time interval
-- * Define max function calls
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Core.Timer
-- @image CORE_Timer.png
--- TIMER class.
-- @type TIMER
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #number tid Timer ID returned by the DCS API function.
-- @field #function func Timer function.
-- @field #table para Parameters passed to the timer function.
-- @field #number Tstart Relative start time in seconds.
-- @field #number Tstop Relative stop time in seconds.
-- @field #number dT Time interval between function calls in seconds.
-- @field #number ncalls Counter of function calls.
-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped.
-- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* William Shakespeare
--
-- ===
--
-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg)
--
-- # The TIMER Concept
--
-- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
--
-- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction).
--
-- # Construction
--
-- A new TIMER is created by the @{#TIMER.New}(*func*, *...*) function
--
-- local mytimer=TIMER:New(myfunction, a, b)
--
-- The first parameter *func* is the function that is called followed by the necessary comma separeted parameters that are passed to that function.
--
-- ## Starting the Timer
--
-- The timer is started by the @{#TIMER.Start}(*Tstart*, *dT*, *Duration*) function
--
-- mytimer:Start(5, 1, 20)
--
-- where
--
-- * *Tstart* is the relative start time in seconds. In the example, the first function call happens after 5 sec.
-- * *dT* is the time interval between function calls in seconds. Above, the function is called once per second.
-- * *Duration* is the duration in seconds after which the timer is stopped. This is relative to the start time. Here, the timer will run for 20 seconds.
--
-- Note that
--
-- * if *Tstart* is not specified (*nil*), the first function call happens immediately.
-- * if *dT* is not specified (*nil*), the function is called only once.
-- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below).
--
-- For example,
--
-- mytimer:Start(3) -- Will call the function once after 3 seconds.
-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty.
-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec.
-- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway.
--
-- ## Stopping the Timer
--
-- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function
--
-- mytimer:Stop()
--
-- If the optional paramter *Delay* is specified, the timer is stopped after *delay* seconds.
--
-- ## Limit Function Calls
--
-- The timer can be stopped after a certain amount of function calles with the @{#TIMER.SetMaxFunctionCalls}(*Nmax*) function
--
-- mytimer:SetMaxFunctionCalls(20)
--
-- where *Nmax* is the number of calls after which the timer is stopped, here 20.
--
-- For example,
--
-- mytimer:SetMaxFunctionCalls(66):Start(1.0, 0.1)
--
-- will start the timer after one second and call the function every 0.1 seconds. Once the function has been called 66 times, the timer is stopped.
--
--
-- @field #TIMER
TIMER = {
ClassName = "TIMER",
lid = nil,
}
--- Timer ID.
_TIMERID=0
--- Timer data base.
--_TIMERDB={}
--- TIMER class version.
-- @field #string version
TIMER.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-- TODO: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new TIMER object.
-- @param #TIMER self
-- @param #function Function The function to call.
-- @param ... Parameters passed to the function if any.
-- @return #TIMER self
function TIMER:New(Function, ...)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#TIMER
-- Function to call.
self.func=Function
-- Function arguments.
self.para=arg or {}
-- Number of function calls.
self.ncalls=0
-- Increase counter
_TIMERID=_TIMERID+1
-- Set UID.
self.uid=_TIMERID
-- Log id.
self.lid=string.format("TIMER UID=%d | ", self.uid)
-- Add to DB.
--_TIMERDB[self.uid]=self
return self
end
--- Create a new TIMER object.
-- @param #TIMER self
-- @param #number Tstart Relative start time in seconds.
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
-- @return #TIMER self
function TIMER:Start(Tstart, dT, Duration)
-- Current time.
local Tnow=timer.getTime()
-- Start time in sec.
self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil
-- Set time interval.
self.dT=dT
-- Stop time.
if Duration then
self.Tstop=self.Tstart+Duration
end
-- Call DCS timer function.
self.tid=timer.scheduleFunction(self._Function, self, self.Tstart)
-- Set log id.
self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid)
-- Debug info.
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop)))
return self
end
--- Stop the timer by removing the timer function.
-- @param #TIMER self
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.
-- @return #TIMER self
function TIMER:Stop(Delay)
if Delay and Delay>0 then
self.Tstop=timer.getTime()+Delay
else
if self.tid then
-- Remove timer function.
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
timer.removeFunction(self.tid)
-- Remove DB entry.
--_TIMERDB[self.uid]=nil
end
end
return self
end
--- Set max number of function calls. When the function has been called this many times, the TIMER is stopped.
-- @param #TIMER self
-- @param #number Nmax Set number of max function calls.
-- @return #TIMER self
function TIMER:SetMaxFunctionCalls(Nmax)
self.ncallsMax=Nmax
return self
end
--- Call timer function.
-- @param #TIMER self
-- @param #number time DCS model time in seconds.
-- @return #number Time when the function is called again or `nil` if the timer is stopped.
function TIMER:_Function(time)
-- Call function.
self.func(unpack(self.para))
-- Increase number of calls.
self.ncalls=self.ncalls+1
-- Next time.
local Tnext=self.dT and time+self.dT or nil
-- Check if we stop the timer.
local stopme=false
if Tnext==nil then
-- No next time.
self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls", self.ncalls))
stopme=true
elseif self.Tstop and Tnext>self.Tstop then
-- Stop time passed.
self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls", Tnext, self.Tstop, self.ncalls))
stopme=true
elseif self.ncallsMax and self.ncalls>=self.ncallsMax then
-- Number of max function calls reached.
self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls", self.ncallsMax, self.ncalls))
stopme=true
end
if stopme then
-- Remove timer function.
self:Stop()
return nil
else
-- Call again in Tnext seconds.
return Tnext
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -19,6 +19,8 @@
do -- UserFlag
--- @type USERFLAG
-- @field #string ClassName Name of the class
-- @field #string UserFlagName Name of the flag.
-- @extends Core.Base#BASE
@@ -30,7 +32,8 @@ do -- UserFlag
--
-- @field #USERFLAG
USERFLAG = {
ClassName = "USERFLAG",
ClassName = "USERFLAG",
UserFlagName = nil,
}
--- USERFLAG Constructor.
@@ -46,18 +49,30 @@ do -- UserFlag
return self
end
--- Get the userflag name.
-- @param #USERFLAG self
-- @return #string Name of the user flag.
function USERFLAG:GetName()
return self.UserFlagName
end
--- Set the userflag to a given Number.
-- @param #USERFLAG self
-- @param #number Number The number value to be checked if it is the same as the userflag.
-- @param #number Delay Delay in seconds, before the flag is set.
-- @return #USERFLAG The userflag instance.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
--
function USERFLAG:Set( Number ) --R2.3
function USERFLAG:Set( Number, Delay ) --R2.3
trigger.action.setUserFlag( self.UserFlagName, Number )
if Delay and Delay>0 then
self:ScheduleOnce(Delay, USERFLAG.Set, self, Number)
else
--env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime()))
trigger.action.setUserFlag( self.UserFlagName, Number )
end
return self
end
@@ -70,7 +85,7 @@ do -- UserFlag
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
--
function USERFLAG:Get( Number ) --R2.3
function USERFLAG:Get() --R2.3
return trigger.misc.getUserFlag( self.UserFlagName )
end

View File

@@ -118,15 +118,21 @@ do -- UserSound
--- Play the usersound to the given @{Wrapper.Group}.
-- @param #USERSOUND self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to.
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
--
function USERSOUND:ToGroup( Group ) --R2.3
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
function USERSOUND:ToGroup( Group, Delay ) --R2.3
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToGroup,{self, Group}, Delay)
else
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
end
return self
end

View File

@@ -56,7 +56,7 @@
--- @type ZONE_BASE
-- @field #string ZoneName Name of the zone.
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @extends Core.Base#BASE
-- @extends Core.Fsm#FSM
--- This class is an abstract BASE class for derived classes, and is not meant to be instantiated.
@@ -120,7 +120,7 @@ ZONE_BASE = {
-- @param #string ZoneName Name of the zone.
-- @return #ZONE_BASE self
function ZONE_BASE:New( ZoneName )
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, FSM:New() )
self:F( ZoneName )
self.ZoneName = ZoneName
@@ -442,6 +442,33 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
return self
end
--- Mark the zone with markers on the F10 map.
-- @param #ZONE_RADIUS self
-- @param #number Points (Optional) The amount of points in the circle. Default 360.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:MarkZone(Points)
local Point = {}
local Vec2 = self:GetVec2()
Points = Points and Points or 360
local Angle
local RadialBase = math.pi*2
for Angle = 0, 360, (360 / Points ) do
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName())
end
end
--- Bounds the zone with tires.
-- @param #ZONE_RADIUS self
-- @param #number Points (optional) The amount of points in the circle. Default 360.
@@ -535,7 +562,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight )
local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0
Points = Points and Points or 360
local Angle
@@ -618,6 +645,9 @@ function ZONE_RADIUS:GetVec3( Height )
end
--- Scan the zone for the presence of units of the given ObjectCategories.
-- Note that after a zone has been scanned, the zone can be evaluated by:
--
@@ -628,12 +658,12 @@ end
-- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty.
-- @{#ZONE_RADIUS.
-- @param #ZONE_RADIUS self
-- @param ObjectCategories
-- @param Coalition
-- @param ObjectCategories An array of categories of the objects to find in the zone.
-- @param UnitCategories An array of unit categories of the objects to find in the zone.
-- @usage
-- self.Zone:Scan()
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories )
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {}
self.ScanData.Coalitions = {}
@@ -655,15 +685,48 @@ function ZONE_RADIUS:Scan( ObjectCategories )
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 then
if ZoneObject then
local ObjectCategory = ZoneObject:getCategory()
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or
(ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
--local name=ZoneObject:getName()
--env.info(string.format("Zone object %s", tostring(name)))
--self:E(ZoneObject)
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self.ScanData.Units[ZoneObject] = ZoneObject
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
local Include = false
if not UnitCategories then
-- Anythink found is included.
Include = true
else
-- Check if found object is in specified categories.
local CategoryDCSUnit = ZoneObject:getDesc().category
for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do
if UnitCategory == CategoryDCSUnit then
Include = true
break
end
end
end
if Include then
local CoalitionDCSUnit = ZoneObject:getCoalition()
-- This coalition is inside the zone.
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self.ScanData.Units[ZoneObject] = ZoneObject
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
end
end
if ObjectCategory == Object.Category.SCENERY then
local SceneryType = ZoneObject:getTypeName()
local SceneryName = ZoneObject:getName()
@@ -671,21 +734,57 @@ function ZONE_RADIUS:Scan( ObjectCategories )
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
end
end
return true
end
-- Search objects.
world.searchObjects( ObjectCategories, SphereSearch, EvaluateZone )
end
--- Count the number of different coalitions inside the zone.
-- @param #ZONE_RADIUS self
-- @return #table Table of DCS units and DCS statics inside the zone.
function ZONE_RADIUS:GetScannedUnits()
return self.ScanData.Units
end
--- Get a set of scanned units.
-- @param #ZONE_RADIUS self
-- @return Core.Set#SET_UNIT Set of units and statics inside the zone.
function ZONE_RADIUS:GetScannedSetUnit()
local SetUnit = SET_UNIT:New()
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 )
else
local FoundStatic = STATIC:FindByName( UnitObject:getName() )
if FoundStatic then
SetUnit:AddUnit( FoundStatic )
end
end
end
end
end
return SetUnit
end
--- Count the number of different coalitions inside the zone.
-- @param #ZONE_RADIUS self
-- @return #number Counted coalitions.
function ZONE_RADIUS:CountScannedCoalitions()
local Count = 0
@@ -693,14 +792,25 @@ function ZONE_RADIUS:CountScannedCoalitions()
for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do
Count = Count + 1
end
return Count
end
--- Check if a certain coalition is inside a scanned zone.
-- @param #ZONE_RADIUS self
-- @param #number Coalition The coalition id, e.g. coalition.side.BLUE.
-- @return #boolean If true, the coalition is inside the zone.
function ZONE_RADIUS:CheckScannedCoalition( Coalition )
if Coalition then
return self.ScanData.Coalitions[Coalition]
end
return nil
end
--- Get Coalitions of the units in the Zone, or Check if there are units of the given Coalition in the Zone.
-- Returns nil if there are none ot two Coalitions in the zone!
-- Returns nil if there are none to two Coalitions in the zone!
-- Returns one Coalition if there are only Units of one Coalition in the Zone.
-- Returns the Coalition for the given Coalition if there are units of the Coalition in the Zone
-- Returns the Coalition for the given Coalition if there are units of the Coalition in the Zone.
-- @param #ZONE_RADIUS self
-- @return #table
function ZONE_RADIUS:GetScannedCoalition( Coalition )
@@ -725,20 +835,27 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition )
end
--- Get scanned scenery type
-- @param #ZONE_RADIUS self
-- @return #table Table of DCS scenery type objects.
function ZONE_RADIUS:GetScannedSceneryType( SceneryType )
return self.ScanData.Scenery[SceneryType]
end
--- Get scanned scenery table
-- @param #ZONE_RADIUS self
-- @return #table Table of DCS scenery objects.
function ZONE_RADIUS:GetScannedScenery()
return self.ScanData.Scenery
end
--- Is All in Zone of Coalition?
-- Check if only the specifed coalition is inside the zone and noone else.
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
-- @param #number Coalition Coalition ID of the coalition which is checked to be the only one in the zone.
-- @return #boolean True, if **only** that coalition is inside the zone and no one else.
-- @usage
-- self.Zone:Scan()
-- local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
@@ -750,11 +867,12 @@ end
--- Is All in Zone of Other Coalition?
-- Check if only one coalition is inside the zone and the specified coalition is not the one.
-- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated!
-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set.
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
-- @param #number Coalition Coalition ID of the coalition which is not supposed to be in the zone.
-- @return #boolean True, if and only if only one coalition is inside the zone and the specified coalition is not it.
-- @usage
-- self.Zone:Scan()
-- local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
@@ -766,11 +884,12 @@ end
--- Is Some in Zone of Coalition?
-- Check if more than one coaltion is inside the zone and the specifed coalition is one of them.
-- You first need to use the @{#ZONE_RADIUS.Scan} method to scan the zone before it can be evaluated!
-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set.
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
-- @param #number Coalition ID of the coaliton which is checked to be inside the zone.
-- @return #boolean True if more than one coalition is inside the zone and the specified coalition is one of them.
-- @usage
-- self.Zone:Scan()
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
@@ -834,7 +953,6 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories )
local function EvaluateZone( ZoneDCSUnit )
env.info( ZoneDCSUnit:getName() )
local ZoneUnit = UNIT:Find( ZoneDCSUnit )
@@ -1323,7 +1441,7 @@ end
function ZONE_POLYGON_BASE:Flush()
self:F2()
self:E( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
return self
end
@@ -1380,16 +1498,15 @@ end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
self:F2( SmokeColor )
local i
local j
local Segments = 10
Segments=Segments or 10
i = 1
j = #self._.Polygon
local i=1
local j=#self._.Polygon
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
@@ -1410,6 +1527,42 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
end
--- Flare the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight )
self:F2(FlareColor)
Segments=Segments or 10
AddHeight = AddHeight or 0
local i=1
local j=#self._.Polygon
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line.
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth)
end
j = i
i = i + 1
end
return self
end
--- Returns if a location is within the zone.

View File

@@ -0,0 +1,203 @@
--- The ZONE_DETECTION class, defined by a zone name, a detection object and a radius.
-- @type ZONE_DETECTION
-- @field DCS#Vec2 Vec2 The current location of the zone.
-- @field DCS#Distance Radius The radius of the zone.
-- @extends #ZONE_BASE
--- The ZONE_DETECTION class defined by a zone name, a location and a radius.
-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties.
--
-- ## ZONE_DETECTION constructor
--
-- * @{#ZONE_DETECTION.New}(): Constructor.
--
-- @field #ZONE_DETECTION
ZONE_DETECTION = {
ClassName="ZONE_DETECTION",
}
--- Constructor of @{#ZONE_DETECTION}, taking the zone name, the zone location and a radius.
-- @param #ZONE_DETECTION self
-- @param #string ZoneName Name of the zone.
-- @param Functional.Detection#DETECTION_BASE Detection The detection object defining the locations of the central detections.
-- @param DCS#Distance Radius The radius around the detections defining the combined zone.
-- @return #ZONE_DETECTION self
function ZONE_DETECTION:New( ZoneName, Detection, Radius )
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_DETECTION
self:F( { ZoneName, Detection, Radius } )
self.Detection = Detection
self.Radius = Radius
return self
end
--- Bounds the zone with tires.
-- @param #ZONE_DETECTION self
-- @param #number Points (optional) The amount of points in the circle. Default 360.
-- @param DCS#country.id CountryID The country id of the tire objects, e.g. country.id.USA for blue or country.id.RUSSIA for red.
-- @param #boolean UnBound (Optional) If true the tyres will be destroyed.
-- @return #ZONE_DETECTION self
function ZONE_DETECTION:BoundZone( Points, CountryID, UnBound )
local Point = {}
local Vec2 = self:GetVec2()
Points = Points and Points or 360
local Angle
local RadialBase = math.pi*2
--
for Angle = 0, 360, (360 / Points ) do
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
local Tire = {
["country"] = CountryName,
["category"] = "Fortifications",
["canCargo"] = false,
["shape_name"] = "H-tyre_B_WF",
["type"] = "Black_Tyre_WF",
--["unitId"] = Angle + 10000,
["y"] = Point.y,
["x"] = Point.x,
["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ),
["heading"] = 0,
} -- end of ["group"]
local Group = coalition.addStaticObject( CountryID, Tire )
if UnBound and UnBound == true then
Group:destroy()
end
end
return self
end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_DETECTION self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
-- @param #number Points (optional) The amount of points in the circle.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @param #number AddOffSet (optional) The angle to be added for the smoking start position.
-- @return #ZONE_DETECTION self
function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
self:F2( SmokeColor )
local Point = {}
local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0
AngleOffset = AngleOffset or 0
Points = Points and Points or 360
local Angle
local RadialBase = math.pi*2
for Angle = 0, 360, 360 / Points do
local Radial = ( Angle + AngleOffset ) * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor )
end
return self
end
--- Flares the zone boundaries in a color.
-- @param #ZONE_DETECTION self
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
-- @param #number Points (optional) The amount of points in the circle.
-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @return #ZONE_DETECTION self
function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight )
self:F2( { FlareColor, Azimuth } )
local Point = {}
local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0
Points = Points and Points or 360
local Angle
local RadialBase = math.pi*2
for Angle = 0, 360, 360 / Points do
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth )
end
return self
end
--- Returns the radius around the detected locations defining the combine zone.
-- @param #ZONE_DETECTION self
-- @return DCS#Distance The radius.
function ZONE_DETECTION:GetRadius()
self:F2( self.ZoneName )
self:T2( { self.Radius } )
return self.Radius
end
--- Sets the radius around the detected locations defining the combine zone.
-- @param #ZONE_DETECTION self
-- @param DCS#Distance Radius The radius.
-- @return #ZONE_DETECTION self
function ZONE_DETECTION:SetRadius( Radius )
self:F2( self.ZoneName )
self.Radius = Radius
self:T2( { self.Radius } )
return self.Radius
end
--- Returns if a location is within the zone.
-- @param #ZONE_DETECTION self
-- @param DCS#Vec2 Vec2 The location to test.
-- @return #boolean true if the location is within the zone.
function ZONE_DETECTION:IsVec2InZone( Vec2 )
self:F2( Vec2 )
local Coordinates = self.Detection:GetDetectedItemCoordinates() -- This returns a list of coordinates that define the (central) locations of the detections.
for CoordinateID, Coordinate in pairs( Coordinates ) do
local ZoneVec2 = Coordinate:GetVec2()
if ZoneVec2 then
if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then
return true
end
end
end
return false
end
--- Returns if a point is within the zone.
-- @param #ZONE_DETECTION self
-- @param DCS#Vec3 Vec3 The point to test.
-- @return #boolean true if the point is within the zone.
function ZONE_DETECTION:IsVec3InZone( Vec3 )
self:F2( Vec3 )
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
end

View File

@@ -1,6 +1,7 @@
--- DCS API prototypes
-- See [https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation)
-- for further explanation and examples.
--- **DCS API** Prototypes
--
-- See the [Simulator Scripting Engine Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation) on Hoggit for further explanation and examples.
--
-- @module DCS
-- @image MOOSE.JPG
@@ -46,9 +47,13 @@ do -- world
-- @field S_EVENT_PLAYER_COMMENT
-- @field S_EVENT_SHOOTING_START [https://wiki.hoggitworld.com/view/DCS_event_shooting_start](https://wiki.hoggitworld.com/view/DCS_event_shooting_start)
-- @field S_EVENT_SHOOTING_END [https://wiki.hoggitworld.com/view/DCS_event_shooting_end](https://wiki.hoggitworld.com/view/DCS_event_shooting_end)
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added)
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change)
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove)
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added) DCS>=2.5.1
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change) DCS>=2.5.1
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove) DCS>=2.5.1
-- @field S_EVENT_KILL [https://wiki.hoggitworld.com/view/DCS_event_kill](https://wiki.hoggitworld.com/view/DCS_event_kill) DCS>=2.5.6
-- @field S_EVENT_SCORE [https://wiki.hoggitworld.com/view/DCS_event_score](https://wiki.hoggitworld.com/view/DCS_event_score) DCS>=2.5.6
-- @field S_EVENT_UNIT_LOST [https://wiki.hoggitworld.com/view/DCS_event_unit_lost](https://wiki.hoggitworld.com/view/DCS_event_unit_lost) DCS>=2.5.6
-- @field S_EVENT_LANDING_AFTER_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection](https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection) DCS>=2.5.6
-- @field S_EVENT_MAX
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
@@ -90,6 +95,11 @@ do -- world
--- Returns a table of mark panels indexed numerically that are present within the mission. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getMarkPanels)
-- @function [parent=#world] getMarkPanels
-- @return #table Table of marks.
--- Returns a table of DCS airbase objects.
-- @function [parent=#world] getAirbases
-- @param #number coalitionId The coalition side number ID. Default is all airbases are returned.
-- @return #table Table of DCS airbase objects.
end -- world
@@ -325,9 +335,27 @@ end -- coalition
do -- Types
--- @type Desc
-- @field #TypeName typeName type name
-- @field #string displayName localized display name
-- @field #table attributes object type attributes
-- @field #number speedMax0 Max speed in meters/second at zero altitude.
-- @field #number massEmpty Empty mass in kg.
-- @field #number tankerType Type of refueling system: 0=boom, 1=probe.
-- @field #number range Range in km(?).
-- @field #table box Bounding box.
-- @field #number Hmax Max height in meters.
-- @field #number Kmax ?
-- @field #number speedMax10K Max speed in meters/second at 10k altitude.
-- @field #number NyMin ?
-- @field #number NyMax ?
-- @field #number fuelMassMax Max fuel mass in kg.
-- @field #number speedMax10K Max speed in meters/second.
-- @field #number massMax Max mass of unit.
-- @field #number RCS ?
-- @field #number life Life points.
-- @field #number VyMax Max vertical velocity in m/s.
-- @field #number Kab ?
-- @field #table attributes Table of attributes.
-- @field #TypeName typeName Type Name.
-- @field #string displayName Localized display name.
-- @field #number category Unit category.
--- A distance type
-- @type Distance
@@ -337,7 +365,7 @@ do -- Types
--- Time is given in seconds.
-- @type Time
-- @extends #number
-- @extends #number Time in seconds.
--- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time.
-- @type ModelTime
@@ -345,20 +373,20 @@ do -- Types
--- Mission time is a model time plus time of the mission start.
-- @type MissionTime
-- @extends #number
-- @extends #number Time in seconds.
--- Distance is given in meters.
-- @type Distance
-- @extends #number
-- @extends #number Distance in meters.
--- Angle is given in radians.
-- @type Angle
-- @extends #number
-- @extends #number Angle in radians.
--- Azimuth is an angle of rotation around world axis y counter-clockwise.
-- @type Azimuth
-- @extends #number
-- @extends #number Angle in radians.
--- Mass is given in kilograms.
-- @type Mass
@@ -378,15 +406,15 @@ do -- Types
--- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format:
-- @type Position3
-- @field #Vec3 p
-- @field #Vec3 x
-- @field #Vec3 y
-- @field #Vec3 z
-- @field #Vec3 p 3D position vector.
-- @field #Vec3 x Orientation component of vector pointing East.
-- @field #Vec3 y Orientation component of vector pointing up.
-- @field #Vec3 z Orientation component of vector pointing North.
--- 3-dimensional box.
-- @type Box3
-- @field #Vec3 min
-- @field #Vec3 max
-- @field #Vec3 min Min.
-- @field #Vec3 max Max
--- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley".
-- @type TypeName
@@ -427,9 +455,15 @@ do -- Types
-- @type TaskArray
-- @list <#Task>
---
--@type WaypointAir
--@field #boolean lateActivated
--@field #boolean uncontrolled
end --
do -- Object
--- [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object)
@@ -485,7 +519,7 @@ do -- Object
--- Returns object coordinates for current time.
-- @function [parent=#Object] getPoint
-- @param #Object self
-- @return #Vec3
-- @return #Vec3 3D position vector with x,y,z components.
--- Returns object position for current time.
-- @function [parent=#Object] getPosition
@@ -495,7 +529,7 @@ do -- Object
--- Returns the unit's velocity vector.
-- @function [parent=#Object] getVelocity
-- @param #Object self
-- @return #Vec3
-- @return #Vec3 3D velocity vector.
--- Returns true if the unit is in air.
-- @function [parent=#Object] inAir
@@ -527,6 +561,126 @@ do -- CoalitionObject
end -- CoalitionObject
do -- Weapon
--- [DCS Class Weapon](https://wiki.hoggitworld.com/view/DCS_Class_Weapon)
-- @type Weapon
-- @extends #CoalitionObject
-- @field #Weapon.flag flag enum stores weapon flags. Some of them are combination of another flags.
-- @field #Weapon.Category Category enum that stores weapon categories.
-- @field #Weapon.GuidanceType GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
-- @field #Weapon.MissileCategory MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
-- @field #Weapon.WarheadType WarheadType enum that stores warhead types.
-- @field #Weapon.Desc Desc The descriptor of a weapon.
--- enum stores weapon flags. Some of them are combination of another flags.
-- @type Weapon.flag
-- @field LGB
-- @field TvGB
-- @field SNSGB
-- @field HEBomb
-- @field Penetrator
-- @field NapalmBomb
-- @field FAEBomb
-- @field ClusterBomb
-- @field Dispencer
-- @field CandleBomb
-- @field ParachuteBomb
-- @field GuidedBomb = LGB + TvGB + SNSGB
-- @field AnyUnguidedBomb = HEBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb
-- @field AnyBomb = GuidedBomb + AnyUnguidedBomb
-- @field LightRocket
-- @field MarkerRocket
-- @field CandleRocket
-- @field HeavyRocket
-- @field AnyRocket = LightRocket + HeavyRocket + MarkerRocket + CandleRocket
-- @field AntiRadarMissile
-- @field AntiShipMissile
-- @field AntiTankMissile
-- @field FireAndForgetASM
-- @field LaserASM
-- @field TeleASM
-- @field CruiseMissile
-- @field GuidedASM = LaserASM + TeleASM
-- @field TacticASM = GuidedASM + FireAndForgetASM
-- @field AnyASM = AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile
-- @field SRAAM
-- @field MRAAM
-- @field LRAAM
-- @field IR_AAM
-- @field SAR_AAM
-- @field AR_AAM
-- @field AnyAAM = IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
-- @field AnyMissile = AnyASM + AnyAAM
-- @field AnyAutonomousMissile = IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
-- @field GUN_POD
-- @field BuiltInCannon
-- @field Cannons = GUN_POD + BuiltInCannon
-- @field AnyAGWeapon = BuiltInCannon + GUN_POD + AnyBomb + AnyRocket + AnyASM
-- @field AnyAAWeapon = BuiltInCannon + GUN_POD + AnyAAM
-- @field UnguidedWeapon = Cannons + BuiltInCannon + GUN_POD + AnyUnguidedBomb + AnyRocket
-- @field GuidedWeapon = GuidedBomb + AnyASM + AnyAAM
-- @field AnyWeapon = AnyBomb + AnyRocket + AnyMissile + Cannons
-- @field MarkerWeapon = MarkerRocket + CandleRocket + CandleBomb
-- @field ArmWeapon = AnyWeapon - MarkerWeapon
--- Weapon.Category enum that stores weapon categories.
-- @type Weapon.Category
-- @field SHELL
-- @field MISSILE
-- @field ROCKET
-- @field BOMB
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
-- @type Weapon.GuidanceType
-- @field INS
-- @field IR
-- @field RADAR_ACTIVE
-- @field RADAR_SEMI_ACTIVE
-- @field RADAR_PASSIVE
-- @field TV
-- @field LASER
-- @field TELE
--- Weapon.MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
-- @type Weapon.MissileCategory
-- @field AAM
-- @field SAM
-- @field BM
-- @field ANTI_SHIP
-- @field CRUISE
-- @field OTHER
--- Weapon.WarheadType enum that stores warhead types.
-- @type Weapon.WarheadType
-- @field AP
-- @field HE
-- @field SHAPED_EXPLOSIVE
--- Returns the unit that launched the weapon.
-- @function [parent=#Weapon] getLauncher
-- @param #Weapon self
-- @return #Unit
--- returns target of the guided weapon. Unguided weapons and guided weapon that is targeted at the point on the ground will return nil.
-- @function [parent=#Weapon] getTarget
-- @param #Weapon self
-- @return #Object
--- returns weapon descriptor. Descriptor type depends on weapon category.
-- @function [parent=#Weapon] getDesc
-- @param #Weapon self
-- @return #Weapon.Desc
Weapon = {} --#Weapon
end -- Weapon
do -- Airbase
--- [DCS Class Airbase](https://wiki.hoggitworld.com/view/DCS_Class_Airbase)
@@ -1082,6 +1236,7 @@ do -- AI
-- @field TAKEOFF
-- @field TAKEOFF_PARKING
-- @field TURNING_POINT
-- @field TAKEOFF_PARKING_HOT
-- @field LAND
--- @type AI.Task.TurnMethod
@@ -1118,8 +1273,8 @@ do -- AI
--- @type AI.Option.Naval
-- @field #AI.Option.Naval.id id
-- @field #AI.Option.Naval.val val
--TODO: work on formation
--- @type AI.Option.Air.id
-- @field NO_OPTION
-- @field ROE
@@ -1128,7 +1283,34 @@ do -- AI
-- @field FLARE_USING
-- @field FORMATION
-- @field RTB_ON_BINGO
-- @field SILENCE
-- @field SILENCE
-- @field RTB_ON_OUT_OF_AMMO
-- @field ECM_USING
-- @field PROHIBIT_AA
-- @field PROHIBIT_JETT
-- @field PROHIBIT_AB
-- @field PROHIBIT_AG
-- @field MISSILE_ATTACK
-- @field PROHIBIT_WP_PASS_REPORT
--- @type AI.Option.Air.id.FORMATION
-- @field LINE_ABREAST
-- @field TRAIL
-- @field WEDGE
-- @field ECHELON_RIGHT
-- @field ECHELON_LEFT
-- @field FINGER_FOUR
-- @field SPREAD_FOUR
-- @field WW2_BOMBER_ELEMENT
-- @field WW2_BOMBER_ELEMENT_HEIGHT
-- @field WW2_FIGHTER_VIC
-- @field HEL_WEDGE
-- @field HEL_ECHELON
-- @field HEL_FRONT
-- @field HEL_COLUMN
-- @field COMBAT_BOX
-- @field JAVELIN_DOWN
--- @type AI.Option.Air.val
-- @field #AI.Option.Air.val.ROE ROE
@@ -1161,12 +1343,27 @@ do -- AI
-- @field AGAINST_FIRED_MISSILE
-- @field WHEN_FLYING_IN_SAM_WEZ
-- @field WHEN_FLYING_NEAR_ENEMIES
--- @type AI.Option.Air.val.ECM_USING
-- @field NEVER_USE
-- @field USE_IF_ONLY_LOCK_BY_RADAR
-- @field USE_IF_DETECTED_LOCK_BY_RADAR
-- @field ALWAYS_USE
--- @type AI.Option.Air.val.MISSILE_ATTACK
-- @field MAX_RANGE
-- @field NEZ_RANGE
-- @field HALF_WAY_RMAX_NEZ
-- @field TARGET_THREAT_EST
-- @field RANDOM_RANGE
--- @type AI.Option.Ground.id
-- @field NO_OPTION
-- @field ROE @{#AI.Option.Ground.val.ROE}
-- @field DISPERSE_ON_ATTACK true or false
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
-- @field ENGAGE_AIR_WEAPONS
--- @type AI.Option.Ground.val
-- @field #AI.Option.Ground.val.ROE ROE
@@ -1196,7 +1393,4 @@ do -- AI
AI = {} --#AI
end -- AI
end -- AI

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,54 @@
--- **Functional** -- Keep airbases clean of crashing or colliding airplanes, and kill missiles when being fired at airbases.
--
--
-- ===
--
--
-- ## Features:
--
--
--
--
-- * Try to keep the airbase clean and operational.
-- * Prevent airplanes from crashing.
-- * Clean up obstructing airplanes from the runway that are standing still for a period of time.
-- * Prevent airplanes firing missiles within the airbase zone.
--
--
-- ===
--
--
-- ## Missions:
--
--
-- [CLA - CleanUp Airbase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CLA%20-%20CleanUp%20Airbase)
--
--
-- ===
--
--
-- Specific airbases need to be provided that need to be guarded. Each airbase registered, will be guarded within a zone of 8 km around the airbase.
-- Any unit that fires a missile, or shoots within the zone of an airbase, will be monitored by CLEANUP_AIRBASE.
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
-- Any airborne or ground unit that is on the runway below 30 meters (default value) will be automatically removed if it is damaged.
--
--
-- This is not a full 100% secure implementation. It is still possible that CLEANUP_AIRBASE cannot prevent (in-time) to keep the airbase clean.
-- The following situations may happen that will still stop the runway of an airbase:
--
--
-- * A damaged unit is not removed on time when above the runway, and crashes on the runway.
-- * A bomb or missile is still able to dropped on the runway.
-- * Units collide on the airbase, and could not be removed on time.
--
--
-- When a unit is within the airbase zone and needs to be monitored,
-- its status will be checked every 0.25 seconds! This is required to ensure that the airbase is kept clean.
-- But as a result, there is more CPU overload.
--
--
-- So as an advise, I suggest you use the CLEANUP_AIRBASE class with care:
--
--
-- * Only monitor airbases that really need to be monitored!
-- * Try not to monitor airbases that are likely to be invaded by enemy troops.
-- For these airbases, there is little use to keep them clean, as they will be invaded anyway...
--
--
-- By following the above guidelines, you can add airbase cleanup with acceptable CPU overhead.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ### Contributions:
--
-- ===
--
--
-- @module Functional.CleanUp
-- @image CleanUp_Airbases.JPG
@@ -60,29 +60,29 @@
-- @extends #CLEANUP_AIRBASE.__
--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
--
--
-- # 1. CLEANUP_AIRBASE Constructor
--
--
-- Creates the main object which is preventing the airbase to get polluted with debris on the runway, which halts the airbase.
--
--
-- -- Clean these Zones.
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi )
--
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi } )
--
-- -- or
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
--
--
-- # 2. Add or Remove airbases
--
--
-- The method @{#CLEANUP_AIRBASE.AddAirbase}() to add an airbase to the cleanup validation process.
-- The method @{#CLEANUP_AIRBASE.RemoveAirbase}() removes an airbase from the cleanup validation process.
--
--
-- # 3. Clean missiles and bombs within the airbase zone.
--
--
-- When missiles or bombs hit the runway, the airbase operations stop.
-- Use the method @{#CLEANUP_AIRBASE.SetCleanMissiles}() to control the cleaning of missiles, which will prevent airbases to stop.
-- Note that this method will not allow anymore airbases to be attacked, so there is a trade-off here to do.
--
--
-- @field #CLEANUP_AIRBASE
CLEANUP_AIRBASE = {
ClassName = "CLEANUP_AIRBASE",
@@ -106,11 +106,11 @@ CLEANUP_AIRBASE.__.Airbases = {}
-- or
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
function CLEANUP_AIRBASE:New( AirbaseNames )
function CLEANUP_AIRBASE:New( AirbaseNames )
local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP_AIRBASE
self:F( { AirbaseNames } )
if type( AirbaseNames ) == 'table' then
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
self:AddAirbase( AirbaseName )
@@ -119,9 +119,9 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
local AirbaseName = AirbaseNames
self:AddAirbase( AirbaseName )
end
self:HandleEvent( EVENTS.Birth, self.__.OnEventBirth )
self.__.CleanUpScheduler = SCHEDULER:New( self, self.__.CleanUpSchedule, {}, 1, self.TimeInterval )
self:HandleEvent( EVENTS.EngineShutdown , self.__.EventAddForCleanUp )
@@ -130,7 +130,7 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
self:HandleEvent( EVENTS.PilotDead, self.__.OnEventCrash )
self:HandleEvent( EVENTS.Dead, self.__.OnEventCrash )
self:HandleEvent( EVENTS.Crash, self.__.OnEventCrash )
for UnitName, Unit in pairs( _DATABASE.UNITS ) do
local Unit = Unit -- Wrapper.Unit#UNIT
if Unit:IsAlive() ~= nil then
@@ -144,7 +144,7 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
end
end
end
return self
end
@@ -155,7 +155,7 @@ end
function CLEANUP_AIRBASE:AddAirbase( AirbaseName )
self.__.Airbases[AirbaseName] = AIRBASE:FindByName( AirbaseName )
self:F({"Airbase:", AirbaseName, self.__.Airbases[AirbaseName]:GetDesc()})
return self
end
@@ -197,7 +197,7 @@ function CLEANUP_AIRBASE.__:IsInAirbase( Vec2 )
break;
end
end
return InAirbase
end
@@ -233,7 +233,7 @@ end
-- @param DCS#Weapon MissileObject
function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
self:F( { MissileObject } )
if MissileObject and MissileObject:isExist() then
MissileObject:destroy()
self:T( "MissileObject Destroyed")
@@ -245,7 +245,7 @@ end
function CLEANUP_AIRBASE.__:OnEventBirth( EventData )
self:F( { EventData } )
if EventData.IniUnit:IsAlive() ~= nil then
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() ~= nil then
if self:IsInAirbase( EventData.IniUnit:GetVec2() ) then
self.CleanUpList[EventData.IniDCSUnitName] = {}
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniUnit
@@ -267,7 +267,7 @@ function CLEANUP_AIRBASE.__:OnEventCrash( Event )
--TODO: DCS BUG - This stuff is not working due to a DCS bug. Burning units cannot be destroyed.
-- self:T("before getGroup")
-- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired
-- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired
-- self:T("after getGroup")
-- _grp:destroy()
-- self:T("after deactivateGroup")
@@ -280,7 +280,7 @@ function CLEANUP_AIRBASE.__:OnEventCrash( Event )
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName
end
end
--- Detects if a unit shoots a missile.
@@ -334,16 +334,16 @@ function CLEANUP_AIRBASE.__:AddForCleanUp( CleanUpUnit, CleanUpUnitName )
self.CleanUpList[CleanUpUnitName] = {}
self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit
self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName
local CleanUpGroup = CleanUpUnit:GetGroup()
self.CleanUpList[CleanUpUnitName].CleanUpGroup = CleanUpGroup
self.CleanUpList[CleanUpUnitName].CleanUpGroupName = CleanUpGroup:GetName()
self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime()
self.CleanUpList[CleanUpUnitName].CleanUpMoved = false
self:T( { "CleanUp: Add to CleanUpList: ", CleanUpGroup:GetName(), CleanUpUnitName } )
end
--- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given AirbaseNames. If this is the case, add the Group to the CLEANUP_AIRBASE List.
@@ -369,7 +369,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
end
end
end
end
@@ -380,26 +380,26 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
local CleanUpCount = 0
for CleanUpUnitName, CleanUpListData in pairs( self.CleanUpList ) do
CleanUpCount = CleanUpCount + 1
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
if CleanUpUnit:IsAlive() ~= nil then
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then
local CleanUpCoordinate = CleanUpUnit:GetCoordinate()
self:T( { "CleanUp Scheduler", CleanUpUnitName } )
if CleanUpUnit:GetLife() <= CleanUpUnit:GetLife0() * 0.95 then
if CleanUpUnit:IsAboveRunway() then
if CleanUpUnit:InAir() then
local CleanUpLandHeight = CleanUpCoordinate:GetLandHeight()
local CleanUpUnitHeight = CleanUpCoordinate.y - CleanUpLandHeight
if CleanUpUnitHeight < 100 then
self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } )
self:DestroyUnit( CleanUpUnit )
@@ -439,7 +439,6 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
end
end
self:T(CleanUpCount)
return true
end

View File

@@ -286,9 +286,9 @@ do -- DESIGNATE
-- * The status report can be automatically flashed by selecting "Status" -> "Flash Status On".
-- * The automatic flashing of the status report can be deactivated by selecting "Status" -> "Flash Status Off".
-- * The flashing of the status menu is disabled by default.
-- * The method @{#DESIGNATE.FlashStatusMenu}() can be used to enable or disable to flashing of the status menu.
-- * The method @{#DESIGNATE.SetFlashStatusMenu}() can be used to enable or disable to flashing of the status menu.
--
-- Designate:FlashStatusMenu( true )
-- Designate:SetFlashStatusMenu( true )
--
-- The example will activate the flashing of the status menu for this Designate object.
--
@@ -469,7 +469,7 @@ do -- DESIGNATE
self.CC = CC
self.Detection = Detection
self.AttackSet = AttackSet
self.RecceSet = Detection:GetDetectionSetGroup()
self.RecceSet = Detection:GetDetectionSet()
self.Recces = {}
self.Designating = {}
self:SetDesignateName()
@@ -1008,9 +1008,9 @@ do -- DESIGNATE
if string.find( Designating, "L", 1, true ) == nil then
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Search other target", DetectedMenu, self.MenuForget, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, 60, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, self.LaseDuration, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, self.LaseDuration ):SetTime( MenuTime ):SetTag( self.DesignateName )
else
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
@@ -1168,10 +1168,10 @@ do -- DESIGNATE
if string.find( self.Designating[Index], "L", 1, true ) == nil then
self.Designating[Index] = self.Designating[Index] .. "L"
self.LaseStart = timer.getTime()
self.LaseDuration = Duration
self:Lasing( Index, Duration, LaserCode )
end
self.LaseStart = timer.getTime()
self.LaseDuration = Duration
self:Lasing( Index, Duration, LaserCode )
end
@@ -1182,7 +1182,7 @@ do -- DESIGNATE
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
local MarkingCount = 0
local MarkedTypes = {}
@@ -1330,7 +1330,7 @@ do -- DESIGNATE
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
self:__Lasing( -30, Index, Duration, LaserCodeRequested )
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )
self:SetDesignateMenu()
@@ -1352,7 +1352,7 @@ do -- DESIGNATE
end
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
local Recces = self.Recces
@@ -1377,7 +1377,7 @@ do -- DESIGNATE
function DESIGNATE:onafterSmoke( From, Event, To, Index, Color )
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
local TargetSetUnitCount = TargetSetUnit:Count()
local MarkedCount = 0
@@ -1393,7 +1393,7 @@ do -- DESIGNATE
self:F( "Smoking ..." )
local RecceGroup = self.RecceSet:FindNearestGroupFromPointVec2(SmokeUnit:GetPointVec2())
local RecceUnit = RecceGroup:GetUnit( 1 )
local RecceUnit = RecceGroup:GetUnit( 1 ) -- Wrapper.Unit#UNIT
if RecceUnit then
@@ -1422,7 +1422,7 @@ do -- DESIGNATE
function DESIGNATE:onafterIlluminate( From, Event, To, Index )
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
local TargetUnit = TargetSetUnit:GetFirst()
if TargetUnit then

View File

@@ -291,23 +291,40 @@ do -- DETECTION_BASE
--- @type DETECTION_BASE.DetectedItems
-- @list <#DETECTION_BASE.DetectedItem>
--- @type DETECTION_BASE.DetectedItem
--- Detected item data structrue.
-- @type DETECTION_BASE.DetectedItem
-- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not.
-- @field Core.Set#SET_UNIT Set
-- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area.
-- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area.
-- @field #boolean Changed Documents if the detected area has changes.
-- @field Core.Set#SET_UNIT Set The Set of Units in the detected area.
-- @field Core.Zone#ZONE_UNIT Zone The Zone of the detected area.
-- @field #boolean Changed Documents if the detected area has changed.
-- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes).
-- @field #number ID -- The identifier of the detected area.
-- @field #number ID The identifier of the detected area.
-- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area.
-- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area.
-- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem.
-- @field Core.Point#COORDINATE InterceptCoord Intercept coordiante.
-- @field #number DistanceRecce Distance in meters of the Recce.
-- @field #number Index Detected item key. Could also be a string.
-- @field #string ItemID ItemPrefix .. "." .. self.DetectedItemMax.
-- @field #boolean Locked Lock detected item.
-- @field #table PlayersNearBy Table of nearby players.
-- @field #table FriendliesDistance Table of distances to friendly units.
-- @field #string TypeName Type name of the detected unit.
-- @field #string CategoryName Catetory name of the detected unit.
-- @field #string Name Name of the detected object.
-- @field #boolean IsVisible If true, detected object is visible.
-- @field #number LastTime Last time the detected item was seen.
-- @field DCS#Vec3 LastPos Last known position of the detected item.
-- @field DCS#Vec3 LastVelocity Last recorded 3D velocity vector of the detected item.
-- @field #boolean KnowType Type of detected item is known.
-- @field #boolean KnowDistance Distance to the detected item is known.
-- @field #number Distance Distance to the detected item.
--- DETECTION constructor.
-- @param #DETECTION_BASE self
-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role.
-- @param Core.Set#SET_GROUP DetectionSet The @{Set} of @{Group}s that is used to detect the units.
-- @return #DETECTION_BASE self
function DETECTION_BASE:New( DetectionSetGroup )
function DETECTION_BASE:New( DetectionSet )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_BASE
@@ -316,7 +333,7 @@ do -- DETECTION_BASE
self.DetectedItemMax = 0
self.DetectedItems = {}
self.DetectionSetGroup = DetectionSetGroup
self.DetectionSet = DetectionSet
self.RefreshTimeInterval = 30
@@ -398,7 +415,7 @@ do -- DETECTION_BASE
-- @param #string To The To State string.
self:AddTransition( "Detecting", "Detect", "Detecting" )
self:AddTransition( "Detecting", "DetectionGroup", "Detecting" )
self:AddTransition( "Detecting", "Detection", "Detecting" )
--- OnBefore Transition Handler for Event Detect.
-- @function [parent=#DETECTION_BASE] OnBeforeDetect
@@ -441,15 +458,18 @@ do -- DETECTION_BASE
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #table Units Table of detected units.
--- Synchronous Event Trigger for Event Detected.
-- @function [parent=#DETECTION_BASE] Detected
-- @param #DETECTION_BASE self
-- @param #table Units Table of detected units.
--- Asynchronous Event Trigger for Event Detected.
-- @function [parent=#DETECTION_BASE] __Detected
-- @param #DETECTION_BASE self
-- @param #number Delay The delay in seconds.
-- @param #table Units Table of detected units.
self:AddTransition( "Detecting", "DetectedItem", "Detecting" )
@@ -540,14 +560,40 @@ do -- DETECTION_BASE
self.DetectedObjects[DetectionObjectName].Distance = 10000000
end
for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do
--self:F( { DetectionGroupData } )
self:F( { DetectionGroup = DetectionGroupData:GetName() } )
self:__DetectionGroup( DetectDelay, DetectionGroupData, DetectionTimeStamp ) -- Process each detection asynchronously.
self.DetectionCount = self.DetectionCount + 1
DetectDelay = DetectDelay + 1
end
-- Count alive(!) groups only. Solves issue #1173 https://github.com/FlightControl-Master/MOOSE/issues/1173
self.DetectionCount = self:CountAliveRecce()
local DetectionInterval = self.DetectionCount / ( self.RefreshTimeInterval - 1 )
self:ForEachAliveRecce(
function( DetectionGroup )
self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously.
DetectDelay = DetectDelay + DetectionInterval
end
)
self:__Detect( -self.RefreshTimeInterval )
end
--- @param #DETECTION_BASE self
-- @param #number The amount of alive recce.
function DETECTION_BASE:CountAliveRecce()
return self.DetectionSet:CountAlive()
end
--- @param #DETECTION_BASE self
function DETECTION_BASE:ForEachAliveRecce( IteratorFunction, ... )
self:F2( arg )
self.DetectionSet:ForEachGroupAlive( IteratorFunction, arg )
return self
end
--- @param #DETECTION_BASE self
-- @param #string From The From State string.
@@ -555,7 +601,7 @@ do -- DETECTION_BASE
-- @param #string To The To State string.
-- @param Wrapper.Group#GROUP DetectionGroup The Group detecting.
-- @param #number DetectionTimeStamp Time stamp of detection event.
function DETECTION_BASE:onafterDetectionGroup( From, Event, To, DetectionGroup, DetectionTimeStamp )
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
--self:F( { DetectedObjects = self.DetectedObjects } )
@@ -563,16 +609,16 @@ do -- DETECTION_BASE
local HasDetectedObjects = false
if DetectionGroup:IsAlive() then
if Detection and Detection:IsAlive() then
--self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
local DetectionGroupName = DetectionGroup:GetName()
local DetectionUnit = DetectionGroup:GetUnit(1)
local DetectionGroupName = Detection:GetName()
local DetectionUnit = Detection:GetUnit(1)
local DetectedUnits = {}
local DetectedTargets = DetectionGroup:GetDetectedTargets(
local DetectedTargets = Detection:GetDetectedTargets(
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
@@ -624,7 +670,7 @@ do -- DETECTION_BASE
local DetectedObjectVec3 = DetectedObject:getPoint()
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
local DetectionGroupVec3 = DetectionGroup:GetVec3()
local DetectionGroupVec3 = Detection:GetVec3()
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 +
@@ -788,23 +834,27 @@ do -- DETECTION_BASE
-- IsDetected = false!
-- This is used in A2A_TASK_DISPATCHER to initiate fighter sweeping! The TASK_A2A_INTERCEPT tasks will be replaced with TASK_A2A_SWEEP tasks.
for DetectedObjectName, DetectedObject in pairs( self.DetectedObjects ) do
if self.DetectedObjects[DetectedObjectName].IsDetected == true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp + 60 <= DetectionTimeStamp then
if self.DetectedObjects[DetectedObjectName].IsDetected == true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp + 300 <= DetectionTimeStamp then
self.DetectedObjects[DetectedObjectName].IsDetected = false
end
end
self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method.
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
self:UpdateDetectedItemDetection( DetectedItem )
self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list.
if DetectedItem then
self:__DetectedItem( 0.1, DetectedItem )
end
end
self:__Detect( self.RefreshTimeInterval )
end
end
@@ -812,7 +862,7 @@ do -- DETECTION_BASE
do -- DetectionItems Creation
-- Clean the DetectedItem table.
--- Clean the DetectedItem table.
-- @param #DETECTION_BASE self
-- @return #DETECTION_BASE
function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID )
@@ -838,7 +888,7 @@ do -- DETECTION_BASE
local DetectedItems = self:GetDetectedItems()
for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do
local DetectedSet = self:GetDetectedSet( DetectedItem )
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
if DetectedSet then
DetectedSet:RemoveUnitsByName( UnitName )
end
@@ -1012,7 +1062,7 @@ do -- DETECTION_BASE
--- Set the parameters to calculate to optimal intercept point.
-- @param #DETECTION_BASE self
-- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false.
-- @param #number IntereptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne.
-- @param #number InterceptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne.
-- @return #DETECTION_BASE self
function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay )
self:F2()
@@ -1229,17 +1279,17 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the FAC units ...
-- @param #DETECTION_BASE self
-- @param DetectedItem
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @param DCS#Unit.Category Category The category of the unit.
-- @return #boolean true if there are friendlies nearby
function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category )
--self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } )
-- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } )
return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false
end
--- Returns friendly units nearby the FAC units ...
-- @param #DETECTION_BASE self
-- @param DetectedItem
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @param DCS#Unit.Category Category The category of the unit.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category )
@@ -1249,6 +1299,7 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the intercept ...
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean trhe if there are friendlies near the intercept.
function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem )
@@ -1257,6 +1308,7 @@ do -- DETECTION_BASE
--- Returns friendly units nearby the intercept point ...
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem )
@@ -1265,7 +1317,8 @@ do -- DETECTION_BASE
--- Returns the distance used to identify friendlies near the deteted item ...
-- @param #DETECTION_BASE self
-- @return #number The distance.
-- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #table A table of distances to friendlies.
function DETECTION_BASE:GetFriendliesDistance( DetectedItem )
return DetectedItem.FriendliesDistance
@@ -1273,6 +1326,7 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the FAC units ...
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean trhe if there are friendlies nearby
function DETECTION_BASE:IsPlayersNearBy( DetectedItem )
@@ -1281,6 +1335,7 @@ do -- DETECTION_BASE
--- Returns friendly units nearby the FAC units ...
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetPlayersNearBy( DetectedItem )
@@ -1289,10 +1344,11 @@ do -- DETECTION_BASE
--- Background worker function to determine if there are friendlies nearby ...
-- @param #DETECTION_BASE self
-- @param #table TargetData
function DETECTION_BASE:ReportFriendliesNearBy( TargetData )
--self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } )
local DetectedItem = TargetData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedItem = TargetData.DetectedItem --#DETECTION_BASE.DetectedItem
local DetectedSet = TargetData.DetectedItem.Set
local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT
@@ -1376,33 +1432,37 @@ do -- DETECTION_BASE
world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, TargetData )
DetectedItem.PlayersNearBy = nil
local DetectionZone = ZONE_UNIT:New( "DetectionPlayers", DetectedUnit, self.FriendliesRange )
_DATABASE:ForEachPlayer(
--- @param Wrapper.Unit#UNIT PlayerUnit
function( PlayerUnitName )
local PlayerUnit = UNIT:FindByName( PlayerUnitName )
if PlayerUnit and PlayerUnit:IsInZone(DetectionZone) then
-- Fix for issue https://github.com/FlightControl-Master/MOOSE/issues/1225
if PlayerUnit and PlayerUnit:IsAlive() then
local coord=PlayerUnit:GetCoordinate()
if coord and coord:IsInRadius( DetectedUnitCoord, self.FriendliesRange ) then
local PlayerUnitCategory = PlayerUnit:GetDesc().category
if ( not self.FriendliesCategory ) or ( self.FriendliesCategory and ( self.FriendliesCategory == PlayerUnitCategory ) ) then
local PlayerUnitName = PlayerUnit:GetName()
local PlayerUnitCategory = PlayerUnit:GetDesc().category
DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {}
DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit
-- Friendlies are sorted per unit category.
DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {}
DetectedItem.FriendliesNearBy[PlayerUnitCategory] = DetectedItem.FriendliesNearBy[PlayerUnitCategory] or {}
DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName] = PlayerUnit
local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() )
DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {}
DetectedItem.FriendliesDistance[Distance] = PlayerUnit
if ( not self.FriendliesCategory ) or ( self.FriendliesCategory and ( self.FriendliesCategory == PlayerUnitCategory ) ) then
local PlayerUnitName = PlayerUnit:GetName()
DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {}
DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit
-- Friendlies are sorted per unit category.
DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {}
DetectedItem.FriendliesNearBy[PlayerUnitCategory] = DetectedItem.FriendliesNearBy[PlayerUnitCategory] or {}
DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName] = PlayerUnit
local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() )
DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {}
DetectedItem.FriendliesDistance[Distance] = PlayerUnit
end
end
end
end
@@ -1514,31 +1574,31 @@ do -- DETECTION_BASE
--- Adds a new DetectedItem to the DetectedItems list.
-- The DetectedItem is a table and contains a SET_UNIT in the field Set.
-- @param #DETECTION_BASE self
-- @param ItemPrefix
-- @param DetectedItemKey The key of the DetectedItem.
-- @param #string ItemPrefix Prefix of detected item.
-- @param #number DetectedItemKey The key of the DetectedItem. Default self.DetectedItemMax. Could also be a string in principle.
-- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added.
-- @return #DETECTION_BASE.DetectedItem
function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set )
local DetectedItem = {}
local DetectedItem = {} --#DETECTION_BASE.DetectedItem
self.DetectedItemCount = self.DetectedItemCount + 1
self.DetectedItemMax = self.DetectedItemMax + 1
if DetectedItemKey then
self.DetectedItems[DetectedItemKey] = DetectedItem
else
self.DetectedItems[self.DetectedItemMax] = DetectedItem
end
self.DetectedItemsByIndex[self.DetectedItemMax] = DetectedItem
DetectedItemKey = DetectedItemKey or self.DetectedItemMax
self.DetectedItems[DetectedItemKey] = DetectedItem
self.DetectedItemsByIndex[DetectedItemKey] = DetectedItem
DetectedItem.Index = DetectedItemKey
DetectedItem.Set = Set or SET_UNIT:New():FilterDeads():FilterCrashes()
DetectedItem.Index = DetectedItemKey or self.DetectedItemMax
DetectedItem.ItemID = ItemPrefix .. "." .. self.DetectedItemMax
DetectedItem.ID = self.DetectedItemMax
DetectedItem.Removed = false
if self.Locking then
self:LockDetectedItem( DetectedItem )
end
return DetectedItem
end
@@ -1549,9 +1609,11 @@ do -- DETECTION_BASE
-- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added.
-- @param Core.Zone#ZONE_UNIT Zone (optional) The Zone to be added where the Units are located.
-- @return #DETECTION_BASE.DetectedItem
function DETECTION_BASE:AddDetectedItemZone( DetectedItemKey, Set, Zone )
function DETECTION_BASE:AddDetectedItemZone( ItemPrefix, DetectedItemKey, Set, Zone )
local DetectedItem = self:AddDetectedItem( "AREA", DetectedItemKey, Set )
self:F( { ItemPrefix, DetectedItemKey, Set, Zone } )
local DetectedItem = self:AddDetectedItem( ItemPrefix, DetectedItemKey, Set )
DetectedItem.Zone = Zone
@@ -1624,7 +1686,7 @@ do -- DETECTION_BASE
-- @return #DETECTION_BASE.DetectedItem
function DETECTION_BASE:GetDetectedItemByIndex( Index )
self:F( { DetectedItemsByIndex = self.DetectedItemsByIndex } )
self:F( { self.DetectedItemsByIndex } )
local DetectedItem = self.DetectedItemsByIndex[Index]
if DetectedItem then
@@ -1661,7 +1723,7 @@ do -- DETECTION_BASE
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT DetectedSet
function DETECTION_BASE:GetDetectedSet( DetectedItem )
function DETECTION_BASE:GetDetectedItemSet( DetectedItem )
local DetectedSetUnit = DetectedItem and DetectedItem.Set
if DetectedSetUnit then
@@ -1697,6 +1759,7 @@ do -- DETECTION_BASE
--- Checks if there is at least one UNIT detected in the Set of the the DetectedItem.
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet.
function DETECTION_BASE:IsDetectedItemDetected( DetectedItem )
@@ -1724,6 +1787,68 @@ do -- DETECTION_BASE
end
--- Lock the detected items when created and lock all existing detected items.
-- @param #DETECTION_BASE self
-- @return #DETECTION_BASE
function DETECTION_BASE:LockDetectedItems()
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
self:LockDetectedItem( DetectedItem )
end
self.Locking = true
return self
end
--- Unlock the detected items when created and unlock all existing detected items.
-- @param #DETECTION_BASE self
-- @return #DETECTION_BASE
function DETECTION_BASE:UnlockDetectedItems()
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
self:UnlockDetectedItem( DetectedItem )
end
self.Locking = nil
return self
end
--- Validate if the detected item is locked.
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #boolean
function DETECTION_BASE:IsDetectedItemLocked( DetectedItem )
return self.Locking and DetectedItem.Locked == true
end
--- Lock a detected item.
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE
function DETECTION_BASE:LockDetectedItem( DetectedItem )
DetectedItem.Locked = true
return self
end
--- Unlock a detected item.
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE
function DETECTION_BASE:UnlockDetectedItem( DetectedItem )
DetectedItem.Locked = nil
return self
end
--- Set the detected item coordinate.
-- @param #DETECTION_BASE self
@@ -1759,6 +1884,20 @@ do -- DETECTION_BASE
return nil
end
--- Get a list of the detected item coordinates.
-- @param #DETECTION_BASE self
-- @return #table A table of Core.Point#COORDINATE
function DETECTION_BASE:GetDetectedItemCoordinates()
local Coordinates = {}
for DetectedItemID, DetectedItem in pairs( self:GetDetectedItems() ) do
Coordinates[DetectedItem] = self:GetDetectedItemCoordinate( DetectedItem )
end
return Coordinates
end
--- Set the detected item threatlevel.
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threatlevel for.
@@ -1810,13 +1949,13 @@ do -- DETECTION_BASE
return nil
end
--- Get the detection Groups.
--- Get the Detection Set.
-- @param #DETECTION_BASE self
-- @return Core.Set#SET_GROUP
function DETECTION_BASE:GetDetectionSetGroup()
-- @return #DETECTION_BASE self
function DETECTION_BASE:GetDetectionSet()
local DetectionSetGroup = self.DetectionSetGroup
return DetectionSetGroup
local DetectionSet = self.DetectionSet
return DetectionSet
end
--- Find the nearest Recce of the DetectedItem.
@@ -1828,7 +1967,7 @@ do -- DETECTION_BASE
local NearestRecce = nil
local DistanceRecce = 1000000000 -- Units are not further than 1000000 km away from an area :-)
for RecceGroupName, RecceGroup in pairs( self.DetectionSetGroup:GetSet() ) do
for RecceGroupName, RecceGroup in pairs( self.DetectionSet:GetSet() ) do
if RecceGroup and RecceGroup:IsAlive() then
for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do
if RecceUnit:IsActive() then
@@ -1947,7 +2086,8 @@ do -- DETECTION_UNITS
function DETECTION_UNITS:CreateDetectionItems()
-- Loop the current detected items, and check if each object still exists and is detected.
for DetectedItemKey, DetectedItem in pairs( self.DetectedItems ) do
for DetectedItemKey, _DetectedItem in pairs( self.DetectedItems ) do
local DetectedItem=_DetectedItem --#DETECTION_BASE.DetectedItem
local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT
@@ -2033,7 +2173,7 @@ do -- DETECTION_UNITS
local DetectedFirstUnitCoord = DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate( DetectedItem, DetectedFirstUnitCoord, DetectedFirstUnit )
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table
self:SetDetectedItemThreatLevel( DetectedItem )
self:NearestRecce( DetectedItem )
@@ -2268,7 +2408,7 @@ do -- DETECTION_TYPES
local DetectedUnitCoord = DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate( DetectedItem, DetectedUnitCoord, DetectedFirstUnit )
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table
self:SetDetectedItemThreatLevel( DetectedItem )
self:NearestRecce( DetectedItem )
end
@@ -2286,7 +2426,7 @@ do -- DETECTION_TYPES
function DETECTION_TYPES:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
self:F( { DetectedItem = DetectedItem } )
local DetectedSet = self:GetDetectedSet( DetectedItem )
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
self:T( DetectedItem )
@@ -2402,28 +2542,67 @@ do -- DETECTION_AREAS
-- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for.
-- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use.
-- @return Core.Report#REPORT The report of the detection items.
function DETECTION_AREAS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
function DETECTION_AREAS:DetectedItemReportMenu( DetectedItem, AttackGroup, Settings )
self:F( { DetectedItem = DetectedItem } )
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
if DetectedItem then
local DetectedSet = self:GetDetectedSet( DetectedItem )
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
local ReportSummaryItem
local DetectedZone = self:GetDetectedItemZone( DetectedItem )
local DetectedItemCoordinate = DetectedZone:GetCoordinate()
local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings )
local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem )
local Report = REPORT:New()
Report:Add( DetectedItemID )
Report:Add( string.format( "Threat: [%s%s]", string.rep( "", ThreatLevelA2G ), string.rep( "", 10-ThreatLevelA2G ) ) )
return Report
end
return nil
end
--- Report summary of a detected item using a given numeric index.
-- @param #DETECTION_AREAS self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for.
-- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use.
-- @return Core.Report#REPORT The report of the detection items.
function DETECTION_AREAS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
self:F( { DetectedItem = DetectedItem } )
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
if DetectedItem then
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
local ReportSummaryItem
--local DetectedZone = self:GetDetectedItemZone( DetectedItem )
local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem )
local DetectedAir = DetectedSet:HasAirUnits()
local DetectedAltitude = self:GetDetectedItemCoordinate( DetectedItem )
local DetectedItemCoordText = ""
if DetectedAir > 0 then
DetectedItemCoordText = DetectedItemCoordinate:ToStringA2A( AttackGroup, Settings )
else
DetectedItemCoordText = DetectedItemCoordinate:ToStringA2G( AttackGroup, Settings )
end
local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem )
local DetectedItemsCount = DetectedSet:Count()
local DetectedItemsTypes = DetectedSet:GetTypeNames()
local Report = REPORT:New()
Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText)
Report:Add( string.format( "Threat: [%s]", string.rep( "", ThreatLevelA2G ), string.rep( "", 10-ThreatLevelA2G ) ) )
Report:Add( string.format( "Threat: [%s%s]", string.rep( "", ThreatLevelA2G ), string.rep( "", 10-ThreatLevelA2G ) ) )
Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) )
Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) )
--Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) )
return Report
end
@@ -2741,7 +2920,7 @@ do -- DETECTION_AREAS
if AddedToDetectionArea == false then
-- New detection area
local DetectedItem = self:AddDetectedItemZone( nil,
local DetectedItem = self:AddDetectedItemZone( "AREA", nil,
SET_UNIT:New():FilterDeads():FilterCrashes(),
ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange )
)
@@ -2773,7 +2952,7 @@ do -- DETECTION_AREAS
-- If there were friendlies nearby, and now there aren't any friendlies nearby, we flag the area as "changed".
-- This is for the A2G dispatcher to detect if there is a change in the tactical situation.
local OldFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table
local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
if OldFriendliesNearbyGround ~= NewFriendliesNearbyGround then
DetectedItem.Changed = true
@@ -2819,3 +2998,5 @@ do -- DETECTION_AREAS
end
end

View File

@@ -0,0 +1,416 @@
do -- DETECTION_ZONES
--- @type DETECTION_ZONES
-- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target.
-- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange.
-- @extends Functional.Detection#DETECTION_BASE
--- (old, to be revised ) Detect units within the battle zone for a list of @{Core.Zone}s detecting targets following (a) detection method(s),
-- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected.
-- The class is group the detected units within zones given a DetectedZoneRange parameter.
-- A set with multiple detected zones will be created as there are groups of units detected.
--
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
--
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
-- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_ZONES}.
--
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
--
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}().
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index.
--
-- ## 4.4) Flare or Smoke detected units
--
-- Use the methods @{Functional.Detection#DETECTION_ZONES.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_ZONES.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place.
--
-- ## 4.5) Flare or Smoke or Bound detected zones
--
-- Use the methods:
--
-- * @{Functional.Detection#DETECTION_ZONES.FlareDetectedZones}() to flare in a color
-- * @{Functional.Detection#DETECTION_ZONES.SmokeDetectedZones}() to smoke in a color
-- * @{Functional.Detection#DETECTION_ZONES.SmokeDetectedZones}() to bound with a tire with a white flag
--
-- the detected zones when a new detection has taken place.
--
-- @field #DETECTION_ZONES
DETECTION_ZONES = {
ClassName = "DETECTION_ZONES",
DetectionZoneRange = nil,
}
--- DETECTION_ZONES constructor.
-- @param #DETECTION_ZONES self
-- @param Core.Set#SET_ZONE DetectionSetZone The @{Set} of ZONE_RADIUS.
-- @param DCS#Coalition.side DetectionCoalition The coalition of the detection.
-- @return #DETECTION_ZONES
function DETECTION_ZONES:New( DetectionSetZone, DetectionCoalition )
-- Inherits from DETECTION_BASE
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetZone ) ) -- #DETECTION_ZONES
self.DetectionSetZone = DetectionSetZone -- Core.Set#SET_ZONE
self.DetectionCoalition = DetectionCoalition
self._SmokeDetectedUnits = false
self._FlareDetectedUnits = false
self._SmokeDetectedZones = false
self._FlareDetectedZones = false
self._BoundDetectedZones = false
return self
end
--- @param #DETECTION_ZONES self
-- @param #number The amount of alive recce.
function DETECTION_ZONES:CountAliveRecce()
return self.DetectionSetZone:Count()
end
--- @param #DETECTION_ZONES self
function DETECTION_ZONES:ForEachAliveRecce( IteratorFunction, ... )
self:F2( arg )
self.DetectionSetZone:ForEachZone( IteratorFunction, arg )
return self
end
--- Report summary of a detected item using a given numeric index.
-- @param #DETECTION_ZONES self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for.
-- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use.
-- @return Core.Report#REPORT The report of the detection items.
function DETECTION_ZONES:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
self:F( { DetectedItem = DetectedItem } )
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
if DetectedItem then
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
local ReportSummaryItem
local DetectedZone = self:GetDetectedItemZone( DetectedItem )
local DetectedItemCoordinate = DetectedZone:GetCoordinate()
local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings )
local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem )
local DetectedItemsCount = DetectedSet:Count()
local DetectedItemsTypes = DetectedSet:GetTypeNames()
local Report = REPORT:New()
Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText)
Report:Add( string.format( "Threat: [%s]", string.rep( "", ThreatLevelA2G ), string.rep( "", 10-ThreatLevelA2G ) ) )
Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) )
Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) )
return Report
end
return nil
end
--- Report detailed of a detection result.
-- @param #DETECTION_ZONES self
-- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for.
-- @return #string
function DETECTION_ZONES:DetectedReportDetailed( AttackGroup ) --R2.1 Fixed missing report
self:F()
local Report = REPORT:New()
for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do
local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem
local ReportSummary = self:DetectedItemReportSummary( DetectedItem, AttackGroup )
Report:SetTitle( "Detected areas:" )
Report:Add( ReportSummary:Text() )
end
local ReportText = Report:Text()
return ReportText
end
--- Calculate the optimal intercept point of the DetectedItem.
-- @param #DETECTION_ZONES self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
function DETECTION_ZONES:CalculateIntercept( DetectedItem )
local DetectedCoord = DetectedItem.Coordinate
-- local DetectedSpeed = DetectedCoord:GetVelocity()
-- local DetectedHeading = DetectedCoord:GetHeading()
--
-- if self.Intercept then
-- local DetectedSet = DetectedItem.Set
-- -- todo: speed
--
-- local TranslateDistance = DetectedSpeed * self.InterceptDelay
--
-- local InterceptCoord = DetectedCoord:Translate( TranslateDistance, DetectedHeading )
--
-- DetectedItem.InterceptCoord = InterceptCoord
-- else
-- DetectedItem.InterceptCoord = DetectedCoord
-- end
DetectedItem.InterceptCoord = DetectedCoord
end
--- Smoke the detected units
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:SmokeDetectedUnits()
self:F2()
self._SmokeDetectedUnits = true
return self
end
--- Flare the detected units
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:FlareDetectedUnits()
self:F2()
self._FlareDetectedUnits = true
return self
end
--- Smoke the detected zones
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:SmokeDetectedZones()
self:F2()
self._SmokeDetectedZones = true
return self
end
--- Flare the detected zones
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:FlareDetectedZones()
self:F2()
self._FlareDetectedZones = true
return self
end
--- Bound the detected zones
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:BoundDetectedZones()
self:F2()
self._BoundDetectedZones = true
return self
end
--- Make text documenting the changes of the detected zone.
-- @param #DETECTION_ZONES self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #string The Changes text
function DETECTION_ZONES:GetChangeText( DetectedItem )
self:F( DetectedItem )
local MT = {}
for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do
if ChangeCode == "AA" then
MT[#MT+1] = "Detected new area " .. ChangeData.ID .. ". The center target is a " .. ChangeData.ItemUnitType .. "."
end
if ChangeCode == "RAU" then
MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". Removed the center target."
end
if ChangeCode == "AAU" then
MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "."
end
if ChangeCode == "RA" then
MT[#MT+1] = "Removed old area " .. ChangeData.ID .. ". No more targets in this area."
end
if ChangeCode == "AU" then
local MTUT = {}
for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do
if ChangeUnitType ~= "ID" then
MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType
end
end
MT[#MT+1] = "Detected for area " .. ChangeData.ID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "."
end
if ChangeCode == "RU" then
local MTUT = {}
for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do
if ChangeUnitType ~= "ID" then
MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType
end
end
MT[#MT+1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "."
end
end
return table.concat( MT, "\n" )
end
--- Make a DetectionSet table. This function will be overridden in the derived clsses.
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES self
function DETECTION_ZONES:CreateDetectionItems()
self:F( "Checking Detected Items for new Detected Units ..." )
local DetectedUnits = SET_UNIT:New()
-- First go through all zones, and check if there are new Zones.
-- New Zones become a new DetectedItem.
for ZoneName, DetectionZone in pairs( self.DetectionSetZone:GetSet() ) do
local DetectedItem = self:GetDetectedItemByKey( ZoneName )
if DetectedItem == nil then
DetectedItem = self:AddDetectedItemZone( "ZONE", ZoneName, nil, DetectionZone )
end
local DetectedItemSetUnit = self:GetDetectedItemSet( DetectedItem )
-- Scan the zone
DetectionZone:Scan( { Object.Category.UNIT }, { Unit.Category.GROUND_UNIT } )
-- For all the units in the zone,
-- check if they are of the same coalition to be included.
local ZoneUnits = DetectionZone:GetScannedUnits()
for DCSUnitID, DCSUnit in pairs( ZoneUnits ) do
local UnitName = DCSUnit:getName()
local ZoneUnit = UNIT:FindByName( UnitName )
local ZoneUnitCoalition = ZoneUnit:GetCoalition()
if ZoneUnitCoalition == self.DetectionCoalition then
if DetectedItemSetUnit:FindUnit( UnitName ) == nil and DetectedUnits:FindUnit( UnitName ) == nil then
self:F( "Adding " .. UnitName )
DetectedItemSetUnit:AddUnit( ZoneUnit )
DetectedUnits:AddUnit( ZoneUnit )
end
end
end
end
-- Now all the tests should have been build, now make some smoke and flares...
-- We also report here the friendlies within the detected areas.
for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do
local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
local DetectedFirstUnit = DetectedSet:GetFirst()
local DetectedZone = self:GetDetectedItemZone( DetectedItem )
-- Set the last known coordinate to the detection item.
local DetectedZoneCoord = DetectedZone:GetCoordinate()
self:SetDetectedItemCoordinate( DetectedItem, DetectedZoneCoord, DetectedFirstUnit )
self:CalculateIntercept( DetectedItem )
-- We search for friendlies nearby.
-- If there weren't any friendlies nearby, and now there are friendlies nearby, we flag the area as "changed".
-- If there were friendlies nearby, and now there aren't any friendlies nearby, we flag the area as "changed".
-- This is for the A2G dispatcher to detect if there is a change in the tactical situation.
local OldFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table
local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
if OldFriendliesNearbyGround ~= NewFriendliesNearbyGround then
DetectedItem.Changed = true
end
self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level
--self:NearestRecce( DetectedItem )
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedZone:SmokeZone( SMOKECOLOR.Red, 30 )
end
--DetectedSet:Flush( self )
DetectedSet:ForEachUnit(
--- @param Wrapper.Unit#UNIT DetectedUnit
function( DetectedUnit )
if DetectedUnit:IsAlive() then
--self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() )
if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then
DetectedUnit:FlareGreen()
end
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedUnit:SmokeGreen()
end
end
end
)
if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then
DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) )
end
if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then
DetectedZone:SmokeZone( SMOKECOLOR.White, 30 )
end
if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then
self.CountryID = DetectedSet:GetFirst():GetCountry()
DetectedZone:BoundZone( 12, self.CountryID )
end
end
end
--- @param #DETECTION_ZONES self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Detection The element on which the detection is based.
-- @param #number DetectionTimeStamp Time stamp of detection event.
function DETECTION_ZONES:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
self.DetectionRun = self.DetectionRun + 1
if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then
self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method.
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
self:UpdateDetectedItemDetection( DetectedItem )
self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list.
if DetectedItem then
self:__DetectedItem( 0.1, DetectedItem )
end
end
self:__Detect( -self.RefreshTimeInterval )
end
end
--- Set IsDetected flag for the DetectedItem, which can have more units.
-- @param #DETECTION_ZONES self
-- @return #DETECTION_ZONES.DetectedItem DetectedItem
-- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet.
function DETECTION_ZONES:UpdateDetectedItemDetection( DetectedItem )
local IsDetected = true
DetectedItem.IsDetected = true
return IsDetected
end
end

View File

@@ -872,7 +872,7 @@ function ESCORT:_AttackTarget( DetectedItem )
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetState( EscortGroup, "Escort", self )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
local Tasks = {}
@@ -895,7 +895,7 @@ function ESCORT:_AttackTarget( DetectedItem )
else
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
local Tasks = {}
@@ -934,7 +934,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
EscortGroupAttack:OptionROEOpenFire()
EscortGroupAttack:OptionROTVertical()
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
local Tasks = {}
@@ -956,7 +956,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
)
else
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
local Tasks = {}

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,7 @@
-- @field #PSEUDOATC
PSEUDOATC={
ClassName = "PSEUDOATC",
player={},
group={},
Debug=false,
mdur=30,
mrefresh=120,
@@ -98,7 +98,7 @@ PSEUDOATC.id="PseudoATC | "
--- PSEUDOATC version.
-- @field #number version
PSEUDOATC.version="0.9.1"
PSEUDOATC.version="0.9.2"
-----------------------------------------------------------------------------------------------------------------------------------------
@@ -383,16 +383,23 @@ function PSEUDOATC:PlayerEntered(unit)
local PlayerName=unit:GetPlayerName()
local UnitName=unit:GetName()
local CallSign=unit:GetCallsign()
local UID=unit:GetDCSObject():getID()
if not self.group[GID] then
self.group[GID]={}
self.group[GID].player={}
end
-- Init player table.
self.player[GID]={}
self.player[GID].group=group
self.player[GID].unit=unit
self.player[GID].groupname=GroupName
self.player[GID].unitname=UnitName
self.player[GID].playername=PlayerName
self.player[GID].callsign=CallSign
self.player[GID].waypoints=group:GetTaskRoute()
self.group[GID].player[UID]={}
self.group[GID].player[UID].group=group
self.group[GID].player[UID].unit=unit
self.group[GID].player[UID].groupname=GroupName
self.group[GID].player[UID].unitname=UnitName
self.group[GID].player[UID].playername=PlayerName
self.group[GID].player[UID].callsign=CallSign
self.group[GID].player[UID].waypoints=group:GetTaskRoute()
-- Info message.
local text=string.format("Player %s entered unit %s of group %s (id=%d).", PlayerName, UnitName, GroupName, GID)
@@ -400,19 +407,26 @@ function PSEUDOATC:PlayerEntered(unit)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
-- Create main F10 menu, i.e. "F10/Pseudo ATC"
self.player[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC")
local countPlayerInGroup = 0
for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end
if countPlayerInGroup <= 1 then
self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC")
end
-- Create/update custom menu for player
self:MenuCreatePlayer(GID,UID)
-- Create/update list of nearby airports.
self:LocalAirports(GID)
self:LocalAirports(GID,UID)
-- Create submenu of local airports.
self:MenuAirports(GID)
self:MenuAirports(GID,UID)
-- Create submenu Waypoints.
self:MenuWaypoints(GID)
self:MenuWaypoints(GID,UID)
-- Start scheduler to refresh the F10 menues.
self.player[GID].scheduler, self.player[GID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID}, self.mrefresh, self.mrefresh)
self.group[GID].player[UID].scheduler, self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID, UID}, self.mrefresh, self.mrefresh)
end
@@ -425,24 +439,23 @@ function PSEUDOATC:PlayerLanded(unit, place)
-- Gather some information.
local group=unit:GetGroup()
local id=group:GetID()
local PlayerName=self.player[id].playername
local Callsign=self.player[id].callsign
local UnitName=self.player[id].unitname
local GroupName=self.player[id].groupname
local CallSign=self.player[id].callsign
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, id, place)
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
-- Stop altitude reporting timer if its activated.
self:AltitudeTimerStop(id)
self:AltitudeTimerStop(GID,UID)
-- Welcome message.
if place and self.chatty then
local text=string.format("Touchdown! Welcome to %s. Have a nice day!", place)
local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!", place,PlayerName)
MESSAGE:New(text, self.mdur):ToGroup(group)
end
@@ -457,15 +470,15 @@ function PSEUDOATC:PlayerTakeOff(unit, place)
-- Gather some information.
local group=unit:GetGroup()
local id=group:GetID()
local PlayerName=self.player[id].playername
local Callsign=self.player[id].callsign
local UnitName=self.player[id].unitname
local GroupName=self.player[id].groupname
local CallSign=self.player[id].callsign
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, id, place)
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
@@ -485,30 +498,44 @@ function PSEUDOATC:PlayerLeft(unit)
-- Get id.
local group=unit:GetGroup()
local id=group:GetID()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
if self.player[id] then
if self.group[GID].player[UID] then
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", self.player[id].playername, self.player[id].callsign, self.player[id].groupname, self.player[id].unitname)
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", PlayerName, CallSign, GroupName, UnitName)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
-- Stop scheduler for menu updates
if self.player[id].schedulerid then
self.player[id].scheduler:Stop(self.player[id].schedulerid)
if self.group[GID].player[UID].schedulerid then
self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid)
end
-- Stop scheduler for reporting alt if it runs.
self:AltitudeTimerStop(id)
self:AltitudeTimerStop(GID,UID)
-- Remove own menu.
if self.group[GID].player[UID].menu_own then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own)
end
-- Remove main menu.
if self.player[id].menu_main then
missionCommands.removeItem(self.player[id].menu_main)
-- WARNING: Remove only if last human element of group
local countPlayerInGroup = 0
for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end
if self.group[GID].menu_main and countPlayerInGroup==1 then
missionCommands.removeItemForGroup(GID,self.group[GID].menu_main)
end
-- Remove player array.
self.player[id]=nil
self.group[GID].player[UID]=nil
end
end
@@ -518,80 +545,94 @@ end
--- Refreshes all player menues.
-- @param #PSEUDOATC self.
-- @param #number id Group id of player unit.
function PSEUDOATC:MenuRefresh(id)
self:F({id=id})
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:MenuRefresh(GID,UID)
self:F({GID=GID,UID=UID})
-- Debug message.
local text=string.format("Refreshing menues for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
local text=string.format("Refreshing menues for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
-- Clear menu.
self:MenuClear(id)
self:MenuClear(GID,UID)
-- Create list of nearby airports.
self:LocalAirports(id)
self:LocalAirports(GID,UID)
-- Create submenu Local Airports.
self:MenuAirports(id)
self:MenuAirports(GID,UID)
-- Create submenu Waypoints etc.
self:MenuWaypoints(id)
self:MenuWaypoints(GID,UID)
end
--- Create player menus.
-- @param #PSEUDOATC self.
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:MenuCreatePlayer(GID,UID)
self:F({GID=GID,UID=UID})
-- Table for menu entries.
local PlayerName=self.group[GID].player[UID].playername
self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID, PlayerName, self.group[GID].menu_main)
end
--- Clear player menus.
-- @param #PSEUDOATC self.
-- @param #number id Group id of player unit.
function PSEUDOATC:MenuClear(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:MenuClear(GID,UID)
self:F({GID=GID,UID=UID})
-- Debug message.
local text=string.format("Clearing menus for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
local text=string.format("Clearing menus for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
-- Delete Airports menu.
if self.player[id].menu_airports then
missionCommands.removeItemForGroup(id, self.player[id].menu_airports)
self.player[id].menu_airports=nil
if self.group[GID].player[UID].menu_airports then
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_airports)
self.group[GID].player[UID].menu_airports=nil
else
self:T2(PSEUDOATC.id.."No airports to clear menus.")
end
-- Delete waypoints menu.
if self.player[id].menu_waypoints then
missionCommands.removeItemForGroup(id, self.player[id].menu_waypoints)
self.player[id].menu_waypoints=nil
if self.group[GID].player[UID].menu_waypoints then
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_waypoints)
self.group[GID].player[UID].menu_waypoints=nil
end
-- Delete report alt until touchdown menu command.
if self.player[id].menu_reportalt then
missionCommands.removeItemForGroup(id, self.player[id].menu_reportalt)
self.player[id].menu_reportalt=nil
if self.group[GID].player[UID].menu_reportalt then
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_reportalt)
self.group[GID].player[UID].menu_reportalt=nil
end
-- Delete request current alt menu command.
if self.player[id].menu_requestalt then
missionCommands.removeItemForGroup(id, self.player[id].menu_requestalt)
self.player[id].menu_requestalt=nil
if self.group[GID].player[UID].menu_requestalt then
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_requestalt)
self.group[GID].player[UID].menu_requestalt=nil
end
end
--- Create "F10/Pseudo ATC/Local Airports/Airport Name/" menu items each containing weather report and BR request.
-- @param #PSEUDOATC self
-- @param #number id Group id of player unit for which menues are created.
function PSEUDOATC:MenuAirports(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:MenuAirports(GID,UID)
self:F({GID=GID,UID=UID})
-- Table for menu entries.
self.player[id].menu_airports=missionCommands.addSubMenuForGroup(id, "Local Airports", self.player[id].menu_main)
self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID, "Local Airports", self.group[GID].player[UID].menu_own)
local i=0
for _,airport in pairs(self.player[id].airports) do
for _,airport in pairs(self.group[GID].player[UID].airports) do
i=i+1
if i > 10 then
@@ -603,37 +644,38 @@ function PSEUDOATC:MenuAirports(id)
local pos=AIRBASE:FindByName(name):GetCoordinate()
--F10menu_ATC_airports[ID][name] = missionCommands.addSubMenuForGroup(ID, name, F10menu_ATC)
local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_airports)
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_airports)
-- Create menu reporting commands
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name)
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name)
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
-- Debug message.
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, id))
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
end
end
--- Create "F10/Pseudo ATC/Waypoints/<Waypoint i> menu items.
-- @param #PSEUDOATC self
-- @param #number id Group id of player unit for which menues are created.
function PSEUDOATC:MenuWaypoints(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:MenuWaypoints(GID, UID)
self:F({GID=GID, UID=UID})
-- Player unit and callsign.
local unit=self.player[id].unit --Wrapper.Unit#UNIT
local callsign=self.player[id].callsign
-- local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
local callsign=self.group[GID].player[UID].callsign
-- Debug info.
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, id))
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, GID))
if #self.player[id].waypoints>0 then
if #self.group[GID].player[UID].waypoints>0 then
-- F10/PseudoATC/Waypoints
self.player[id].menu_waypoints=missionCommands.addSubMenuForGroup(id, "Waypoints", self.player[id].menu_main)
self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID, "Waypoints", self.group[GID].player[UID].menu_own)
local j=0
for i, wp in pairs(self.player[id].waypoints) do
for i, wp in pairs(self.group[GID].player[UID].waypoints) do
-- Increase counter
j=j+1
@@ -647,16 +689,16 @@ function PSEUDOATC:MenuWaypoints(id)
local name=string.format("Waypoint %d", i-1)
-- "F10/PseudoATC/Waypoints/Waypoint X"
local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_waypoints)
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints)
-- Menu commands for each waypoint "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X/<Commands>"
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name)
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name)
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
end
end
self.player[id].menu_reportalt = missionCommands.addCommandForGroup(id, "Talk me down", self.player[id].menu_main, self.AltidudeTimerToggle, self, id)
self.player[id].menu_requestalt = missionCommands.addCommandForGroup(id, "Request altitude", self.player[id].menu_main, self.ReportHeight, self, id)
self.group[GID].player[UID].menu_reportalt = missionCommands.addCommandForGroup(GID, "Talk me down", self.group[GID].player[UID].menu_own, self.AltidudeTimerToggle, self, GID, UID)
self.group[GID].player[UID].menu_requestalt = missionCommands.addCommandForGroup(GID, "Request altitude", self.group[GID].player[UID].menu_own, self.ReportHeight, self, GID, UID)
end
-----------------------------------------------------------------------------------------------------------------------------------------
@@ -664,14 +706,15 @@ end
--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location.
-- @param #PSEUDOATC self
-- @param #number id Group id to which the report is delivered.
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
-- @param #string location Name of the location at which the pressure is measured.
function PSEUDOATC:ReportWeather(id, position, location)
self:F({id=id, position=position, location=location})
function PSEUDOATC:ReportWeather(GID, UID, position, location)
self:F({GID=GID, UID=UID, position=position, location=location})
-- Player unit system settings.
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
local text=string.format("Local weather at %s:\n", location)
@@ -723,23 +766,24 @@ function PSEUDOATC:ReportWeather(id, position, location)
end
-- Message text.
local text=text..string.format("Wind from %s at %s (%s).", Ds, Vs, Bd)
local text=text..string.format("%s, Wind from %s at %s (%s).", self.group[GID].player[UID].playername, Ds, Vs, Bd)
-- Send message
self:_DisplayMessageToGroup(self.player[id].unit, text, self.mdur, true)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true)
end
--- Report absolute bearing and range form player unit to airport.
-- @param #PSEUDOATC self
-- @param #number id Group id to the report is delivered.
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
-- @param #string location Name of the location at which the pressure is measured.
function PSEUDOATC:ReportBR(id, position, location)
self:F({id=id, position=position, location=location})
function PSEUDOATC:ReportBR(GID, UID, position, location)
self:F({GID=GID, UID=UID, position=position, location=location})
-- Current coordinates.
local unit=self.player[id].unit --Wrapper.Unit#UNIT
local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
local coord=unit:GetCoordinate()
-- Direction vector from current position (coord) to target (position).
@@ -752,7 +796,7 @@ function PSEUDOATC:ReportBR(id, position, location)
local Bs=string.format('%03d°', angle)
-- Settings.
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
local Rs=string.format("%.1f NM", UTILS.MetersToNM(range))
@@ -763,18 +807,20 @@ function PSEUDOATC:ReportBR(id, position, location)
-- Message text.
local text=string.format("%s: Bearing %s, Range %s.", location, Bs, Rs)
-- Send message to player group.
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
-- Send message
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true)
end
--- Report altitude above ground level of player unit.
-- @param #PSEUDOATC self
-- @param #number id Group id to the report is delivered.
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
-- @param #number dt (Optional) Duration the message is displayed.
-- @param #boolean _clear (Optional) Clear previouse messages.
-- @return #number Altitude above ground.
function PSEUDOATC:ReportHeight(id, dt, _clear)
self:F({id=id, dt=dt})
function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
self:F({GID=GID, UID=UID, dt=dt})
local dt = dt or self.mdur
if _clear==nil then
@@ -791,7 +837,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
end
-- Get height AGL.
local unit=self.player[id].unit --Wrapper.Unit#UNIT
local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
@@ -800,7 +846,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
local callsign=unit:GetCallsign()
-- Settings.
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
-- Height string.
local Hs=string.format("%d ft", UTILS.MetersToFeet(height))
@@ -817,7 +863,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
end
-- Send message to player group.
self:_DisplayMessageToGroup(self.player[id].unit,_text, dt,_clear)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text, dt,_clear)
-- Return height
return height
@@ -830,47 +876,50 @@ end
--- Toggle report altitude reporting on/off.
-- @param #PSEUDOATC self.
-- @param #number id Group id of player unit.
function PSEUDOATC:AltidudeTimerToggle(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:AltidudeTimerToggle(GID,UID)
self:F({GID=GID, UID=UID})
if self.player[id].altimerid then
if self.group[GID].player[UID].altimerid then
-- If the timer is on, we turn it off.
self:AltitudeTimerStop(id)
self:AltitudeTimerStop(GID, UID)
else
-- If the timer is off, we turn it on.
self:AltitudeTimeStart(id)
self:AltitudeTimeStart(GID, UID)
end
end
--- Start altitude reporting scheduler.
-- @param #PSEUDOATC self.
-- @param #number id Group id of player unit.
function PSEUDOATC:AltitudeTimeStart(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:AltitudeTimeStart(GID, UID)
self:F({GID=GID, UID=UID})
-- Debug info.
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", id))
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", UID))
-- Start timer. Altitude is reported every ~3 seconds.
self.player[id].altimer, self.player[id].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, id, 0.1, true}, 1, 3)
self.group[GID].player[UID].altimer, self.group[GID].player[UID].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, GID, UID, 0.1, true}, 1, 3)
end
--- Stop/destroy DCS scheduler function for reporting altitude.
-- @param #PSEUDOATC self.
-- @param #number id Group id of player unit.
function PSEUDOATC:AltitudeTimerStop(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:AltitudeTimerStop(GID, UID)
self:F({GID=GID,UID=UID})
-- Debug info.
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", id))
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", UID))
-- Stop timer.
if self.player[id].altimerid then
self.player[id].altimer:Stop(self.player[id].altimerid)
if self.group[GID].player[UID].altimerid then
self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid)
end
self.player[id].altimer=nil
self.player[id].altimerid=nil
self.group[GID].player[UID].altimer=nil
self.group[GID].player[UID].altimerid=nil
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -878,16 +927,17 @@ end
--- Create list of nearby airports sorted by distance to player unit.
-- @param #PSEUDOATC self
-- @param #number id Group id of player unit.
function PSEUDOATC:LocalAirports(id)
self:F(id)
-- @param #number GID Group id of player unit.
-- @param #number UID Unit id of player.
function PSEUDOATC:LocalAirports(GID, UID)
self:F({GID=GID, UID=UID})
-- Airports table.
self.player[id].airports=nil
self.player[id].airports={}
self.group[GID].player[UID].airports=nil
self.group[GID].player[UID].airports={}
-- Current player position.
local pos=self.player[id].unit:GetCoordinate()
local pos=self.group[GID].player[UID].unit:GetCoordinate()
-- Loop over coalitions.
for i=0,2 do
@@ -903,7 +953,7 @@ function PSEUDOATC:LocalAirports(id)
local d=q:Get2DDistance(pos)
-- Add to table.
table.insert(self.player[id].airports, {distance=d, name=name})
table.insert(self.group[GID].player[UID].airports, {distance=d, name=name})
end
end
@@ -914,7 +964,7 @@ function PSEUDOATC:LocalAirports(id)
end
-- Sort airports table w.r.t. distance to player.
table.sort(self.player[id].airports, compare)
table.sort(self.group[GID].player[UID].airports, compare)
end
@@ -992,3 +1042,5 @@ function PSEUDOATC:_myname(unitname)
return string.format("%s (%s)", csign, pname)
end

View File

@@ -150,6 +150,8 @@
-- @field #number parkingscanradius Radius in meters until which parking spots are scanned for obstacles like other units, statics or scenery.
-- @field #boolean parkingscanscenery If true, area around parking spots is scanned for scenery objects. Default is false.
-- @field #boolean parkingverysafe If true, parking spots are considered as non-free until a possible aircraft has left and taken off. Default false.
-- @field #boolean despawnair If true, aircraft are despawned when they reach their destination zone. Default.
-- @field #boolean eplrs If true, turn on EPLSR datalink for the RAT group.
-- @extends Core.Spawn#SPAWN
--- Implements an easy to use way to randomly fill your map with AI aircraft.
@@ -240,7 +242,7 @@
-- * AIRBASE.TerminalType.OpenMed: Open/Shelter air airplane only.
-- * AIRBASE.TerminalType.OpenBig: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there.
-- * AIRBASE.TerminalType.OpenMedOrBig: Combines OpenMed and OpenBig spots.
-- * AIRBASE.TerminalType.HelicopterUnsable: Combines HelicopterOnly, OpenMed and OpenBig.
-- * AIRBASE.TerminalType.HelicopterUsable: Combines HelicopterOnly, OpenMed and OpenBig.
-- * AIRBASE.TerminalType.FighterAircraft: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
--
-- So for example
@@ -428,6 +430,8 @@ RAT={
parkingscanradius=40, -- Scan radius.
parkingscanscenery=false, -- Scan parking spots for scenery obstacles.
parkingverysafe=false, -- Very safe option.
despawnair=true,
eplrs=false,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -546,7 +550,7 @@ RAT.id="RAT | "
--- RAT version.
-- @list version
RAT.version={
version = "2.3.4",
version = "2.3.9",
print = true,
}
@@ -717,6 +721,11 @@ function RAT:Spawn(naircraft)
self.FLcruise=005*RAT.unit.FL2m
end
end
-- Enable helos to go to destinations 100 meters away.
if self.category==RAT.cat.heli then
self.mindist=50
end
-- Run consistency checks.
self:_CheckConsistency()
@@ -1093,6 +1102,14 @@ function RAT:SetParkingSpotSafeOFF()
return self
end
--- Aircraft that reach their destination zone are not despawned. They will probably go the the nearest airbase and try to land.
-- @param #RAT self
-- @return #RAT RAT self object.
function RAT:SetDespawnAirOFF()
self.despawnair=false
return self
end
--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air.
-- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines.
-- @param #RAT self
@@ -1627,6 +1644,19 @@ function RAT:Invisible()
return self
end
--- Turn EPLRS datalink on/off.
-- @param #RAT self
-- @param #boolean switch If true (or nil), turn EPLRS on.
-- @return #RAT RAT self object.
function RAT:SetEPLRS(switch)
if switch==nil or switch==true then
self.eplrs=true
else
self.eplrs=false
end
return self
end
--- Aircraft are immortal.
-- @param #RAT self
-- @return #RAT RAT self object.
@@ -1812,14 +1842,14 @@ function RAT:ATC_Delay(time)
end
--- Set minimum distance between departure and destination. Default is 5 km.
-- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different.
-- Minimum distance should not be smaller than maybe ~100 meters to ensure that departure and destination are different.
-- @param #RAT self
-- @param #number dist Distance in km.
-- @return #RAT RAT self object.
function RAT:SetMinDistance(dist)
self:F2(dist)
-- Distance in meters. Absolute minimum is 500 m.
self.mindist=math.max(500, dist*1000)
self.mindist=math.max(100, dist*1000)
return self
end
@@ -2149,6 +2179,11 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live
self:_CommandImmortal(group, true)
end
-- Set group to be immortal.
if self.eplrs then
group:CommandEPLRS(true, 1)
end
-- Set ROE, default is "weapon hold".
self:_SetROE(group, self.roe)
@@ -2446,7 +2481,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
local VxCruiseMax
if self.Vcruisemax then
-- User input.
VxCruiseMax = min(self.Vcruisemax, self.aircraft.Vmax)
VxCruiseMax = math.min(self.Vcruisemax, self.aircraft.Vmax)
else
-- Max cruise speed 90% of Vmax or 900 km/h whichever is lower.
VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
@@ -3357,11 +3392,19 @@ function RAT:_GetAirportsOfMap()
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
-- Add airport to table.
table.insert(self.airports_map, _myab)
if _myab then
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
self:T(RAT.id..text)
-- Add airport to table.
table.insert(self.airports_map, _myab)
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
self:T(RAT.id..text)
else
self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!", tostring(_name)))
end
end
end
@@ -3373,7 +3416,7 @@ function RAT:_GetAirportsOfCoalition()
for _,coalition in pairs(self.ctable) do
for _,_airport in pairs(self.airports_map) do
local airport=_airport --Wrapper.Airbase#AIRBASE
local category=airport:GetDesc().category
local category=airport:GetAirbaseCategory()
if airport:GetCoalition()==coalition then
-- Planes cannot land on FARPs.
--local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP"
@@ -3599,13 +3642,18 @@ function RAT:Status(message, forID)
local text=string.format("Flight %s will be despawned NOW!", self.alias)
self:T(RAT.id..text)
-- Despawn old group.
-- Respawn group
if (not self.norespawn) and (not self.respawn_after_takeoff) then
local idx=self:GetSpawnIndexFromGroup(group)
local coord=group:GetCoordinate()
self:_Respawn(idx, coord, 0)
end
self:_Despawn(group, 0)
-- Despawn old group.
if self.despawnair then
self:_Despawn(group, 0)
end
end
@@ -3799,7 +3847,7 @@ function RAT:_OnBirth(EventData)
-- Check if any unit of the group was spawned on top of another unit in the MOOSE data base.
local ontop=false
if self.checkontop and (_airbase and _airbase:GetDesc().category==Airbase.Category.AIRDROME) then
if self.checkontop and (_airbase and _airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME) then
ontop=self:_CheckOnTop(SpawnGroup, self.ontopradius)
end
@@ -4409,7 +4457,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport
if (Airport~=nil) and (Type~=RAT.wp.air) then
local AirbaseID = Airport:GetID()
local AirbaseCategory = Airport:GetDesc().category
local AirbaseCategory = Airport:GetAirbaseCategory()
if AirbaseCategory == Airbase.Category.SHIP then
RoutePoint.linkUnit = AirbaseID
RoutePoint.helipadId = AirbaseID
@@ -5093,7 +5141,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
local AirbaseCategory = departure:GetDesc().category
local AirbaseCategory = departure:GetAirbaseCategory()
if AirbaseCategory == Airbase.Category.SHIP then
spawnonship=true
elseif AirbaseCategory == Airbase.Category.HELIPAD then
@@ -5125,6 +5173,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
if self.uncontrolled then
-- This is used in the SPAWN:SpawnWithIndex() function. Some values are overwritten there!
self.SpawnUnControlled=true
SpawnTemplate.uncontrolled=true
end
-- Number of units in the group. With grouping this can actually differ from the template group size!
@@ -5435,7 +5484,7 @@ function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then
local text
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay
self:T(RAT.id..text)
BASE:T(RAT.id..text)
RAT.ATC.init=true
for _,ap in pairs(airports_map) do
local name=ap:GetName()
@@ -5458,7 +5507,7 @@ end
-- @param #string name Group name of the flight.
-- @param #string dest Name of the destination airport.
function RAT:_ATCAddFlight(name, dest)
self:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
RAT.ATC.flight[name]={}
RAT.ATC.flight[name].destination=dest
RAT.ATC.flight[name].Tarrive=-1
@@ -5483,7 +5532,7 @@ end
-- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered.
function RAT:_ATCRegisterFlight(name, time)
self:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
BASE:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0
end
@@ -5514,7 +5563,7 @@ 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)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then
@@ -5522,7 +5571,7 @@ function RAT:_ATCStatus()
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then
@@ -5530,7 +5579,7 @@ function RAT:_ATCStatus()
--self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold))
else
self:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
end
end
@@ -5572,12 +5621,12 @@ 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.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(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.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
-- Clear flight for landing.
RAT:_ATCClearForLanding(name, flight)
@@ -5705,12 +5754,7 @@ function RAT:_ATCQueue()
for k,v in ipairs(_queue) do
table.insert(RAT.ATC.airport[airport].queue, v[1])
end
--fvh
--for k,v in ipairs(RAT.ATC.airport[airport].queue) do
--print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding))
--end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1635,7 +1635,7 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup )
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",

View File

@@ -57,8 +57,10 @@ SEAD = {
-- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes )
local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes )
self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@@ -85,7 +87,29 @@ function SEAD:OnEventShot( EventData )
local SEADWeaponName = EventData.WeaponName -- return weapon type
-- Start of the 2nd loop
self:T( "Missile Launched = " .. SEADWeaponName )
if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
--if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
then
local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = EventData.Weapon:getTarget() -- Identify target
local _targetMimname = Unit.getName(_targetMim)
@@ -111,47 +135,62 @@ function SEAD:OnEventShot( EventData )
self:T( _targetskill )
if self.TargetSkill[_targetskill] then
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
local _targetMim = Weapon.getTarget(SEADWeapon)
local _targetMimname = Unit.getName(_targetMim)
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
local _targetMimcont= _targetMimgroup:getController()
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
local function SuppressionEnd1(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
SuppressedGroups1[id.groupName] = nil
end
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
if SuppressedGroups1[id.groupName] == nil then
SuppressedGroups1[id.groupName] = {
SuppressionEndTime1 = timer.getTime() + delay1,
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
}
}
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
end
local SuppressedGroups = {}
local function SuppressionEnd(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
SuppressedGroups[id.groupName] = nil
end
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if SuppressedGroups[id.groupName] == nil then
SuppressedGroups[id.groupName] = {
SuppressionEndTime = timer.getTime() + delay,
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
}
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **Millertime** - Concept
-- ### Contributions: **Millertime** - Concept, **funkyfranky**
--
-- ===
--
@@ -49,6 +49,15 @@
do -- ZONE_CAPTURE_COALITION
--- @type ZONE_CAPTURE_COALITION
-- @field #string ClassName Name of the class.
-- @field #number MarkBlue ID of blue F10 mark.
-- @field #number MarkRed ID of red F10 mark.
-- @field #number StartInterval Time in seconds after the status monitor is started.
-- @field #number RepeatInterval Time in seconds after which the zone status is updated.
-- @field #boolean HitsOn If true, hit events are monitored and trigger the "Attack" event when a defending unit is hit.
-- @field #number HitTimeLast Time stamp in seconds when the last unit inside the zone was hit.
-- @field #number HitTimeAttackOver Time interval in seconds before the zone goes from "Attacked" to "Guarded" state after the last hit.
-- @field #boolean MarkOn If true, create marks of zone status on F10 map.
-- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION
@@ -197,8 +206,7 @@ do -- ZONE_CAPTURE_COALITION
--
-- ### IMPORTANT
--
-- **Each capture zone object must have the monitoring process started specifically.
-- The monitoring process is NOT started by default!!!**
-- **Each capture zone object must have the monitoring process started specifically. The monitoring process is NOT started by default!**
--
--
-- # Full Example
@@ -338,29 +346,48 @@ do -- ZONE_CAPTURE_COALITION
--
-- @field #ZONE_CAPTURE_COALITION
ZONE_CAPTURE_COALITION = {
ClassName = "ZONE_CAPTURE_COALITION",
ClassName = "ZONE_CAPTURE_COALITION",
MarkBlue = nil,
MarkRed = nil,
StartInterval = nil,
RepeatInterval = nil,
HitsOn = nil,
HitTimeLast = nil,
HitTimeAttackOver = nil,
MarkOn = nil,
}
--- @field #table ZONE_CAPTURE_COALITION.States
ZONE_CAPTURE_COALITION.States = {}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor and Start/Stop Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- ZONE_CAPTURE_COALITION Constructor.
-- @param #ZONE_CAPTURE_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
-- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS.
-- @return #ZONE_CAPTURE_COALITION
-- @usage
--
-- AttackZone = ZONE:New( "AttackZone" )
--
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED, {UNITS ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
-- ZoneCaptureCoalition:__Guard( 1 ) -- Start the Guarding of the AttackZone.
--
function ZONE_CAPTURE_COALITION:New( Zone, Coalition )
function ZONE_CAPTURE_COALITION:New( Zone, Coalition, UnitCategories, ObjectCategories )
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition ) ) -- #ZONE_CAPTURE_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories ) ) -- #ZONE_CAPTURE_COALITION
self:F( { Zone = Zone, Coalition = Coalition, UnitCategories = UnitCategories, ObjectCategories = ObjectCategories } )
self:SetObjectCategories(ObjectCategories)
-- Default is no smoke.
self:SetSmokeZone(false)
-- Default is F10 marks ON.
self:SetMarkZone(true)
-- Start in state "Empty".
self:SetStartState("Empty")
do
@@ -545,174 +572,12 @@ do -- ZONE_CAPTURE_COALITION
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
-- ZoneGoal objects are added to the _DATABASE.ZONES_GOAL and SET_ZONE_GOAL sets.
_EVENTDISPATCHER:CreateEventNewZoneGoal(self)
return self
end
--- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:GetParent( self, ZONE_CAPTURE_COALITION ).onenterCaptured( self )
self.Goal:Achieved()
end
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
self:F( { IsGuarded = IsGuarded } )
return IsGuarded
end
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone()
self:F( { IsEmpty = IsEmpty } )
return IsEmpty
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Mark.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark()
local Coord = self.Zone:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:F( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterGuarded()
--self:GetParent( self ):onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterCaptured()
--self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetScannedCoalition()
self:F( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterEmpty()
--self:GetParent( self ):onenterEmpty()
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterAttacked()
--self:GetParent( self ):onenterAttacked()
self:Mark()
end
--- When started, check the Coalition status.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onafterGuard()
--self:F({BASE:GetParent( self )})
--BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:StatusZone()
local State = self:GetState()
self:F( { State = self:GetState() } )
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
if State ~= "Guarded" and self:IsGuarded() then
self:Guard()
end
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
end
--- Starts the zone capturing monitoring process.
-- This process can be CPU intensive, ensure that you specify reasonable time intervals for the monitoring process.
@@ -723,6 +588,7 @@ do -- ZONE_CAPTURE_COALITION
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number StartInterval (optional) Specifies the start time interval in seconds when the zone state will be checked for the first time.
-- @param #number RepeatInterval (optional) Specifies the repeat time interval in seconds when the zone state will be checked repeatedly.
-- @return #ZONE_CAPTURE_COALITION self
-- @usage
--
-- -- Setup the zone.
@@ -737,13 +603,23 @@ do -- ZONE_CAPTURE_COALITION
--
function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval )
StartInterval = StartInterval or 15
RepeatInterval = RepeatInterval or 15
self.StartInterval = StartInterval or 1
self.RepeatInterval = RepeatInterval or 15
if self.ScheduleStatusZone then
self:ScheduleStop( self.ScheduleStatusZone )
end
self.ScheduleStatusZone = self:ScheduleRepeat( StartInterval, RepeatInterval, 0.1, nil, self.StatusZone, self )
-- Start Status scheduler.
self.ScheduleStatusZone = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 0.1, nil, self.StatusZone, self )
-- We check if a unit within the zone is hit. If it is, then we must move the zone to attack state.
self:HandleEvent(EVENTS.Hit, self.OnEventHit)
-- Create mark on F10 map.
self:Mark()
return self
end
@@ -785,9 +661,281 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:Stop()
if self.ScheduleStatusZone then
self:ScheduleStop( self.ScheduleStatusZone )
self:ScheduleStop(self.ScheduleStatusZone)
end
if self.SmokeScheduler then
self:ScheduleStop(self.SmokeScheduler)
end
self:UnHandleEvent(EVENTS.Hit)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set whether hit events of defending units are monitored and trigger "Attack" events.
-- @param #ZONE_CAPTURE_COALITION self
-- @param #boolean Switch If *true*, hit events are monitored. If *false* or *nil*, hit events are not monitored.
-- @param #number TimeAttackOver (Optional) Time in seconds after an attack is over after the last hit and the zone state goes to "Guarded". Default is 300 sec = 5 min.
-- @return #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch, TimeAttackOver)
self.HitsOn=Switch
self.HitTimeAttackOver=TimeAttackOver or 5*60
return self
end
--- Set whether marks on the F10 map are shown, which display the current zone status.
-- @param #ZONE_CAPTURE_COALITION self
-- @param #boolean Switch If *true* or *nil*, marks are shown. If *false*, marks are not displayed.
-- @return #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:SetMarkZone(Switch)
if Switch==nil or Switch==true then
self.MarkOn=true
else
self.MarkOn=false
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DCS Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Monitor hit events.
-- @param #ZONE_CAPTURE_COALITION self
-- @param Core.Event#EVENTDATA EventData The event data.
function ZONE_CAPTURE_COALITION:OnEventHit( EventData )
if self.HitsOn then
local UnitHit = EventData.TgtUnit
-- Check if unit is inside the capture zone and that it is of the defending coalition.
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
-- Update last hit time.
self.HitTimeLast=timer.getTime()
-- Only trigger attacked event if not already in state "Attacked".
if self:GetState()~="Attacked" then
self:F2("Hit ==> Attack")
self:Attack()
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Guard" event.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onafterGuard()
self:F2("After Guard")
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 0.1, nil, self.StatusSmoke, self )
end
end
end
--- On enter "Guarded" state.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterGuarded()
self:F2("Enter Guarded")
self:Mark()
end
--- On enter "Captured" state.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:F2("Enter Captured")
-- Get new coalition.
local NewCoalition = self:GetScannedCoalition()
self:F( { NewCoalition = NewCoalition } )
-- Set new owner of zone.
self:SetCoalition(NewCoalition)
-- Update mark.
self:Mark()
-- Goal achieved.
self.Goal:Achieved()
end
--- On enter "Empty" state.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterEmpty()
self:F2("Enter Empty")
self:Mark()
end
--- On enter "Attacked" state.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterAttacked()
self:F2("Enter Attacked")
self:Mark()
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status Check Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check if zone is "Empty".
-- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsNoneInZone()
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty = self:IsNoneInZone()
self:F( { IsEmpty = IsEmpty } )
return IsEmpty
end
--- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone.
-- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition )
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded = self:IsAllInZoneOfCoalition( self.Coalition )
self:F( { IsGuarded = IsGuarded } )
return IsGuarded
end
--- Check if zone is "Captured", i.e. another coalition took control over the zone and is the only one present.
-- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsAllInZoneOfOtherCoalition( self.Coalition )
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
--- Check if zone is "Attacked", i.e. another coaliton entered the zone.
-- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:StatusZone()
-- Get FSM state.
local State = self:GetState()
-- Scan zone in parent class ZONE_GOAL_COALITION
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
local Tnow=timer.getTime()
-- Check if zone is guarded.
if State ~= "Guarded" and self:IsGuarded() then
-- Check that there was a sufficient amount of time after the last hit before going back to "Guarded".
if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then
self:Guard()
self.HitTimeLast=nil
end
end
-- Check if zone is empty.
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
-- Check if zone is attacked.
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
-- Check if zone is captured.
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
-- Count stuff in zone.
local unitset=self:GetScannedSetUnit()
local nRed=0
local nBlue=0
for _,object in pairs(unitset:GetSet()) do
local coal=object:GetCoalition()
if object:IsAlive() then
if coal==coalition.side.RED then
nRed=nRed+1
elseif coal==coalition.side.BLUE then
nBlue=nBlue+1
end
end
end
-- Status text.
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
local NewState = self:GetState()
if NewState~=State then
text=text..string.format(" --> %s", NewState)
end
self:I(text)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Update Mark on F10 map.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark()
if self.MarkOn then
local Coord = self:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
-- Remove marks.
if self.MarkRed then
Coord:RemoveMark(self.MarkRed)
end
if self.MarkBlue then
Coord:RemoveMark(self.MarkBlue)
end
-- Create new marks for each coaliton.
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
elseif self.Coalition == coalition.side.RED then
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
end
end

View File

@@ -8,6 +8,7 @@
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky**
--
-- ===
--
@@ -17,10 +18,16 @@
do -- Zone
--- @type ZONE_GOAL
-- @extends Core.Fsm#FSM
-- @field #string ClassName Name of the class.
-- @field Core.Goal#GOAL Goal The goal object.
-- @field #number SmokeTime Time stamp in seconds when the last smoke of the zone was triggered.
-- @field Core.Scheduler#SCHEDULER SmokeScheduler Scheduler responsible for smoking the zone.
-- @field #number SmokeColor Color of the smoke.
-- @field #boolean SmokeZone If true, smoke zone.
-- @extends Core.Zone#ZONE_RADIUS
-- Models processes that have a Goal with a defined achievement involving a Zone.
--- Models processes that have a Goal with a defined achievement involving a Zone.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL constructor
@@ -39,24 +46,41 @@ do -- Zone
--
-- @field #ZONE_GOAL
ZONE_GOAL = {
ClassName = "ZONE_GOAL",
ClassName = "ZONE_GOAL",
Goal = nil,
SmokeTime = nil,
SmokeScheduler = nil,
SmokeColor = nil,
SmokeZone = nil,
}
--- ZONE_GOAL Constructor.
-- @param #ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved.
-- @param Core.Zone#ZONE_RADIUS Zone A @{Zone} object with the goal to be achieved.
-- @return #ZONE_GOAL
function ZONE_GOAL:New( Zone )
local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL
local self = BASE:Inherit( self, ZONE_RADIUS:New( Zone:GetName(), Zone:GetVec2(), Zone:GetRadius() ) ) -- #ZONE_GOAL
self:F( { Zone = Zone } )
self.Zone = Zone -- Core.Zone#ZONE_BASE
-- Goal object.
self.Goal = GOAL:New()
self.SmokeTime = nil
-- Set smoke ON.
self:SetSmokeZone(true)
self:AddTransition( "*", "DestroyedUnit", "*" )
--- DestroyedUnit event.
-- @function [parent=#ZONE_GOAL] DestroyedUnit
-- @param #ZONE_GOAL self
--- DestroyedUnit delayed event
-- @function [parent=#ZONE_GOAL] __DestroyedUnit
-- @param #ZONE_GOAL self
-- @param #number delay Delay in seconds.
--- DestroyedUnit Handler OnAfter for ZONE_GOAL
-- @function [parent=#ZONE_GOAL] OnAfterDestroyedUnit
@@ -70,52 +94,64 @@ do -- Zone
return self
end
--- Get the Zone
--- Get the Zone.
-- @param #ZONE_GOAL self
-- @return Core.Zone#ZONE_BASE
-- @return #ZONE_GOAL
function ZONE_GOAL:GetZone()
return self.Zone
return self
end
--- Get the name of the ProtectZone
--- Get the name of the Zone.
-- @param #ZONE_GOAL self
-- @return #string
function ZONE_GOAL:GetZoneName()
return self.Zone:GetName()
return self:GetName()
end
--- Smoke the center of theh zone.
--- Activate smoking of zone with the color or the current owner.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color SmokeColor
function ZONE_GOAL:Smoke( SmokeColor )
-- @param #boolean switch If *true* or *nil* activate smoke. If *false* or *nil*, no smoke.
-- @return #ZONE_GOAL
function ZONE_GOAL:SetSmokeZone(switch)
self.SmokeZone=switch
--[[
if switch==nil or switch==true then
self.SmokeZone=true
else
self.SmokeZone=false
end
]]
return self
end
--- Set the smoke color.
-- @param #ZONE_GOAL self
-- @param DCS#SMOKECOLOR.Color SmokeColor
function ZONE_GOAL:Smoke( SmokeColor )
self:F( { SmokeColor = SmokeColor} )
self.SmokeColor = SmokeColor
end
--- Flare the center of the zone.
--- Flare the zone boundary.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color FlareColor
-- @param DCS#SMOKECOLOR.Color FlareColor
function ZONE_GOAL:Flare( FlareColor )
self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) )
self:FlareZone( FlareColor, 30)
end
--- When started, check the Smoke and the Zone status.
-- @param #ZONE_GOAL self
function ZONE_GOAL:onafterGuard()
--self:GetParent( self ):onafterStart()
self:F("Guard")
--self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
-- Start smoke
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat(1, 1, 0.1, nil, self.StatusSmoke, self)
end
end
@@ -123,44 +159,54 @@ do -- Zone
--- Check status Smoke.
-- @param #ZONE_GOAL self
function ZONE_GOAL:StatusSmoke()
self:F({self.SmokeTime, self.SmokeColor})
local CurrentTime = timer.getTime()
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then
self.Zone:GetCoordinate():Smoke( self.SmokeColor )
--self.SmokeColor = nil
self.SmokeTime = CurrentTime
if self.SmokeZone then
-- Current time.
local CurrentTime = timer.getTime()
-- Restart smoke every 5 min.
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then
self:GetCoordinate():Smoke( self.SmokeColor )
self.SmokeTime = CurrentTime
end
end
end
end
--- @param #ZONE_GOAL self
-- @param Core.Event#EVENTDATA EventData
-- @param Core.Event#EVENTDATA EventData Event data table.
function ZONE_GOAL:__Destroyed( EventData )
self:F( { "EventDead", EventData } )
self:F( { EventData.IniUnit } )
local Vec3 = EventData.IniDCSUnit:getPosition().p
self:F( { Vec3 = Vec3 } )
local ZoneGoal = self:GetZone()
self:F({ZoneGoal})
if EventData.IniDCSUnit then
if ZoneGoal:IsVec3InZone(Vec3) then
local Vec3 = EventData.IniDCSUnit:getPosition().p
self:F( { Vec3 = Vec3 } )
if Vec3 and self:IsVec3InZone(Vec3) then
local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then
for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
self.Goal:AddPlayerContribution( PlayerName )
self:DestroyedUnit( EventData.IniUnitName, PlayerName )
end
end
end
end
end

View File

@@ -17,6 +17,11 @@
do -- ZoneGoal
--- @type ZONE_GOAL_COALITION
-- @field #string ClassName Name of the Class.
-- @field #number Coalition The current coalition ID of the zone owner.
-- @field #number PreviousCoalition The previous owner of the zone.
-- @field #table UnitCategories Table of unit categories that are able to capture and hold the zone. Default is only GROUND units.
-- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS.
-- @extends Functional.ZoneGoal#ZONE_GOAL
@@ -37,7 +42,11 @@ do -- ZoneGoal
--
-- @field #ZONE_GOAL_COALITION
ZONE_GOAL_COALITION = {
ClassName = "ZONE_GOAL_COALITION",
ClassName = "ZONE_GOAL_COALITION",
Coalition = nil,
PreviousCoaliton = nil,
UnitCategories = nil,
ObjectCategories = nil,
}
--- @field #table ZONE_GOAL_COALITION.States
@@ -46,27 +55,70 @@ do -- ZoneGoal
--- ZONE_GOAL_COALITION Constructor.
-- @param #ZONE_GOAL_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone. Default coalition.side.NEUTRAL.
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:New( Zone, Coalition )
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
if not Zone then
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!")
return nil
end
-- Inherit ZONE_GOAL.
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
self:SetCoalition( Coalition )
-- Set initial owner.
self:SetCoalition( Coalition or coalition.side.NEUTRAL)
-- Set default unit and object categories for the zone scan.
self:SetUnitCategories(UnitCategories)
self:SetObjectCategories()
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
-- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
self.PreviousCoalition=self.Coalition or Coalition
self.Coalition = Coalition
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories )
if UnitCategories and type(UnitCategories)~="table" then
UnitCategories={UnitCategories}
end
self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT}
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories )
if ObjectCategories and type(ObjectCategories)~="table" then
ObjectCategories={ObjectCategories}
end
self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC}
return self
end
--- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
@@ -75,37 +127,38 @@ do -- ZoneGoal
return self.Coalition
end
--- Get the previous coaliton, i.e. the one owning the zone before the current one.
-- @param #ZONE_GOAL_COALITION self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_COALITION:GetPreviousCoalition()
return self.PreviousCoalition
end
--- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @return #string Coalition name.
function ZONE_GOAL_COALITION:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
return UTILS.GetCoalitionName(self.Coalition)
end
--- Check status Coalition ownership.
-- @param #ZONE_GOAL_COALITION self
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:StatusZone()
-- Get current state.
local State = self:GetState()
self:F( { State = self:GetState() } )
self.Zone:Scan( { Object.Category.UNIT, Object.Category.STATIC } )
-- Debug text.
local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName())
self:F(text)
-- Scan zone.
self:Scan( self.ObjectCategories, self.UnitCategories )
return self
end
end

View File

@@ -5,7 +5,7 @@
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class
_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER
_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDULEDISPATCHER
--- Declare the main database object, which is used internally by the MOOSE classes.
_DATABASE = DATABASE:New() -- Core.Database#DATABASE

View File

@@ -0,0 +1,128 @@
__Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Core/Event.lua' )
__Moose.Include( 'Scripts/Moose/Core/Settings.lua' )
__Moose.Include( 'Scripts/Moose/Core/Menu.lua' )
__Moose.Include( 'Scripts/Moose/Core/Zone.lua' )
__Moose.Include( 'Scripts/Moose/Core/Zone_Detection.lua' )
__Moose.Include( 'Scripts/Moose/Core/Database.lua' )
__Moose.Include( 'Scripts/Moose/Core/Set.lua' )
__Moose.Include( 'Scripts/Moose/Core/Point.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Positionable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Controllable.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Group.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoSlingload.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoCrate.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoGroup.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Scoring.lua' )
__Moose.Include( 'Scripts/Moose/Functional/CleanUp.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Movement.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Sead.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Escort.lua' )
__Moose.Include( 'Scripts/Moose/Functional/MissileTrainer.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ATC_Ground.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Detection.lua' )
__Moose.Include( 'Scripts/Moose/Functional/DetectionZones.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Designate.lua' )
__Moose.Include( 'Scripts/Moose/Functional/RAT.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Range.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoal.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoalCoalition.lua' )
__Moose.Include( 'Scripts/Moose/Functional/ZoneCaptureCoalition.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Artillery.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air_Engage.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Cap.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Gci.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_BAI.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_CAS.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_SEAD.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Patrol.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cap.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cas.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Bai.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Formation.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Escort.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Request.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher_Request.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/TaskInfo.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Manager.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/DetectionManager.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Transport.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_CSAR.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Capture_Zone.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task_Capture_Dispatcher.lua' )
__Moose.Include( 'Scripts/Moose/Globals.lua' )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -144,6 +144,25 @@
-- 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.
--
--
-- @field #COMMANDCENTER
COMMANDCENTER = {
ClassName = "COMMANDCENTER",
@@ -156,6 +175,14 @@ COMMANDCENTER = {
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
@@ -169,21 +196,24 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName()
self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition()
self.AutoAssignTasks = false
self.Missions = {}
self:SetAutoAssignTasks( false )
self:SetAutoAcceptTasks( true )
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
self:SetFlashStatus( false )
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 self:HasGroup( EventGroup ) then
--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.ReportMissionsStatus, self, EventGroup )
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
@@ -442,7 +472,7 @@ function COMMANDCENTER:GetMenu( TaskGroup )
self.CommandCenterMenus[TaskGroup] = CommandCenterMenu
if self.AutoAssignTasks == false then
local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignRandomTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask")
local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove( MenuTime, "AutoTask" )
@@ -453,23 +483,43 @@ end
--- Assigns a random task to a TaskGroup.
-- @param #COMMANDCENTER self
-- @return #COMMANDCENTER
function COMMANDCENTER:AssignRandomTask( TaskGroup )
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
Tasks[#Tasks+1] = MissionTask
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
Task:SetAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) )
Task:AssignToGroup( TaskGroup )
if Task then
self:I( "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
@@ -498,29 +548,54 @@ 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
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
self:GetMenu( TaskGroup )
end
if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop( self.AssignTasks )
end
self:SetCommandCenterMenu()
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
@@ -531,12 +606,16 @@ function COMMANDCENTER:AssignTasks()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
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:AssignRandomTask( TaskGroup )
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
@@ -713,3 +792,12 @@ function COMMANDCENTER:ReportDetails( ReportGroup, Task )
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
function COMMANDCENTER:SetFlashStatus( Flash )
self:F()
self.FlashStatus = Flash or true
end

View File

@@ -46,6 +46,7 @@ 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.
@@ -56,6 +57,9 @@ do -- DETECTION MANAGER
Detection = nil,
}
--- @field Tasking.CommandCenter#COMMANDCENTER
DETECTION_MANAGER.CC = nil
--- FAC constructor.
-- @param #DETECTION_MANAGER self
-- @param Core.Set#SET_GROUP SetGroup
@@ -217,13 +221,95 @@ do -- DETECTION MANAGER
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 )
self:E()
end

View File

@@ -968,18 +968,19 @@ function MISSION:ReportPlayersProgress( ReportGroup )
-- Determine how many tasks are remaining.
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
local TaskGoalTotal = Task:GetGoalTotal() or 0
local TaskName = Task:GetName()
local Goal = Task:GetGoal()
PlayerList[TaskName] = PlayerList[TaskName] or {}
if TaskGoalTotal ~= 0 then
local PlayerNames = self:GetPlayerNames()
for PlayerName, PlayerData in pairs( PlayerNames ) do
PlayerList[TaskName][PlayerName] = string.format( 'Player (%s): Task "%s": %d%%', PlayerName, TaskName, Task:GetPlayerProgress( PlayerName ) * 100 / TaskGoalTotal )
if Goal then
local TotalContributions = Goal:GetTotalContributions()
local PlayerContributions = Goal:GetPlayerContributions()
self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } )
for PlayerName, PlayerContribution in pairs( PlayerContributions ) do
PlayerList[TaskName][PlayerName] = string.format( 'Player (%s): Task "%s": %d%%', PlayerName, TaskName, PlayerContributions[PlayerName] * 100 / TotalContributions )
end
else
PlayerList[TaskName]["_"] = string.format( 'Player (---): Task "%s": %d%%', TaskName, 0 )
end
end
end
for TaskName, TaskData in pairs( PlayerList ) do

View File

@@ -856,6 +856,8 @@ do -- Group Assignment
CommandCenter:SetMenu()
self:MenuFlashTaskStatus( TaskGroup, self:GetMission():GetCommandCenter().FlashStatus )
return self
end
@@ -1111,6 +1113,11 @@ function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime )
end
local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
if not self.FlashTaskStatus then
local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, true ):SetTime( MenuTime ):SetTag( "Tasking" )
else
local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Stop Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, nil ):SetTime( MenuTime ):SetTag( "Tasking" )
end
end
end
@@ -1225,12 +1232,34 @@ end
--- Report the task status.
-- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuTaskStatus( TaskGroup )
local ReportText = self:ReportDetails( TaskGroup )
self:T( ReportText )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
if TaskGroup:IsAlive() then
local ReportText = self:ReportDetails( TaskGroup )
self:T( ReportText )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
end
end
--- Report the task status.
-- @param #TASK self
function TASK:MenuFlashTaskStatus( TaskGroup, Flash )
self.FlashTaskStatus = Flash
if self.FlashTaskStatus then
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 )
else
if self.FlashTaskScheduler then
self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID )
self.FlashTaskScheduler = nil
self.FlashTaskScheduleID = nil
end
end
end
@@ -1709,6 +1738,23 @@ end
do -- Links
--- Set goal of a task
-- @param #TASK self
-- @param Core.Goal#GOAL Goal
-- @return #TASK
function TASK:SetGoal( Goal )
self.Goal = Goal
end
--- Get goal of a task
-- @param #TASK self
-- @return Core.Goal#GOAL The Goal
function TASK:GetGoal()
return self.Goal
end
--- Set dispatcher of a task
-- @param #TASK self
-- @param Tasking.DetectionManager#DETECTION_MANAGER Dispatcher
@@ -1786,7 +1832,7 @@ function TASK:GetPlayerCount() --R2.1 Get a count of the players.
if PlayerGroup:IsAlive() == true then
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
PlayerCount = PlayerCount + #PlayerNames
PlayerCount = PlayerCount + ((PlayerNames) and #PlayerNames or 0) -- PlayerNames can be nil when there are no players.
end
end
end
@@ -1808,7 +1854,7 @@ function TASK:GetPlayerNames() --R2.1 Get a map of the players.
if PlayerGroup:IsAlive() == true then
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
for PlayerNameID, PlayerName in pairs( PlayerNames ) do
for PlayerNameID, PlayerName in pairs( PlayerNames or {} ) do
PlayerNameMap[PlayerName] = PlayerGroup
end
end

View File

@@ -24,7 +24,7 @@ TASKINFO = {
ClassName = "TASKINFO",
}
--- @type #TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report.
--- @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.
@@ -52,16 +52,16 @@ end
--- Add taskinfo.
-- @param #TASKINFO self
-- @param #string The info key.
-- @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 )
self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } )
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 } )
self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } )
end
return self
end
@@ -124,12 +124,21 @@ end
-- @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 )
self:AddInfo( "Coordinate", Coordinate, Order, Detail, Keep )
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
@@ -153,7 +162,7 @@ end
-- @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", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]", Order, Detail, Keep )
self:AddInfo( "Threat", " [" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]:" .. ThreatText, Order, Detail, Keep )
return self
end
@@ -297,75 +306,63 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
for Key, Data in UTILS.spairs( self.Info.Set, function( t, a, b ) return t[a].Order < t[b].Order end ) do
self:F( { Key = Key, Detail = Detail, Data = Data } )
if Data.Detail:find( Detail ) then
local Text = ""
if Key == "TaskName" then
local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true )
if Key == "TaskName" then
Key = nil
Text = Data.Data
end
if Key == "Coordinate" then
elseif Data.Type and Data.Type == "Coordinate" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task )
end
if Key == "Threat" then
elseif Key == "Threat" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Counting" then
elseif Key == "Counting" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Targets" then
elseif Key == "Targets" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "QFE" then
elseif Key == "QFE" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringPressure( ReportGroup:GetUnit(1), nil, Task )
end
if Key == "Temperature" then
elseif Key == "Temperature" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringTemperature( ReportGroup:GetUnit(1), nil, Task )
end
if Key == "Wind" then
elseif Key == "Wind" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, Task )
end
if Key == "Cargo" then
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
Text = DataText
end
if Key == "Friendlies" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Players" then
local DataText = Data.Data -- #string
Text = DataText
end
if Line < math.floor( Data.Order / 10 ) then
if Line == 0 then
if Text ~= "" then
Report:AddIndent( LineReport:Text( ", " ), "-" )
end
Report:AddIndent( LineReport:Text( ", " ), "-" )
else
if Text ~= "" then
Report:AddIndent( LineReport:Text( ", " ) )
end
Report:AddIndent( LineReport:Text( ", " ) )
end
LineReport = REPORT:New()
Line = math.floor( Data.Order / 10 )
end
if Text ~= "" then
LineReport:Add( ( Key and ( Key .. ":" ) or "" ) .. Text )
LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text )
end
end
end
Report:AddIndent( LineReport:Text( ", " ) )
end

View File

@@ -292,7 +292,9 @@ do -- TASK_A2A
--- 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 )
function TASK_A2A:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
@@ -351,6 +353,26 @@ do -- TASK_A2A
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

View File

@@ -295,6 +295,8 @@ do -- TASK_A2G
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
-- @param #TASK_A2G self
function TASK_A2G:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
@@ -355,6 +357,27 @@ do -- TASK_A2G
end
end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_A2G self
-- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
self:F({Distance=Distance})
return math.floor( Distance )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end

View File

@@ -633,7 +633,7 @@ do -- TASK_A2G_DISPATCHER
--DetectedSet:Flush( self )
local DetectedItemID = DetectedItem.ID
local TaskIndex = DetectedItem.ID
local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } )
@@ -649,6 +649,7 @@ do -- TASK_A2G_DISPATCHER
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_SEAD ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
@@ -659,7 +660,7 @@ do -- TASK_A2G_DISPATCHER
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_CAS ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
@@ -671,7 +672,7 @@ do -- TASK_A2G_DISPATCHER
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_BAI ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else

View File

@@ -1381,6 +1381,23 @@ do -- TASK_CARGO
return 0
end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_CARGO self
-- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_CARGO:GetAutoAssignPriority( AutoAssignMethod, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
return 0
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end

View File

@@ -0,0 +1,396 @@
--- **Tasking** - Creates and manages player TASK_ZONE_CAPTURE tasks.
--
-- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human
-- players capture zones in a co-operation effort.
--
-- The dispatcher will implement for you mechanisms to create capture zone tasks:
--
-- * As setup by the mission designer.
-- * Dynamically capture zone tasks.
--
--
--
-- **Specific features:**
--
-- * Creates a task to capture zones and achieve mission goals.
-- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled.
-- * Co-operation tasking, so a player joins a group of players executing the same task.
--
--
-- **A complete task menu system to allow players to:**
--
-- * Join the task, abort the task.
-- * Mark the location of the zones to capture on the map.
-- * Provide details of the zones.
-- * Route to the zones.
-- * Display the task briefing.
--
--
-- **A complete mission menu system to allow players to:**
--
-- * Join a task, abort the task.
-- * Display task reports.
-- * Display mission statistics.
-- * Mark the task locations on the map.
-- * Provide details of the zones.
-- * Display the mission briefing.
-- * Provide status updates as retrieved from the command center.
-- * Automatically assign a random task as part of a mission.
-- * Manually assign a specific task as part of a mission.
--
--
-- **A settings system, using the settings menu:**
--
-- * Tweak the duration of the display of messages.
-- * Switch between metric and imperial measurement system.
-- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS.
-- * Various other options.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.Task_Zone_Capture_Dispatcher
-- @image MOOSE.JPG
do -- TASK_CAPTURE_DISPATCHER
--- TASK_CAPTURE_DISPATCHER class.
-- @type TASK_CAPTURE_DISPATCHER
-- @extends Tasking.Task_Manager#TASK_MANAGER
-- @field TASK_CAPTURE_DISPATCHER.ZONE ZONE
--- @type TASK_CAPTURE_DISPATCHER.CSAR
-- @field Wrapper.Unit#UNIT PilotUnit
-- @field Tasking.Task#TASK Task
--- Implements the dynamic dispatching of capture zone tasks.
--
-- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human
-- players capture zones in a co-operation effort.
--
-- Let's explore **step by step** how to setup the task capture zone dispatcher.
--
-- # 1. Setup a mission environment.
--
-- It is easy, as it works just like any other task setup, so setup a command center and a mission.
--
-- ## 1.1. Create a command center.
--
-- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
-- The command assumes that you´ve setup a group in the mission editor with the name HQ.
-- This group will act as the command center object.
-- It is a good practice to mark this group as invisible and invulnerable.
--
-- local CommandCenter = COMMANDCENTER
-- :New( GROUP:FindByName( "HQ" ), "HQ" ) -- Create the CommandCenter.
--
-- ## 1.2. Create a mission.
--
-- Tasks work in a **mission**, which groups these tasks to achieve a joint **mission goal**. A command center can **govern multiple missions**.
--
-- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor.
--
-- -- Declare the Mission for the Command Center.
-- local Mission = MISSION
-- :New( CommandCenter,
-- "Overlord",
-- "High",
-- "Capture the blue zones.",
-- coalition.side.RED
-- )
--
--
-- # 2. Dispatch a **capture zone** task.
--
-- So, now that we have a command center and a mission, we now create the capture zone task.
-- We create the capture zone task using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() constructor.
--
-- ## 2.1. Create the capture zones.
--
-- Because a capture zone task will not generate the capture zones, you'll need to create them first.
--
--
-- -- We define here a capture zone; of the type ZONE_CAPTURE_COALITION.
-- -- The zone to be captured has the name Alpha, and was defined in the mission editor as a trigger zone.
-- CaptureZone = ZONE:New( "Alpha" )
-- CaptureZoneCoalitionApha = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED )
--
-- ## 2.2. Create a set of player groups.
--
-- What is also needed, is to have a set of @{Core.Group}s defined that contains the clients of the players.
--
-- -- Allocate the player slots, which must be aircraft (airplanes or helicopters), that can be manned by players.
-- -- We use the method FilterPrefixes to filter those player groups that have client slots, as defined in the mission editor.
-- -- In this example, we filter the groups where the name starts with "Blue Player", which captures the blue player slots.
-- local PlayerGroupSet = SET_GROUP:New():FilterPrefixes( "Blue Player" ):FilterStart()
--
-- ## 2.3. Setup the capture zone task.
--
-- First, we need to create a TASK_CAPTURE_DISPATCHER object.
--
-- TaskCaptureZoneDispatcher = TASK_CAPTURE_DISPATCHER:New( Mission, PilotGroupSet )
--
-- So, the variable `TaskCaptureZoneDispatcher` will contain the object of class TASK_CAPTURE_DISPATCHER,
-- which will allow you to dispatch capture zone tasks:
--
-- * for mission `Mission`, as was defined in section 1.2.
-- * for the group set `PilotGroupSet`, as was defined in section 2.2.
--
-- Now that we have `TaskDispatcher` object, we can now **create the TaskCaptureZone**, using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() method!
--
-- local TaskCaptureZone = TaskCaptureZoneDispatcher:AddCaptureZoneTask(
-- "Capture zone Alpha",
-- CaptureZoneCoalitionAlpha,
-- "Fly to zone Alpha and eliminate all enemy forces to capture it." )
--
-- As a result of this code, the `TaskCaptureZone` (returned) variable will contain an object of @{#TASK_CAPTURE_ZONE}!
-- We pass to the method the title of the task, and the `CaptureZoneCoalitionAlpha`, which is the zone to be captured, as defined in section 2.1!
-- This returned `TaskCaptureZone` object can now be used to setup additional task configurations, or to control this specific task with special events.
--
-- And you're done! As you can see, it is a small bit of work, but the reward is great.
-- And, because all this is done using program interfaces, you can easily build a mission to capture zones yourself!
-- Based on various events happening within your mission, you can use the above methods to create new capture zones,
-- and setup a new capture zone task and assign it to a group of players, while your mission is running!
--
--
--
-- @field #TASK_CAPTURE_DISPATCHER
TASK_CAPTURE_DISPATCHER = {
ClassName = "TASK_CAPTURE_DISPATCHER",
Mission = nil,
Tasks = {},
Zones = {},
ZoneCount = 0,
}
TASK_CAPTURE_DISPATCHER.AI_A2G_Dispatcher = nil -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
--- TASK_CAPTURE_DISPATCHER constructor.
-- @param #TASK_CAPTURE_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.
-- @return #TASK_CAPTURE_DISPATCHER self
function TASK_CAPTURE_DISPATCHER:New( Mission, SetGroup )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER
self.Mission = Mission
self:AddTransition( "Started", "Assign", "Started" )
self:AddTransition( "Started", "ZoneCaptured", "Started" )
self:__StartTasks( 5 )
return self
end
--- Link a task capture dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other coalition!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param #TASK_CAPTURE_DISPATCHER DefenseTaskCaptureDispatcher
function TASK_CAPTURE_DISPATCHER:SetDefenseTaskCaptureDispatcher( DefenseTaskCaptureDispatcher )
self.DefenseTaskCaptureDispatcher = DefenseTaskCaptureDispatcher
end
--- Get the linked task capture dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other coalition!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @return #TASK_CAPTURE_DISPATCHER
function TASK_CAPTURE_DISPATCHER:GetDefenseTaskCaptureDispatcher()
return self.DefenseTaskCaptureDispatcher
end
--- Link an AI A2G dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other AI A2G dispatcher!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER DefenseAIA2GDispatcher
function TASK_CAPTURE_DISPATCHER:SetDefenseAIA2GDispatcher( DefenseAIA2GDispatcher )
self.DefenseAIA2GDispatcher = DefenseAIA2GDispatcher
end
--- Get the linked AI A2G dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the AI A2G dispatcher!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @return AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
function TASK_CAPTURE_DISPATCHER:GetDefenseAIA2GDispatcher()
return self.DefenseAIA2GDispatcher
end
--- Add a capture zone task.
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param #string TaskPrefix (optional) The prefix of the capture zone task.
-- If no TaskPrefix is given, then "Capture" will be used as the TaskPrefix.
-- The TaskPrefix will be appended with a . + a number of 3 digits, if the TaskPrefix already exists in the task collection.
-- @param Functional.CaptureZoneCoalition#ZONE_CAPTURE_COALITION CaptureZone The zone of the coalition to be captured as the task goal.
-- @param #string Briefing The briefing of the task to be shown to the player.
-- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE
-- @usage
--
--
function TASK_CAPTURE_DISPATCHER:AddCaptureZoneTask( TaskPrefix, CaptureZone, Briefing )
local TaskName = TaskPrefix or "Capture"
if self.Zones[TaskName] then
self.ZoneCount = self.ZoneCount + 1
TaskName = string.format( "%s.%03d", TaskName, self.ZoneCount )
end
self.Zones[TaskName] = {}
self.Zones[TaskName].CaptureZone = CaptureZone
self.Zones[TaskName].Briefing = Briefing
self.Zones[TaskName].Task = nil
self.Zones[TaskName].TaskPrefix = TaskPrefix
self:ManageTasks()
return self.Zones[TaskName] and self.Zones[TaskName].Task
end
--- Link an AI_A2G_DISPATCHER to the TASK_CAPTURE_DISPATCHER.
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER AI_A2G_Dispatcher The AI Dispatcher to be linked to the tasking.
-- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE
function TASK_CAPTURE_DISPATCHER:Link_AI_A2G_Dispatcher( AI_A2G_Dispatcher )
self.AI_A2G_Dispatcher = AI_A2G_Dispatcher -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
AI_A2G_Dispatcher.Detection:LockDetectedItems()
return self
end
--- Assigns tasks to the @{Core.Set#SET_GROUP}.
-- @param #TASK_CAPTURE_DISPATCHER self
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_CAPTURE_DISPATCHER:ManageTasks()
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
-- Here we need to check if the pilot is still existing.
-- Task = self:RemoveTask( TaskIndex )
end
end
-- Now that all obsolete tasks are removed, loop through the Zone tasks.
for TaskName, CaptureZone in pairs( self.Zones ) do
if not CaptureZone.Task then
-- New Transport Task
CaptureZone.Task = TASK_CAPTURE_ZONE:New( Mission, self.SetGroup, TaskName, CaptureZone.CaptureZone, CaptureZone.Briefing )
CaptureZone.Task.TaskPrefix = CaptureZone.TaskPrefix -- We keep the TaskPrefix for further reference!
Mission:AddTask( CaptureZone.Task )
TaskReport:Add( TaskName )
-- Link the Task Dispatcher to the capture zone task, because it is used on the UpdateTaskInfo.
CaptureZone.Task:SetDispatcher( self )
CaptureZone.Task:UpdateTaskInfo()
function CaptureZone.Task.OnEnterAssigned( Task, From, Event, To )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Unlock( Task.TaskZoneName ) -- This will unlock the zone to be defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = true
end
function CaptureZone.Task.OnEnterSuccess( Task, From, Event, To )
--self:Success( Task )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end
function CaptureZone.Task.OnEnterCancelled( Task, From, Event, To )
self:Cancelled( Task )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end
function CaptureZone.Task.OnEnterFailed( Task, From, Event, To )
self:Failed( Task )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end
function CaptureZone.Task.OnEnterAborted( Task, From, Event, To )
self:Aborted( Task )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end
-- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher.
function CaptureZone.Task.OnAfterCaptured( Task, From, Event, To, TaskUnit )
self:Captured( Task, Task.TaskPrefix, TaskUnit )
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end
end
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 ~= "" 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

View File

@@ -47,7 +47,7 @@ do -- TASK_ZONE_GOAL
-- @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.ZoneGoal#ZONE_GOAL ZoneGoal
-- @param Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal
-- @return #TASK_ZONE_GOAL self
function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL
@@ -59,19 +59,25 @@ do -- TASK_ZONE_GOAL
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "StartMonitoring", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" )
Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} )
Fsm:AddTransition( "Monitoring", "RouteTo", "Monitoring" )
Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} )
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
--Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
self:SetTargetZone( self.ZoneGoal:GetZone() )
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK Task
function Fsm:OnAfterAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:__StartMonitoring( 0.1 )
self:__RouteToZone( 0.1 )
end
--- Test
-- @param #FSM_PROCESS self
@@ -80,7 +86,6 @@ do -- TASK_ZONE_GOAL
function Fsm:onafterStartMonitoring( TaskUnit, Task )
self:F( { self } )
self:__Monitor( 0.1 )
self:__RouteTo( 0.1 )
end
--- Monitor Loop
@@ -101,7 +106,7 @@ do -- TASK_ZONE_GOAL
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
self:__RouteTo( 0.1 )
self:__RouteToZone( 0.1 )
end
end
@@ -160,37 +165,37 @@ do -- TASK_ZONE_GOAL
end
do -- TASK_ZONE_CAPTURE
do -- TASK_CAPTURE_ZONE
--- The TASK_ZONE_CAPTURE class
-- @type TASK_ZONE_CAPTURE
--- The TASK_CAPTURE_ZONE class
-- @type TASK_CAPTURE_ZONE
-- @field Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal
-- @extends #TASK_ZONE_GOAL
--- # TASK_ZONE_CAPTURE class, extends @{Tasking.TaskZoneGoal#TASK_ZONE_GOAL}
--- # TASK_CAPTURE_ZONE class, extends @{Tasking.TaskZoneGoal#TASK_ZONE_GOAL}
--
-- The TASK_ZONE_CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
-- The TASK_CAPTURE_ZONE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
-- These tasks are important to be executed as they will help to achieve air superiority at the vicinity.
--
-- The TASK_ZONE_CAPTURE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks
-- The TASK_CAPTURE_ZONE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks
-- based on detected enemy ground targets.
--
-- @field #TASK_ZONE_CAPTURE
TASK_ZONE_CAPTURE = {
ClassName = "TASK_ZONE_CAPTURE",
-- @field #TASK_CAPTURE_ZONE
TASK_CAPTURE_ZONE = {
ClassName = "TASK_CAPTURE_ZONE",
}
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
--- Instantiates a new TASK_CAPTURE_ZONE.
-- @param #TASK_CAPTURE_ZONE 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.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_ZONE_CAPTURE
-- @return #TASK_CAPTURE_ZONE self
function TASK_CAPTURE_ZONE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_CAPTURE_ZONE
self:F()
Mission:AddTask( self )
@@ -206,42 +211,84 @@ do -- TASK_ZONE_CAPTURE
"Capture Zone " .. self.TaskZoneName
)
self:UpdateTaskInfo()
self:UpdateTaskInfo( true )
self:SetGoal( self.ZoneGoal.Goal )
return self
end
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:UpdateTaskInfo()
--- Instantiates a new TASK_CAPTURE_ZONE.
-- @param #TASK_CAPTURE_ZONE self
function TASK_CAPTURE_ZONE:UpdateTaskInfo( Persist )
Persist = Persist or false
local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate()
self.TaskInfo:AddCoordinate( ZoneCoordinate, 0, "SOD" )
self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD" )
self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD" )
self.TaskInfo:AddTaskName( 0, "MSOD", Persist )
self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist )
-- self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist )
-- self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist )
local SetUnit = self.ZoneGoal:GetScannedSetUnit()
local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G()
local ThreatCount = SetUnit:Count()
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 20, "MOD", Persist )
self.TaskInfo:AddInfo( "Remaining Units", ThreatCount, 21, "MOD", Persist, true)
if self.Dispatcher then
local DefenseTaskCaptureDispatcher = self.Dispatcher:GetDefenseTaskCaptureDispatcher() -- Tasking.Task_Capture_Dispatcher#TASK_CAPTURE_DISPATCHER
if DefenseTaskCaptureDispatcher then
-- Loop through all zones of the player Defenses, and check which zone has an assigned task!
-- The Zones collection contains a Task. This Task is checked if it is assigned.
-- If Assigned, then this task will be the task that is the closest to the defense zone.
for TaskName, CaptureZone in pairs( DefenseTaskCaptureDispatcher.Zones or {} ) do
local Task = CaptureZone.Task -- Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE
if Task and Task:IsStateAssigned() then -- We also check assigned.
-- Now we register the defense player zone information to the task report.
self.TaskInfo:AddInfo( "Defense Player Zone", Task.ZoneGoal:GetName(), 30, "MOD", Persist )
self.TaskInfo:AddCoordinate( Task.ZoneGoal:GetZone():GetCoordinate(), 31, "MOD", Persist, false, "Defense Player Coordinate" )
end
end
end
local DefenseAIA2GDispatcher = self.Dispatcher:GetDefenseAIA2GDispatcher() -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
if DefenseAIA2GDispatcher then
-- Loop through all the tasks of the AI Defenses, and check which zone is involved in the defenses and is active!
for Defender, Task in pairs( DefenseAIA2GDispatcher:GetDefenderTasks() or {} ) do
local DetectedItem = DefenseAIA2GDispatcher:GetDefenderTaskTarget( Defender )
if DetectedItem then
local DetectedZone = DefenseAIA2GDispatcher.Detection:GetDetectedItemZone( DetectedItem )
if DetectedZone then
self.TaskInfo:AddInfo( "Defense AI Zone", DetectedZone:GetName(), 40, "MOD", Persist )
self.TaskInfo:AddCoordinate( DetectedZone:GetCoordinate(), 41, "MOD", Persist, false, "Defense AI Coordinate" )
end
end
end
end
end
end
function TASK_ZONE_CAPTURE:ReportOrder( ReportGroup )
local Coordinate = self:GetData( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
function TASK_CAPTURE_ZONE:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo:GetCoordinate()
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_ZONE_CAPTURE self
--- @param #TASK_CAPTURE_ZONE self
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_ZONE_CAPTURE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName )
function TASK_CAPTURE_ZONE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName )
self:F( { PlayerUnit = PlayerUnit } )
self:F( { PlayerUnit = PlayerUnit, Achieved = self.ZoneGoal.Goal:IsAchieved() } )
if self.ZoneGoal then
if self.ZoneGoal.Goal:IsAchieved() then
self:Success()
local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions()
local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions()
self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } )
@@ -251,11 +298,32 @@ do -- TASK_ZONE_CAPTURE
Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions )
end
end
self:Success()
end
end
self:__Goal( -10, PlayerUnit, PlayerName )
end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_CAPTURE_ZONE 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_CAPTURE_ZONE:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup, AutoAssignReference )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetCoordinate()
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
return math.floor( Distance )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end

View File

@@ -185,7 +185,6 @@ do -- TASK_MANAGER
-- @param #TASK_MANAGER self
-- @return #TASK_MANAGER self
function TASK_MANAGER:ManageTasks()
self:E()
end

View File

@@ -0,0 +1,313 @@
--- **Utilities** Enumerators.
--
-- An enumerator is a variable that holds a constant value. Enumerators are very useful because they make the code easier to read and to change in general.
--
-- For example, instead of using the same value at multiple different places in your code, you should use a variable set to that value.
-- If, for whatever reason, the value needs to be changed, you only have to change the variable once and do not have to search through you code and reset
-- every value by hand.
--
-- Another big advantage is that the LDT intellisense "knows" the enumerators. So you can use the autocompletion feature and do not have to keep all the
-- values in your head or look them up in the docs.
--
-- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit.
--
-- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
--
-- @module ENUMS
-- @image MOOSE.JPG
--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world)
-- @type ENUMS
--- Because ENUMS are just better practice.
--
-- The ENUMS class adds some handy variables, which help you to make your code better and more general.
--
-- @field #ENUMS
ENUMS = {}
--- Rules of Engagement.
-- @type ENUMS.ROE
-- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target.
-- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking.
-- @field #number OpenFire AI will engage only targets specified in its taskings.
-- @field #number ReturnFire AI will only engage threats that shoot first.
-- @field #number WeaponHold AI will hold fire under all circumstances.
ENUMS.ROE = {
WeaponFree=0,
OpenFireWeaponFree=1,
OpenFire=2,
ReturnFire=3,
WeaponHold=4,
}
--- Reaction On Threat.
-- @type ENUMS.ROT
-- @field #number NoReaction No defensive actions will take place to counter threats.
-- @field #number PassiveDefense AI will use jammers and other countermeasures in an attempt to defeat the threat. AI will not attempt a maneuver to defeat a threat.
-- @field #number EvadeFire AI will react by performing defensive maneuvers against incoming threats. AI will also use passive defense.
-- @field #number BypassAndEscape AI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats.
-- @field #number AllowAbortMission If a threat is deemed severe enough the AI will abort its mission and return to base.
ENUMS.ROT = {
NoReaction=0,
PassiveDefense=1,
EvadeFire=2,
BypassAndEscape=3,
AllowAbortMission=4,
}
--- Alarm state.
-- @type ENUMS.AlarmState
-- @field #number Auto AI will automatically switch alarm states based on the presence of threats. The AI kind of cheats in this regard.
-- @field #number Green Group is not combat ready. Sensors are stowed if possible.
-- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state.
ENUMS.AlarmState = {
Auto=0,
Green=1,
Red=2,
}
--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki.
-- @type ENUMS.WeaponFlag
ENUMS.WeaponFlag={
-- Bombs
LGB = 2,
TvGB = 4,
SNSGB = 8,
HEBomb = 16,
Penetrator = 32,
NapalmBomb = 64,
FAEBomb = 128,
ClusterBomb = 256,
Dispencer = 512,
CandleBomb = 1024,
ParachuteBomb = 2147483648,
-- Rockets
LightRocket = 2048,
MarkerRocket = 4096,
CandleRocket = 8192,
HeavyRocket = 16384,
-- Air-To-Surface Missiles
AntiRadarMissile = 32768,
AntiShipMissile = 65536,
AntiTankMissile = 131072,
FireAndForgetASM = 262144,
LaserASM = 524288,
TeleASM = 1048576,
CruiseMissile = 2097152,
AntiRadarMissile2 = 1073741824,
-- Air-To-Air Missiles
SRAM = 4194304,
MRAAM = 8388608,
LRAAM = 16777216,
IR_AAM = 33554432,
SAR_AAM = 67108864,
AR_AAM = 134217728,
--- Guns
GunPod = 268435456,
BuiltInCannon = 536870912,
---
-- Combinations
--
-- Bombs
GuidedBomb = 14, -- (LGB + TvGB + SNSGB)
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb)
AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb)
--- Rockets
AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket
--- Air-To-Surface Missiles
GuidedASM = 1572864, -- (LaserASM + TeleASM)
TacticalASM = 1835008, -- (GuidedASM + FireAndForgetASM)
AnyASM = 4161536, -- (AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile)
AnyASM2 = 1077903360, -- 4161536+1073741824,
--- Air-To-Air Missiles
AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
AnyMissile = 268402688, -- AnyASM + AnyAAM
--- Guns
Cannons = 805306368, -- GUN_POD + BuiltInCannon
---
-- Even More Genral
Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
AutoDCS = 1073741822, -- Something if often see
AnyAG = 2956984318, -- Any Air-To-Ground Weapon
AnyAA = 264241152, -- Any Air-To-Air Weapon
AnyUnguided = 2952822768, -- Any Unguided Weapon
AnyGuided = 268402702, -- Any Guided Weapon
}
--- Mission tasks.
-- @type ENUMS.MissionTask
-- @field #string NOTHING No special task. Group can perform the minimal tasks: Orbit, Refuelling, Follow and Aerobatics.
-- @field #string AFAC Forward Air Controller Air. Can perform the tasks: Attack Group, Attack Unit, FAC assign group, Bombing, Attack Map Object.
-- @field #string ANTISHIPSTRIKE Naval ops. Can perform the tasks: Attack Group, Attack Unit.
-- @field #string AWACS AWACS.
-- @field #string CAP Combat Air Patrol.
-- @field #string CAS Close Air Support.
-- @field #string ESCORT Escort another group.
-- @field #string FIGHTERSWEEP Fighter sweep.
-- @field #string GROUNDATTACK Ground attack.
-- @field #string INTERCEPT Intercept.
-- @field #string PINPOINTSTRIKE Pinpoint strike.
-- @field #string RECONNAISSANCE Reconnaissance mission.
-- @field #string REFUELING Refueling mission.
-- @field #string RUNWAYATTACK Attack the runway of an airdrome.
-- @field #string SEAD Suppression of Enemy Air Defenses.
-- @field #string TRANSPORT Troop transport.
ENUMS.MissionTask={
NOTHING="Nothing",
AFAC="AFAC",
ANTISHIPSTRIKE="Antiship Strike",
AWACS="AWACS",
CAP="CAP",
CAS="CAS",
ESCORT="Escort",
FIGHTERSWEEP="Fighter Sweep",
GROUNDATTACK="Ground Attack",
INTERCEPT="Intercept",
PINPOINTSTRIKE="Pinpoint Strike",
RECONNAISSANCE="Reconnaissance",
REFUELING="Refueling",
RUNWAYATTACK="Runway Attack",
SEAD="SEAD",
TRANSPORT="Transport",
}
--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
-- @type ENUMS.Formation
ENUMS.Formation={}
ENUMS.Formation.FixedWing={}
ENUMS.Formation.FixedWing.LineAbreast={}
ENUMS.Formation.FixedWing.LineAbreast.Close = 65537
ENUMS.Formation.FixedWing.LineAbreast.Open = 65538
ENUMS.Formation.FixedWing.LineAbreast.Group = 65539
ENUMS.Formation.FixedWing.Trail={}
ENUMS.Formation.FixedWing.Trail.Close = 131073
ENUMS.Formation.FixedWing.Trail.Open = 131074
ENUMS.Formation.FixedWing.Trail.Group = 131075
ENUMS.Formation.FixedWing.Wedge={}
ENUMS.Formation.FixedWing.Wedge.Close = 196609
ENUMS.Formation.FixedWing.Wedge.Open = 196610
ENUMS.Formation.FixedWing.Wedge.Group = 196611
ENUMS.Formation.FixedWing.EchelonRight={}
ENUMS.Formation.FixedWing.EchelonRight.Close = 262145
ENUMS.Formation.FixedWing.EchelonRight.Open = 262146
ENUMS.Formation.FixedWing.EchelonRight.Group = 262147
ENUMS.Formation.FixedWing.EchelonLeft={}
ENUMS.Formation.FixedWing.EchelonLeft.Close = 327681
ENUMS.Formation.FixedWing.EchelonLeft.Open = 327682
ENUMS.Formation.FixedWing.EchelonLeft.Group = 327683
ENUMS.Formation.FixedWing.FingerFour={}
ENUMS.Formation.FixedWing.FingerFour.Close = 393217
ENUMS.Formation.FixedWing.FingerFour.Open = 393218
ENUMS.Formation.FixedWing.FingerFour.Group = 393219
ENUMS.Formation.FixedWing.Spread={}
ENUMS.Formation.FixedWing.Spread.Close = 458753
ENUMS.Formation.FixedWing.Spread.Open = 458754
ENUMS.Formation.FixedWing.Spread.Group = 458755
ENUMS.Formation.FixedWing.BomberElement={}
ENUMS.Formation.FixedWing.BomberElement.Close = 786433
ENUMS.Formation.FixedWing.BomberElement.Open = 786434
ENUMS.Formation.FixedWing.BomberElement.Group = 786435
ENUMS.Formation.FixedWing.BomberElementHeight={}
ENUMS.Formation.FixedWing.BomberElementHeight.Close = 851968
ENUMS.Formation.FixedWing.FighterVic={}
ENUMS.Formation.FixedWing.FighterVic.Close = 917505
ENUMS.Formation.FixedWing.FighterVic.Open = 917506
ENUMS.Formation.RotaryWing={}
ENUMS.Formation.RotaryWing.Column={}
ENUMS.Formation.RotaryWing.Column.D70=720896
ENUMS.Formation.RotaryWing.Wedge={}
ENUMS.Formation.RotaryWing.Wedge.D70=8
ENUMS.Formation.RotaryWing.FrontRight={}
ENUMS.Formation.RotaryWing.FrontRight.D300=655361
ENUMS.Formation.RotaryWing.FrontRight.D600=655362
ENUMS.Formation.RotaryWing.FrontLeft={}
ENUMS.Formation.RotaryWing.FrontLeft.D300=655617
ENUMS.Formation.RotaryWing.FrontLeft.D600=655618
ENUMS.Formation.RotaryWing.EchelonRight={}
ENUMS.Formation.RotaryWing.EchelonRight.D70 =589825
ENUMS.Formation.RotaryWing.EchelonRight.D300=589826
ENUMS.Formation.RotaryWing.EchelonRight.D600=589827
ENUMS.Formation.RotaryWing.EchelonLeft={}
ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
ENUMS.Formation.Vehicle={}
ENUMS.Formation.Vehicle.Vee="Vee"
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
ENUMS.Formation.Vehicle.OffRoad="Off Road"
ENUMS.Formation.Vehicle.Rank="Rank"
ENUMS.Formation.Vehicle.EchelonLeft="EchelonL"
ENUMS.Formation.Vehicle.OnRoad="On Road"
ENUMS.Formation.Vehicle.Cone="Cone"
ENUMS.Formation.Vehicle.Diamond="Diamond"
--- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings.
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
-- @type ENUMS.FormationOld
ENUMS.FormationOld={}
ENUMS.FormationOld.FixedWing={}
ENUMS.FormationOld.FixedWing.LineAbreast=1
ENUMS.FormationOld.FixedWing.Trail=2
ENUMS.FormationOld.FixedWing.Wedge=3
ENUMS.FormationOld.FixedWing.EchelonRight=4
ENUMS.FormationOld.FixedWing.EchelonLeft=5
ENUMS.FormationOld.FixedWing.FingerFour=6
ENUMS.FormationOld.FixedWing.SpreadFour=7
ENUMS.FormationOld.FixedWing.BomberElement=12
ENUMS.FormationOld.FixedWing.BomberElementHeight=13
ENUMS.FormationOld.FixedWing.FighterVic=14
ENUMS.FormationOld.RotaryWing={}
ENUMS.FormationOld.RotaryWing.Wedge=8
ENUMS.FormationOld.RotaryWing.Echelon=9
ENUMS.FormationOld.RotaryWing.Front=10
ENUMS.FormationOld.RotaryWing.Column=11
--- Morse Code. See the [Wikipedia](https://en.wikipedia.org/wiki/Morse_code).
--
-- * Short pulse "*"
-- * Long pulse "-"
--
-- Pulses are separated by a blank character " ".
--
-- @type ENUMS.Morse
ENUMS.Morse={}
ENUMS.Morse.A="* -"
ENUMS.Morse.B="- * * *"
ENUMS.Morse.C="- * - *"
ENUMS.Morse.D="- * *"
ENUMS.Morse.E="*"
ENUMS.Morse.F="* * - *"
ENUMS.Morse.G="- - *"
ENUMS.Morse.H="* * * *"
ENUMS.Morse.I="* *"
ENUMS.Morse.J="* - - -"
ENUMS.Morse.K="- * -"
ENUMS.Morse.L="* - * *"
ENUMS.Morse.M="- -"
ENUMS.Morse.N="- *"
ENUMS.Morse.O="- - -"
ENUMS.Morse.P="* - - *"
ENUMS.Morse.Q="- - * -"
ENUMS.Morse.R="* - *"
ENUMS.Morse.S="* * *"
ENUMS.Morse.T="-"
ENUMS.Morse.U="* * -"
ENUMS.Morse.V="* * * -"
ENUMS.Morse.W="* - -"
ENUMS.Morse.X="- * * -"
ENUMS.Morse.Y="- * - -"
ENUMS.Morse.Z="- - * *"
ENUMS.Morse.N1="* - - - -"
ENUMS.Morse.N2="* * - - -"
ENUMS.Morse.N3="* * * - -"
ENUMS.Morse.N4="* * * * -"
ENUMS.Morse.N5="* * * * *"
ENUMS.Morse.N6="- * * * *"
ENUMS.Morse.N7="- - * * *"
ENUMS.Morse.N8="- - - * *"
ENUMS.Morse.N9="- - - - *"
ENUMS.Morse.N0="- - - - -"
ENUMS.Morse[" "]=" "

View File

@@ -0,0 +1,539 @@
--- **Utils** - Lua Profiler.
--
-- Find out how many times functions are called and how much real time it costs.
--
-- ===
--
-- ### Author: **TAW CougarNL**, *funkyfranky*
--
-- @module Utilities.PROFILER
-- @image MOOSE.JPG
--- PROFILER class.
-- @type PROFILER
-- @field #string ClassName Name of the class.
-- @field #table Counters Function counters.
-- @field #table dInfo Info.
-- @field #table fTime Function time.
-- @field #table fTimeTotal Total function time.
-- @field #table eventhandler Event handler to get mission end event.
-- @field #number TstartGame Game start time timer.getTime().
-- @field #number TstartOS OS real start time os.clock.
-- @field #boolean logUnknown Log unknown functions. Default is off.
-- @field #number ThreshCPS Low calls per second threshold. Only write output if function has more calls per second than this value.
-- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value.
-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler".
-- @field #string fileNameSuffix Output file name prefix, e.g. "txt"
--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? *
--
-- ===
--
-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg)
--
-- # The PROFILER Concept
--
-- Profile your lua code. This tells you, which functions are called very often and which consume most real time.
-- With this information you can optimize the perfomance of your code.
--
-- # Prerequisites
--
-- The modules **os** and **lfs** need to be desanizied.
--
--
-- # Start
--
-- The profiler can simply be started with the @{#PROFILER.Start}(*Delay, Duration*) function
--
-- PROFILER.Start()
--
-- The optional parameter *Delay* can be used to delay the start by a certain amount of seconds and the optional parameter *Duration* can be used to
-- stop the profiler after a certain amount of seconds.
--
-- # Stop
--
-- The profiler automatically stops when the mission ends. But it can be stopped any time with the @{#PROFILER.Stop}(*Delay*) function
--
-- PROFILER.Stop()
--
-- The optional parameter *Delay* can be used to specify a delay after which the profiler is stopped.
--
-- When the profiler is stopped, the output is written to a file.
--
-- # Output
--
-- The profiler output is written to a file in your DCS home folder
--
-- X:\User\<Your User Name>\Saved Games\DCS OpenBeta\Logs
--
-- The default file name is "MooseProfiler.txt". If that file exists, the file name is "MooseProfiler-001.txt" etc.
--
-- ## Data
--
-- The data in the output file provides information on the functions that were called in the mission.
--
-- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time.
--
-- If you only want output for functions that are called more than *X* times per second, you can set
--
-- PROFILER.ThreshCPS=1.5
--
-- With this setting, only functions which are called more than 1.5 times per second are displayed. The default setting is PROFILER.ThreshCPS=0.0 (no threshold).
--
-- Furthermore, you can limit the output for functions that consumed a certain amount of CPU time in total by
--
-- PROFILER.ThreshTtot=0.005
--
-- With this setting, which is also the default, only functions which in total used more than 5 milliseconds CPU time.
--
-- @field #PROFILER
PROFILER = {
ClassName = "PROFILER",
Counters = {},
dInfo = {},
fTime = {},
fTimeTotal = {},
eventHandler = {},
logUnknown = false,
ThreshCPS = 0.0,
ThreshTtot = 0.005,
fileNamePrefix = "MooseProfiler",
fileNameSuffix = "txt"
}
--- Waypoint data.
-- @type PROFILER.Data
-- @field #string func The function name.
-- @field #string src The source file.
-- @field #number line The line number
-- @field #number count Number of function calls.
-- @field #number tm Total time in seconds.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start/Stop Profiler
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start profiler.
-- @param #number Delay Delay in seconds before profiler is stated. Default is immediately.
-- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends.
function PROFILER.Start(Delay, Duration)
-- Check if os, io and lfs are available.
local go=true
if not os then
env.error("ERROR: Profiler needs os to be desanitized!")
go=false
end
if not io then
env.error("ERROR: Profiler needs io to be desanitized!")
go=false
end
if not lfs then
env.error("ERROR: Profiler needs lfs to be desanitized!")
go=false
end
if not go then
return
end
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration)
else
-- Set start time.
PROFILER.TstartGame=timer.getTime()
PROFILER.TstartOS=os.clock()
-- Add event handler.
world.addEventHandler(PROFILER.eventHandler)
-- Info in log.
env.info('############################ Profiler Started ############################')
if Duration then
env.info(string.format("- Will be running for %d seconds", Duration))
else
env.info(string.format("- Will be stopped when mission ends"))
end
env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS))
env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot))
env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix)))
env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv")))
env.info('###############################################################################')
-- Message on screen
local duration=Duration or 600
trigger.action.outText("### Profiler running ###", duration)
-- Set hook.
debug.sethook(PROFILER.hook, "cr")
-- Auto stop profiler.
if Duration then
PROFILER.Stop(Duration)
end
end
end
--- Stop profiler.
-- @param #number Delay Delay before stop in seconds.
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay, PROFILER.Stop)
else
-- Remove hook.
debug.sethook()
-- Run time game.
local runTimeGame=timer.getTime()-PROFILER.TstartGame
-- Run time real OS.
local runTimeOS=os.clock()-PROFILER.TstartOS
-- Show info.
PROFILER.showInfo(runTimeGame, runTimeOS)
end
end
--- Event handler.
function PROFILER.eventHandler:onEvent(event)
if event.id==world.event.S_EVENT_MISSION_END then
PROFILER.Stop()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Hook
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Debug hook.
-- @param #table event Event.
function PROFILER.hook(event)
local f=debug.getinfo(2, "f").func
if event=='call' then
if PROFILER.Counters[f]==nil then
PROFILER.Counters[f]=1
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
if PROFILER.fTimeTotal[f]==nil then
PROFILER.fTimeTotal[f]=0
end
else
PROFILER.Counters[f]=PROFILER.Counters[f]+1
end
if PROFILER.fTime[f]==nil then
PROFILER.fTime[f]=os.clock()
end
elseif (event=='return') then
if PROFILER.fTime[f]~=nil then
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
PROFILER.fTime[f]=nil
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Data
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get data.
-- @param #function func Function.
-- @return #string Function name.
-- @return #string Source file name.
-- @return #string Line number.
-- @return #number Function time in seconds.
function PROFILER.getData(func)
local n=PROFILER.dInfo[func]
if n.what=="C" then
return n.name, "?", "?", PROFILER.fTimeTotal[func]
end
return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func]
end
--- Write text to log file.
-- @param #function f The file.
-- @param #string txt The text.
function PROFILER._flog(f, txt)
f:write(txt.."\r\n")
end
--- Show table.
-- @param #table data Data table.
-- @param #function f The file.
-- @param #number runTimeGame Game run time in seconds.
function PROFILER.showTable(data, f, runTimeGame)
-- Loop over data.
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps=t.count/runTimeGame
local threshCPS=cps>=PROFILER.ThreshCPS
local threshTot=t.tm>=PROFILER.ThreshTtot
if threshCPS and threshTot then
-- Output
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
PROFILER._flog(f, text)
end
end
end
--- Print csv file.
-- @param #table data Data table.
-- @param #number runTimeGame Game run time in seconds.
function PROFILER.printCSV(data, runTimeGame)
-- Output file.
local file=PROFILER.getfilename("csv")
local g=io.open(file, 'w')
-- Header.
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write(text.."\r\n")
-- Loop over data.
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps=t.count/runTimeGame
-- Output
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
g:write(txt.."\r\n")
end
-- Close file.
g:close()
end
--- Write info to output file.
-- @param #string ext Extension.
-- @return #string File name.
function PROFILER.getfilename(ext)
local dir=lfs.writedir()..[[Logs\]]
ext=ext or PROFILER.fileNameSuffix
local file=dir..PROFILER.fileNamePrefix.."."..ext
if not UTILS.FileExists(file) then
return file
end
for i=1,999 do
local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext)
if not UTILS.FileExists(file) then
return file
end
end
end
--- Write info to output file.
-- @param #number runTimeGame Game time in seconds.
-- @param #number runTimeOS OS time in seconds.
function PROFILER.showInfo(runTimeGame, runTimeOS)
-- Output file.
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
local f=io.open(file, 'w')
-- Gather data.
local Ttot=0
local Calls=0
local t={}
local tcopy=nil --#PROFILER.Data
local tserialize=nil --#PROFILER.Data
local tforgen=nil --#PROFILER.Data
local tpairs=nil --#PROFILER.Data
for func, count in pairs(PROFILER.Counters) do
local s,src,line,tm=PROFILER.getData(func)
if PROFILER.logUnknown==true then
if s==nil then s="<Unknown>" end
end
if s~=nil then
-- Profile data.
local T=
{ func=s,
src=src,
line=line,
count=count,
tm=tm,
} --#PROFILER.Data
-- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data.
if s=="_copy" then
if tcopy==nil then
tcopy=T
else
tcopy.count=tcopy.count+T.count
tcopy.tm=tcopy.tm+T.tm
end
elseif s=="_Serialize" then
if tserialize==nil then
tserialize=T
else
tserialize.count=tserialize.count+T.count
tserialize.tm=tserialize.tm+T.tm
end
elseif s=="(for generator)" then
if tforgen==nil then
tforgen=T
else
tforgen.count=tforgen.count+T.count
tforgen.tm=tforgen.tm+T.tm
end
elseif s=="pairs" then
if tpairs==nil then
tpairs=T
else
tpairs.count=tpairs.count+T.count
tpairs.tm=tpairs.tm+T.tm
end
else
table.insert(t, T)
end
-- Total function time.
Ttot=Ttot+tm
-- Total number of calls.
Calls=Calls+count
end
end
-- Add special cases.
if tcopy then
table.insert(t, tcopy)
end
if tserialize then
table.insert(t, tserialize)
end
if tforgen then
table.insert(t, tforgen)
end
if tpairs then
table.insert(t, tpairs)
end
env.info('############################ Profiler Stopped ############################')
env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
env.info(string.format("* Total functions : %d", #t))
env.info(string.format("* Total func calls : %d", Calls))
env.info(string.format("* Writing to file : \"%s\"", file))
env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv")))
env.info("##############################################################################")
-- Sort by total time.
table.sort(t, function(a,b) return a.tm>b.tm end)
-- Write data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"---- Profiler Report ----")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Total functions = %d", #t))
PROFILER._flog(f,string.format("* Total func calls = %d", Calls))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS))
PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot))
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Sort by number of calls.
table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end)
-- Detailed data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Time per Call ----")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Sort by number of calls.
table.sort(t, function(a,b) return a.count>b.count end)
-- Detailed data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Total Calls ----")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Closing.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
-- Close file.
f:close()
-- Print csv file.
PROFILER.printCSV(t, runTimeGame)
end

View File

@@ -21,6 +21,11 @@ routines.build = 22
-- Utils- conversion, Lua utils, etc.
routines.utils = {}
routines.utils.round = function(number, decimals)
local power = 10^decimals
return math.floor(number * power) / power
end
--from http://lua-users.org/wiki/CopyTable
routines.utils.deepCopy = function(object)
local lookup_table = {}
@@ -1626,6 +1631,11 @@ function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints bu
for point_num, point in pairs(group_data.route.points) do
local routeData = {}
if env.mission.version > 7 then
routeData.name = env.getValueDictByKey(point.name)
else
routeData.name = point.name
end
if not point.point then
routeData.x = point.x
routeData.y = point.y

View File

@@ -1,5 +1,4 @@
--- This module contains derived utilities taken from the MIST framework,
-- which are excellent tools to be reused in an OO environment!.
--- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment.
--
-- ### Authors:
--
@@ -33,18 +32,94 @@ FLARECOLOR = trigger.flareColor -- #FLARECOLOR
--- Big smoke preset enum.
-- @type BIGSMOKEPRESET
BIGSMOKEPRESET = {
SmallSmokeAndFire=0,
MediumSmokeAndFire=1,
LargeSmokeAndFire=2,
HugeSmokeAndFire=3,
SmallSmoke=4,
MediumSmoke=5,
LargeSmoke=6,
HugeSmoke=7,
SmallSmokeAndFire=1,
MediumSmokeAndFire=2,
LargeSmokeAndFire=3,
HugeSmokeAndFire=4,
SmallSmoke=5,
MediumSmoke=6,
LargeSmoke=7,
HugeSmoke=8,
}
--- DCS map as returned by env.mission.theatre.
-- @type DCSMAP
-- @field #string Caucasus Caucasus map.
-- @field #string Normandy Normandy map.
-- @field #string NTTR Nevada Test and Training Range map.
-- @field #string PersianGulf Persian Gulf map.
-- @field #string TheChannel The Channel map.
-- @field #string Syria Syria map.
DCSMAP = {
Caucasus="Caucasus",
NTTR="Nevada",
Normandy="Normandy",
PersianGulf="PersianGulf",
TheChannel="TheChannel",
Syria="Syria",
}
--- See [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns)
-- @type CALLSIGN
CALLSIGN={
-- Aircraft
Aircraft={
Enfield=1,
Springfield=2,
Uzi=3,
Colt=4,
Dodge=5,
Ford=6,
Chevy=7,
Pontiac=8,
-- A-10A or A-10C
Hawg=9,
Boar=10,
Pig=11,
Tusk=12,
},
-- AWACS
AWACS={
Overlord=1,
Magic=2,
Wizard=3,
Focus=4,
Darkstar=5,
},
-- Tanker
Tanker={
Texaco=1,
Arco=2,
Shell=3,
},
-- JTAC
JTAC={
Axeman=1,
Darknight=2,
Warrier=3,
Pointer=4,
Eyeball=5,
Moonbeam=6,
Whiplash=7,
Finger=8,
Pinpoint=9,
Ferret=10,
Shaba=11,
Playboy=12,
Hammer=13,
Jaguar=14,
Deathstar=15,
Anvil=16,
Firefly=17,
Mantis=18,
Badger=19,
},
} --#CALLSIGN
--- Utilities static class.
-- @type UTILS
-- @field #number _MarkID Marker index counter. Running number when marker is added.
UTILS = {
_MarkID = 1
}
@@ -112,28 +187,40 @@ UTILS.IsInstanceOf = function( object, className )
end
--from http://lua-users.org/wiki/CopyTable
--- Deep copy a table. See http://lua-users.org/wiki/CopyTable
-- @param #table object The input table.
-- @return #table Copy of the input table.
UTILS.DeepCopy = function(object)
local lookup_table = {}
-- Copy function.
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, getmetatable(object))
end
local objectreturn = _copy(object)
return objectreturn
end
-- porting in Slmod's serialize_slmod2
--- Porting in Slmod's serialize_slmod2.
-- @param #table tbl Input table.
UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function
lookup_table = {}
@@ -237,6 +324,10 @@ UTILS.MetersToNM = function(meters)
return meters/1852
end
UTILS.MetersToSM = function(meters)
return meters/1609.34
end
UTILS.MetersToFeet = function(meters)
return meters/0.3048
end
@@ -250,7 +341,11 @@ UTILS.FeetToMeters = function(feet)
end
UTILS.KnotsToKmph = function(knots)
return knots* 1.852
return knots * 1.852
end
UTILS.KmphToKnots = function(knots)
return knots / 1.852
end
UTILS.KmphToMps = function( kmph )
@@ -265,23 +360,54 @@ UTILS.MiphToMps = function( miph )
return miph * 0.44704
end
--- Convert meters per second to miles per hour.
-- @param #number mps Speed in m/s.
-- @return #number Speed in miles per hour.
UTILS.MpsToMiph = function( mps )
return mps / 0.44704
end
--- Convert meters per second to knots.
-- @param #number mps Speed in m/s.
-- @return #number Speed in knots.
UTILS.MpsToKnots = function( mps )
return mps * 3600 / 1852
return mps * 1.94384 --3600 / 1852
end
--- Convert knots to meters per second.
-- @param #number knots Speed in knots.
-- @return #number Speed in m/s.
UTILS.KnotsToMps = function( knots )
return knots * 1852 / 3600
return knots / 1.94384 --* 1852 / 3600
end
--- Convert temperature from Celsius to Farenheit.
-- @param #number Celcius Temperature in degrees Celsius.
-- @return #number Temperature in degrees Farenheit.
UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32
end
--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in inHg.
UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714
end
--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in mmHg.
UTILS.hPa2mmHg = function( hPa )
return hPa * 0.7500615613030
end
--- Convert kilo gramms (kg) to pounds (lbs).
-- @param #number kg Mass in kg.
-- @return #number Mass in lbs.
UTILS.kg2lbs = function( kg )
return kg * 2.20462
end
--[[acc:
in DM: decimal point of minutes.
@@ -335,15 +461,16 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
local secFrmtStr -- create the formatting string for the seconds place
secFrmtStr = '%02d'
-- if acc <= 0 then -- no decimal place.
-- secFrmtStr = '%02d'
-- else
-- local width = 3 + acc -- 01.310 - that's a width of 6, for example.
-- secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
-- end
if acc <= 0 then -- no decimal place.
secFrmtStr = '%02d'
else
local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS!
secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end
return string.format('%03d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' '
.. string.format('%03d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi
-- 024<32> 23' 12"N or 024<32> 23' 12.03"N
return string.format('%03d°', latDeg)..string.format('%02d', latMin)..'\''..string.format(secFrmtStr, latSec)..'"'..latHemi..' '
.. string.format('%03d°', lonDeg)..string.format('%02d', lonMin)..'\''..string.format(secFrmtStr, lonSec)..'"'..lonHemi
else -- degrees, decimal minutes.
latMin = UTILS.Round(latMin, acc)
@@ -367,20 +494,40 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end
return string.format('%03d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' '
.. string.format('%03d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi
-- 024 23'N or 024 23.123'N
return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' '
.. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi
end
end
-- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
if acc == 0 then
return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
else
return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', UTILS.Round(MGRS.Easting/(10^(5-acc)), 0))
.. ' ' .. string.format('%0' .. acc .. 'd', UTILS.Round(MGRS.Northing/(10^(5-acc)), 0))
-- Test if Easting/Northing have less than 4 digits.
--MGRS.Easting=123 -- should be 00123
--MGRS.Northing=5432 -- should be 05432
-- Truncate rather than round MGRS grid!
local Easting=tostring(MGRS.Easting)
local Northing=tostring(MGRS.Northing)
-- Count number of missing digits. Easting/Northing should have 5 digits. However, it is passed as a number. Therefore, any leading zeros would not be displayed by lua.
local nE=5-string.len(Easting)
local nN=5-string.len(Northing)
-- Get leading zeros (if any).
for i=1,nE do Easting="0"..Easting end
for i=1,nN do Northing="0"..Northing end
-- Return MGRS string.
return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc))
end
end
@@ -425,6 +572,57 @@ function UTILS.spairs( t, order )
end
end
-- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first.
function UTILS.kpairs( t, getkey, order )
-- collect the keys
local keys = {}
local keyso = {}
for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keyso[i], t[keys[i]]
end
end
end
-- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order.
function UTILS.rpairs( t )
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
local random = {}
local j = #keys
for i = 1, j do
local k = math.random( 1, #keys )
random[i] = keys[k]
table.remove( keys, k )
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if random[i] then
return random[i], t[random[i]]
end
end
end
-- get a new mark ID for markings
function UTILS.GetMarkID()
@@ -512,8 +710,9 @@ end
--- Convert time in seconds to hours, minutes and seconds.
-- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function.
-- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day.
-- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D).
function UTILS.SecondsToClock(seconds)
function UTILS.SecondsToClock(seconds, short)
-- Nil check.
if seconds==nil then
@@ -526,20 +725,49 @@ function UTILS.SecondsToClock(seconds)
-- Seconds of this day.
local _seconds=seconds%(60*60*24)
if seconds <= 0 then
if seconds<0 then
return nil
else
local hours = string.format("%02.f", math.floor(_seconds/3600))
local mins = string.format("%02.f", math.floor(_seconds/60 - (hours*60)))
local secs = string.format("%02.f", math.floor(_seconds - hours*3600 - mins *60))
local days = string.format("%d", seconds/(60*60*24))
return hours..":"..mins..":"..secs.."+"..days
local clock=hours..":"..mins..":"..secs.."+"..days
if short then
if hours=="00" then
--clock=mins..":"..secs
clock=hours..":"..mins..":"..secs
else
clock=hours..":"..mins..":"..secs
end
end
return clock
end
end
--- Seconds of today.
-- @return #number Seconds passed since last midnight.
function UTILS.SecondsOfToday()
-- Time in seconds.
local time=timer.getAbsTime()
-- Short format without days since mission start.
local clock=UTILS.SecondsToClock(time, true)
-- Time is now the seconds passed since last midnight.
return UTILS.ClockToSeconds(clock)
end
--- Cound seconds until next midnight.
-- @return #number Seconds to midnight.
function UTILS.SecondsToMidnight()
return 24*60*60-UTILS.SecondsOfToday()
end
--- Convert clock time from hours, minutes and seconds to seconds.
-- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days.
-- @param #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
-- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
function UTILS.ClockToSeconds(clock)
-- Nil check.
@@ -551,7 +779,7 @@ function UTILS.ClockToSeconds(clock)
local seconds=0
-- Split additional days.
local dsplit=UTILS.split(clock, "+")
local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds.
if #dsplit>1 then
@@ -593,6 +821,15 @@ function UTILS.DisplayMissionTime(duration)
MESSAGE:New(text, duration):ToAll()
end
--- Replace illegal characters [<>|/?*:\\] in a string.
-- @param #string Text Input text.
-- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_".
-- @return #string The input text with illegal chars replaced.
function UTILS.ReplaceIllegalCharacters(Text, ReplaceBy)
ReplaceBy=ReplaceBy or "_"
local text=Text:gsub("[<>|/?*:\\]", ReplaceBy)
return text
end
--- Generate a Gaussian pseudo-random number.
-- @param #number x0 Expectation value of distribution.
@@ -680,3 +917,531 @@ function UTILS.VecCross(a, b)
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
end
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z.
function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end
--- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a+b with c(i)=a(i)+b(i), i=x,y,z.
function UTILS.VecAdd(a, b)
return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z}
end
--- Calculate the angle between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product).
function UTILS.VecAngle(a, b)
local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b))
local alpha=0
if cosalpha>=0.9999999999 then --acos(1) is not defined.
alpha=0
elseif cosalpha<=-0.999999999 then --acos(-1) is not defined.
alpha=math.pi
else
alpha=math.acos(cosalpha)
end
return math.deg(alpha)
end
--- Calculate "heading" of a 3D vector in the X-Z plane.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @return #number Heading in degrees in [0,360).
function UTILS.VecHdg(a)
local h=math.deg(math.atan2(a.z, a.x))
if h<0 then
h=h+360
end
return h
end
--- Calculate the difference between two "heading", i.e. angles in [0,360) deg.
-- @param #number h1 Heading one.
-- @param #number h2 Heading two.
-- @return #number Heading difference in degrees.
function UTILS.HdgDiff(h1, h2)
-- Angle in rad.
local alpha= math.rad(tonumber(h1))
local beta = math.rad(tonumber(h2))
-- Runway vector.
local v1={x=math.cos(alpha), y=0, z=math.sin(alpha)}
local v2={x=math.cos(beta), y=0, z=math.sin(beta)}
local delta=UTILS.VecAngle(v1, v2)
return math.abs(delta)
end
--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number distance The distance to translate.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.VecTranslate(a, distance, angle)
local SX = a.x
local SY = a.z
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return {x=TX, y=a.y, z=TY}
end
--- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X, y=Y, z=Z}
return A
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz.
-- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X".
-- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X".
-- @return #number Frequency in Hz or #nil if parameters are invalid.
function UTILS.TACANToFrequency(TACANChannel, TACANMode)
if type(TACANChannel) ~= "number" then
return nil -- error in arguments
end
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end
--- Returns the DCS map/theatre as optained by env.mission.theatre
-- @return #string DCS map name.
function UTILS.GetDCSMap()
return env.mission.theatre
end
--- Returns the mission date. This is the date the mission **started**.
-- @return #string Mission date in yyyy/mm/dd format.
-- @return #number The year anno domini.
-- @return #number The month.
-- @return #number The day.
function UTILS.GetDCSMissionDate()
local year=tostring(env.mission.date.Year)
local month=tostring(env.mission.date.Month)
local day=tostring(env.mission.date.Day)
return string.format("%s/%s/%s", year, month, day), tonumber(year), tonumber(month), tonumber(day)
end
--- Returns the day of the mission.
-- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime().
-- @return #number Day of the mission. Mission starts on day 0.
function UTILS.GetMissionDay(Time)
Time=Time or timer.getAbsTime()
local clock=UTILS.SecondsToClock(Time, false)
local x=tonumber(UTILS.Split(clock, "+")[2])
return x
end
--- Returns the current day of the year of the mission.
-- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime().
-- @return #number Current day of year of the mission. For example, January 1st returns 0, January 2nd returns 1 etc.
function UTILS.GetMissionDayOfYear(Time)
local Date, Year, Month, Day=UTILS.GetDCSMissionDate()
local d=UTILS.GetMissionDay(Time)
return UTILS.GetDayOfYear(Year, Month, Day)+d
end
--- Returns the current date.
-- @return #string Mission date in yyyy/mm/dd format.
-- @return #number The year anno domini.
-- @return #number The month.
-- @return #number The day.
function UTILS.GetDate()
-- Mission start date
local date, year, month, day=UTILS.GetDCSMissionDate()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time, false)
local x=tonumber(UTILS.Split(clock, "+")[2])
local day=day+x
end
--- Returns the magnetic declination of the map.
-- Returned values for the current maps are:
--
-- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944
-- * Persian Gulf +2 (East), year ~ 2011
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map)
-- Map.
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
elseif map==DCSMAP.NTTR then
declination=12
elseif map==DCSMAP.Normandy then
declination=-10
elseif map==DCSMAP.PersianGulf then
declination=2
elseif map==DCSMAP.TheChannel then
declination=-10
elseif map==DCSMAP.Syria then
declination=5
else
declination=0
end
return declination
end
--- Checks if a file exists or not. This requires **io** to be desanitized.
-- @param #string file File that should be checked.
-- @return #boolean True if the file exists, false if the file does not exist or nil if the io module is not available and the check could not be performed.
function UTILS.FileExists(file)
if io then
local f=io.open(file, "r")
if f~=nil then
io.close(f)
return true
else
return false
end
else
return nil
end
end
--- Checks the current memory usage collectgarbage("count"). Info is printed to the DCS log file. Time stamp is the current mission runtime.
-- @param #boolean output If true, print to DCS log file.
-- @return #number Memory usage in kByte.
function UTILS.CheckMemory(output)
local time=timer.getTime()
local clock=UTILS.SecondsToClock(time)
local mem=collectgarbage("count")
if output then
env.info(string.format("T=%s Memory usage %d kByte = %.2f MByte", clock, mem, mem/1024))
end
return mem
end
--- Get the coalition name from its numerical ID, e.g. coaliton.side.RED.
-- @param #number Coalition The coalition ID.
-- @return #string The coalition name, i.e. "Neutral", "Red" or "Blue" (or "Unknown").
function UTILS.GetCoalitionName(Coalition)
if Coalition then
if Coalition==coalition.side.BLUE then
return "Blue"
elseif Coalition==coalition.side.RED then
return "Red"
elseif Coalition==coalition.side.NEUTRAL then
return "Neutral"
else
return "Unknown"
end
else
return "Unknown"
end
end
--- Get the modulation name from its numerical value.
-- @param #number Modulation The modulation enumerator number. Can be either 0 or 1.
-- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown".
function UTILS.GetModulationName(Modulation)
if Modulation then
if Modulation==0 then
return "AM"
elseif Modulation==1 then
return "FM"
else
return "Unknown"
end
else
return "Unknown"
end
end
--- Get the callsign name from its enumerator value
-- @param #number Callsign The enumerator callsign.
-- @return #string The callsign name or "Ghostrider".
function UTILS.GetCallsignName(Callsign)
for name, value in pairs(CALLSIGN.Aircraft) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.AWACS) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.JTAC) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.Tanker) do
if value==Callsign then
return name
end
end
return "Ghostrider"
end
--- Get the time difference between GMT and local time.
-- @return #number Local time difference in hours compared to GMT. E.g. Dubai is GMT+4 ==> +4 is returned.
function UTILS.GMTToLocalTimeDifference()
local theatre=UTILS.GetDCSMap()
if theatre==DCSMAP.Caucasus then
return 4 -- Caucasus UTC+4 hours
elseif theatre==DCSMAP.PersianGulf then
return 4 -- Abu Dhabi UTC+4 hours
elseif theatre==DCSMAP.NTTR then
return -8 -- Las Vegas UTC-8 hours
elseif theatre==DCSMAP.Normandy then
return 0 -- Calais UTC+1 hour
elseif theatre==DCSMAP.TheChannel then
return 2 -- This map currently needs +2
elseif theatre==DCSMAP.Syria then
return 3 -- Damascus is UTC+3 hours
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0
end
end
--- Get the day of the year. Counting starts on 1st of January.
-- @param #number Year The year.
-- @param #number Month The month.
-- @param #number Day The day.
-- @return #number The day of the year.
function UTILS.GetDayOfYear(Year, Month, Day)
local floor = math.floor
local n1 = floor(275 * Month / 9)
local n2 = floor((Month + 9) / 12)
local n3 = (1 + floor((Year - 4 * floor(Year / 4) + 2) / 3))
return n1 - (n2 * n3) + Day - 30
end
--- Get sunrise or sun set of a specific day of the year at a specific location.
-- @param #number DayOfYear The day of the year.
-- @param #number Latitude Latitude.
-- @param #number Longitude Longitude.
-- @param #boolean Rising If true, calc sun rise, or sun set otherwise.
-- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4.
-- @return #number Sun rise/set in seconds of the day.
function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
-- Defaults
local zenith=90.83
local latitude=Latitude
local longitude=Longitude
local rising=Rising
local n=DayOfYear
Tlocal=Tlocal or 0
-- Short cuts.
local rad = math.rad
local deg = math.deg
local floor = math.floor
local frac = function(n) return n - floor(n) end
local cos = function(d) return math.cos(rad(d)) end
local acos = function(d) return deg(math.acos(d)) end
local sin = function(d) return math.sin(rad(d)) end
local asin = function(d) return deg(math.asin(d)) end
local tan = function(d) return math.tan(rad(d)) end
local atan = function(d) return deg(math.atan(d)) end
local function fit_into_range(val, min, max)
local range = max - min
local count
if val < min then
count = floor((min - val) / range) + 1
return val + count * range
elseif val >= max then
count = floor((val - max) / range) + 1
return val - count * range
else
return val
end
end
-- Convert the longitude to hour value and calculate an approximate time
local lng_hour = longitude / 15
local t
if rising then -- Rising time is desired
t = n + ((6 - lng_hour) / 24)
else -- Setting time is desired
t = n + ((18 - lng_hour) / 24)
end
-- Calculate the Sun's mean anomaly
local M = (0.9856 * t) - 3.289
-- Calculate the Sun's true longitude
local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360)
-- Calculate the Sun's right ascension
local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360)
-- Right ascension value needs to be in the same quadrant as L
local Lquadrant = floor(L / 90) * 90
local RAquadrant = floor(RA / 90) * 90
RA = RA + Lquadrant - RAquadrant
-- Right ascension value needs to be converted into hours
RA = RA / 15
-- Calculate the Sun's declination
local sinDec = 0.39782 * sin(L)
local cosDec = cos(asin(sinDec))
-- Calculate the Sun's local hour angle
local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))
if rising and cosH > 1 then
return "N/R" -- The sun never rises on this location on the specified date
elseif cosH < -1 then
return "N/S" -- The sun never sets on this location on the specified date
end
-- Finish calculating H and convert into hours
local H
if rising then
H = 360 - acos(cosH)
else
H = acos(cosH)
end
H = H / 15
-- Calculate local mean time of rising/setting
local T = H + RA - (0.06571 * t) - 6.622
-- Adjust back to UTC
local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24)
return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60
end
--- Get sun rise of a specific day of the year at a specific location.
-- @param #number Day Day of the year.
-- @param #number Month Month of the year.
-- @param #number Year Year.
-- @param #number Latitude Latitude.
-- @param #number Longitude Longitude.
-- @param #boolean Rising If true, calc sun rise, or sun set otherwise.
-- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0.
-- @return #number Sun rise in seconds of the day.
function UTILS.GetSunrise(Day, Month, Year, Latitude, Longitude, Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day)
return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tlocal)
end
--- Get sun set of a specific day of the year at a specific location.
-- @param #number Day Day of the year.
-- @param #number Month Month of the year.
-- @param #number Year Year.
-- @param #number Latitude Latitude.
-- @param #number Longitude Longitude.
-- @param #boolean Rising If true, calc sun rise, or sun set otherwise.
-- @param #number Tlocal Local time offset in hours. E.g. +4 for a location which has GMT+4. Default 0.
-- @return #number Sun rise in seconds of the day.
function UTILS.GetSunset(Day, Month, Year, Latitude, Longitude, Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day)
return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tlocal)
end
--- Get OS time. Needs os to be desanitized!
-- @return #number Os time in seconds.
function UTILS.GetOSTime()
if os then
return os.clock()
end
return nil
end

File diff suppressed because it is too large Load Diff

View File

@@ -133,7 +133,8 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error )
end
function CLIENT:Register( ClientName )
local self = BASE:Inherit( self, UNIT:Register( ClientName ) )
local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT
self:F( ClientName )
self.ClientName = ClientName
@@ -141,7 +142,8 @@ function CLIENT:Register( ClientName )
self.ClientAlive2 = false
--self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 )
self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 )
self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5, 0.5 )
self.AliveCheckScheduler:NoTrace()
self:F( self )
return self

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -90,7 +90,6 @@ end
--- Returns the type name of the DCS Identifiable.
-- @param #IDENTIFIABLE self
-- @return #string The type name of the DCS Identifiable.
-- @return #nil The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:GetTypeName()
self:F2( self.IdentifiableName )
@@ -107,9 +106,17 @@ function IDENTIFIABLE:GetTypeName()
end
--- Returns category of the DCS Identifiable.
--- Returns object category of the DCS Identifiable. One of
--
-- * Object.Category.UNIT = 1
-- * Object.Category.WEAPON = 2
-- * Object.Category.STATIC = 3
-- * Object.Category.BASE = 4
-- * Object.Category.SCENERY = 5
-- * Object.Category.Cargo = 6
--
-- @param #IDENTIFIABLE self
-- @return DCS#Object.Category The category ID
-- @return DCS#Object.Category The category ID, i.e. a number.
function IDENTIFIABLE:GetCategory()
self:F2( self.ObjectName )
@@ -168,20 +175,13 @@ function IDENTIFIABLE:GetCoalitionName()
local DCSIdentifiable = self:GetDCSObject()
if DCSIdentifiable then
-- Get coaliton ID.
local IdentifiableCoalition = DCSIdentifiable:getCoalition()
self:T3( IdentifiableCoalition )
if IdentifiableCoalition == coalition.side.BLUE then
return "Blue"
end
return UTILS.GetCoalitionName(IdentifiableCoalition)
if IdentifiableCoalition == coalition.side.RED then
return "Red"
end
if IdentifiableCoalition == coalition.side.NEUTRAL then
return "Neutral"
end
end
self:F( self.ClassName .. " " .. self.IdentifiableName .. " not found!" )

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