Compare commits

...

623 Commits

Author SHA1 Message Date
Applevangelist
0971f8ffa7 #SPAWN
* Fix for InitCleanup()
2022-09-22 10:57:43 +02:00
Applevangelist
9739f94428 #ATIS
* Add MSRSQUEUE to avoid overlaps
2022-09-20 17:15:57 +02:00
Applevangelist
450f4eaec0 #AIRBASE, ATIS
* less noise in log

#SRS
* Added enumerator for voices MSRS.Voices.Microsoft... and MSRS.Voices.Google...

#RANGE
* Added SRS support, some bug fixing
2022-09-20 15:51:16 +02:00
Applevangelist
b5186fa328 #SETTINGS
* Fix IsImperial()
2022-09-20 15:26:34 +02:00
Applevangelist
526dc8363d #DESIGNATE
* Remove useless summary autolase report, restored single lines when lasing
2022-09-20 10:26:19 +02:00
Applevangelist
242d735d8c #DETECTION
* Make designate refer A2G coordinate system as set by user
2022-09-20 09:27:19 +02:00
Applevangelist
9fedd32e78 #UTILS
* Fix for group set reloading - thanks @BlueEcko!
2022-09-20 08:23:49 +02:00
Applevangelist
a146003a6a #DESIGNATE
* Some documentation clarifications
2022-09-19 18:25:54 +02:00
Applevangelist
d17df74191 ATIS
* Remove dupe inheritance of BASE
2022-09-19 13:20:45 +02:00
Applevangelist
d23403383f #CSAR
* Added option to use the OV-10A Bronco
* Slight changes to SRS approach
* Added door check in UTILS for Bronco
2022-09-19 11:43:50 +02:00
Applevangelist
877b36f8a8 #CSAR
* overcome error cannot get Vec3
2022-09-15 19:51:15 +02:00
Applevangelist
299820162b #ATIS
* No old-style radioqueue when using SRS
2022-09-15 14:01:23 +02:00
Applevangelist
5d20a59d92 #GROUP
* Customized Callsigns - remove extra spaces
2022-09-15 13:40:37 +02:00
Applevangelist
c1ab3b5476 #GROUP
* Fix ignore leading spaces in customized callsigns
2022-09-15 08:17:37 +02:00
Applevangelist
0076130483 #GROUP
* Docu clarifications
2022-09-13 10:51:25 +02:00
Applevangelist
a5194b1999 #GROUP
-- remove log output
2022-09-12 17:33:40 +02:00
Applevangelist
b53b1f083f #GROUP
* Fix IsPlayer
* Added `GROUP:GetCustomCallSign()`
2022-09-12 17:28:49 +02:00
Frank
994a36001f Merge pull request #1782 from FlightControl-Master/FF/MasterDevel
Updates from develop
2022-09-11 13:38:00 +02:00
Frank
26b91e3f66 Updates from develop
DCS.lua
Ops/ATIS.lua
Ops/RecoveryTanker.lua
Utilities/Enums.lua
2022-09-11 13:32:57 +02:00
Applevangelist
f42aedd485 #CONTROLLABLE
*Foreign class definiton removed
2022-09-10 15:14:28 +02:00
Applevangelist
a73818a615 #BUILD files 2022-09-10 12:08:30 +02:00
Applevangelist
7c22e9fe69 #Changes from Develop 2022-09-10 11:56:03 +02:00
Applevangelist
c58e91b956 #TASKING
* Merge changes from Develop
2022-09-10 11:41:41 +02:00
Applevangelist
a61059242c #AI
* Merge changes from DEV
2022-09-10 11:38:50 +02:00
Applevangelist
cea9437e66 #GROUP
Fix GetSpeedMax() always returns zero
2022-09-09 17:33:23 +02:00
Applevangelist
3f052ef1da #GROUP
* changes from Dev
#UNIT
* changes from Dev
2022-09-08 15:49:58 +02:00
Applevangelist
c137a4b06d Revert "#ZONE"
This reverts commit 1207417894.
2022-09-06 13:18:22 +02:00
Applevangelist
6c46dafc59 Revert "Auxiliary commit to revert individual files from 1207417894a369fa4a353ca0a1f6120c2fc32666"
This reverts commit 551d4a9c85c89e4aaad2c59ed058c84ff8c7ad6c.
2022-09-06 13:16:40 +02:00
Applevangelist
1207417894 #ZONE
* ZONE_ELASTIC
* Typo
2022-09-06 11:17:50 +02:00
Frank
7c717c219c Merge pull request #1771 from grandpaSam/master
Updated CARGO tag documentation
2022-09-01 22:54:22 +02:00
grandpaSam
a9fad53255 Updated CARGO tag documentation
The documentation for the CARGO tag on static objects omitted a required C= parameter. Without this parameter the database will not register the static as a CARGO object
2022-09-01 11:49:46 -07:00
Applevangelist
372cc021bd UNIT - added optical tracker to HasSEAD() 2022-09-01 08:36:40 +02:00
Applevangelist
6820774266 GROUP - added optical tracker to ID SAMs 2022-09-01 08:03:53 +02:00
Applevangelist
a0c189a349 UTILS - added NDB 305kHz in frequency generator 2022-08-31 18:11:55 +02:00
Applevangelist
1060ff16d8 GROUP - Attributes aligned to dev 2022-08-31 17:40:44 +02:00
Applevangelist
1255f48645 Update Moose.files 2022-08-30 14:38:36 +02:00
Applevangelist
7ef69208d4 #ZONE_RADIUS
* Added `ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(...)`

#MARKEROPS_BASE
* Added Class
2022-08-30 14:36:42 +02:00
Applevangelist
5277cca4e1 Range fix issue #1753 (#1767)
Issue #1753 - when using `AddBombingTargets` the randommove flag was not passed to `AddBombingTargetUnit` for a group object
2022-08-25 16:00:47 +02:00
Applevangelist
aaf77815ca #SRS
* Added MSRSQUEUE message queue
2022-08-25 10:50:45 +02:00
Applevangelist
3f488cc091 #AIRBASE
* Added 3 new airports to the enumerator SouthAtlantic
2022-08-25 10:38:44 +02:00
Applevangelist
bdbbdfe60e #RANGE
* Added Instructor and RangeControl radio info on F10
2022-08-23 09:58:50 +02:00
Applevangelist
d15c2be2d0 Range fixes (#1764)
* Fixed some typos in self.PlayerSettings
* Fixed result messages
* Thanks for [JFG] Caponi
2022-08-22 17:03:33 +02:00
Applevangelist
73994914eb Fix for #1763 UTILS.GetOSTime() (#1765)
* Fix GetOSTime fixes #1763
2022-08-22 17:03:21 +02:00
Applevangelist
63b0dae794 Update ATC_Ground.lua (#1766)
* Make ATC_Ground work on any map
2022-08-22 17:03:02 +02:00
Engines
003e865ff7 Late break bug (#1762)
* Quick update to Airboss.lua to inluce Invincible.  Not yet tested.

* Initial quick test and calibration of the landing spot; looks good.  More testing required.

* Recompiled the Moose.lua based off the last commit.

* Removing surpurflous Moose.lua for pull request.

* There was an error with the late break paratemers on the Brit carriers causing pilots to be thrown off the pattern on the break.  Fixed, tested on Invicible, working as anticipated now.

* Removing the test Moose.lua (again).

* Fixing merge confilict

* Trying again.
2022-08-15 07:15:15 +02:00
Gavin Edwards
e3c03287b7 Engines invincible (#1758)
* Quick update to Airboss.lua to inluce Invincible.  Not yet tested.

* Initial quick test and calibration of the landing spot; looks good.  More testing required.

* Recompiled the Moose.lua based off the last commit.

* Removing surpurflous Moose.lua for pull request.
2022-08-13 21:40:53 +02:00
Applevangelist
73493c3a23 Update Unit.lua (#1751) 2022-07-31 09:17:36 +02:00
Applevangelist
562a3f6208 SET - fix for dead units 2022-07-29 08:51:23 +02:00
Applevangelist
3c5f3d6c37 UNIT - added get altitude function with AGL option 2022-07-25 08:12:28 +02:00
Applevangelist
a37d4214c0 SET - fix for left over self:I() 2022-07-22 11:06:55 +02:00
Applevangelist
636d6ce324 AIRBASE - Added 2 AFB on the Falklands Map (#1748)
--@field MarianaIslands
AIRBASE.SouthAtlantic={
  ["Port_Stanley"]="Port Stanley",
  ["Mount_Pleasant"]="Mount Pleasant",
  ["San_Carlos_FOB"]="San Carlos FOB",
  ["Rio_Grande"]="Rio Grande",
  ["Rio_Gallegos"]="Rio Gallegos",
  ["Ushuaia"]="Ushuaia",
  ["Ushuaia_Helo_Port"]="Ushuaia Helo Port",
  ["Punta_Arenas"]="Punta Arenas",
  ["Pampa_Guanaco"]="Pampa Guanaco",
  ["San_Julian"]="San Julian",
}
2022-07-22 11:02:55 +02:00
Applevangelist
eef8b362d2 Beacon - added deactivate Link4 2022-07-19 08:29:43 +02:00
Applevangelist
4ae586ebaa Controllable/Beacon - added function to switch on Link4
A2A - typo in documentation
2022-07-19 08:10:54 +02:00
rfdazzle
f6e673c2bb Fixed ATIS TTS readouts for wind direction & TACAN (#1739)
Added a substitution that takes effect when self.useSRS which converts wind direction into aviation-speak, e.g. "Zero Seven One" instead of the previous behaviour which was "Zero Seventy-One".

Updated TACAN TTS string to include the 'Ray' in 'X-Ray' when SRS is in use
2022-07-07 08:41:56 +02:00
Frank
183a60159c Update Range.lua
- Fixed bug for strafing
2022-07-01 23:05:26 +02:00
Chump
1fdf4f371d Fix for issues #1735 & #1736 (#1737)
* Update Database.lua

Remove duplicate function

* Update PseudoATC.lua

Added nil check
2022-06-26 21:11:49 +01:00
Applevangelist
f50c374d04 CSAR - hand back descriptive name as 3rd parameter on event Boarded() 2022-06-25 17:24:56 +02:00
Applevangelist
d59fc331f6 UTILS - Fix for Gazelle Door Check 2022-06-25 14:27:51 +02:00
Applevangelist
b83f478294 CSAR - fix for oncrash 2022-06-16 15:42:02 +02:00
Applevangelist
d5636f4a19 CSAR - added event "Landed" (at a friendly/neutral AFB), fix for AFB rescue 2022-06-16 13:41:44 +02:00
Applevangelist
196bcf39cf Added Falklands map stuff 2022-06-14 16:56:33 +02:00
Applevangelist
afec1c3a5b COORDINATE - additions to BRAANATO 2022-06-14 13:06:55 +02:00
Applevangelist
6025339b46 CSAR - some fixes for latest open beta 2022-06-14 12:39:17 +02:00
Applevangelist
40c6cc59d3 Update Beacon.lua 2022-06-13 15:43:10 +02:00
Applevangelist
514e568e04 Update Beacon.lua 2022-06-13 15:39:39 +02:00
Applevangelist
2f34b0a5ed Update Beacon.lua (#1734) 2022-06-13 15:34:01 +02:00
Applevangelist
708c076885 CSAR - Put wounded group back into status green, so they run to the chopper 2022-06-12 12:47:23 +02:00
Applevangelist
f0e0b918af CTLD - More docu 2022-06-09 12:12:29 +02:00
Applevangelist
e45f5e1122 CTLD - further documentation 2022-06-09 11:46:29 +02:00
Applevangelist
932015668b Added documentation for CTLD_HERCULES 2022-06-09 10:56:20 +02:00
Applevangelist
4011bc3fe6 AIRBASE added South Atlantic 2022-06-08 20:24:45 +02:00
Applevangelist
1dcccdc434 CSAR - added a couple of more lines to go out via TTS 2022-06-07 08:56:13 +02:00
Applevangelist
3380ed9360 CSAR - added options to use Google TTS 2022-06-07 08:13:19 +02:00
Applevangelist
ed9c14e63d GROUP - changes in GetDCSGroup 2022-05-22 12:06:49 +02:00
Applevangelist
4762793adc Create README.md 2022-05-22 11:16:16 +02:00
Frank
7df3946189 Update Airboss.lua
- Wind is calculated at 15 m (not 50 m)
2022-05-20 20:16:45 +02:00
Applevangelist
d5fb75fe43 Positionable - added 6 passengers (cargo weight) to Toyota HL/LC new with 2.7.2 OB 2022-05-14 13:11:30 +02:00
Applevangelist
c0b32a5584 SRS - added hints on using Google with TTS 2022-05-13 16:37:41 +02:00
Applevangelist
e2b1276d7b Point - added option to add an SSML tag to ToStringBRAANATO 2022-05-12 14:49:30 +02:00
Applevangelist
f6aea13fae SRS - put volume in "" - just in case 2022-05-11 07:30:26 +02:00
Applevangelist
646b113c55 SRS - actually pass the volume to the command line 2022-05-11 06:19:09 +02:00
Applevangelist
58074f499f AIRBASE - corrected ["Deir_ez_Zor"] = "Deir ez-Zor" (minus doesn't work in enum) 2022-05-10 19:40:32 +02:00
Applevangelist
1483ffd7ff Correct link to demo missions 2022-05-10 16:18:00 +02:00
476th-Scaley
cdaef851a0 Update AI_Air.lua (#1729)
* Update AI_Air.lua

Altered RTB airspeed (slower) and target altitude over the airfield being returned to (higher) to produce more realistic and fuel efficient descent profiles. Leads to aircraft arriving overhead the airfield quite high and generally flying one orbit to descend to land. 

Scaley

* Create AI_Air.lua

Co-authored-by: Applevangelist <72444570+Applevangelist@users.noreply.github.com>
2022-05-10 10:10:41 +02:00
Applevangelist
04068d7117 Group - small change 2022-05-07 19:42:31 +02:00
Applevangelist
41e8ddea8c GROUP - change to GetUnits(n) to make it more robust, now returns first alive unit,actually. Similar changes to GetHeading() 2022-05-07 11:54:33 +02:00
Applevangelist
cc49791997 Fallout fixes 2022-05-06 11:45:11 +02:00
Applevangelist
ba4a8050ba AI Patrol - life check on route 2022-05-06 10:36:39 +02:00
Applevangelist
7c5067a59a UNIT Register - small fix for trains 2022-05-06 08:01:31 +02:00
Applevangelist
69eb920173 AI_CAP - more fallout from the dead units in the API 2022-05-05 17:40:00 +02:00
Applevangelist
07d761941a AI_AIR - restrict AB on RTB 2022-05-05 16:41:32 +02:00
Applevangelist
ca52585759 AI/ZONE - Some fixes for units unreachable 2022-05-05 12:07:56 +02:00
Applevangelist
decc9d09f8 docu update 2022-05-05 11:34:14 +02:00
Applevangelist
466a18447c SRS - adding volume setting and a test on OS and IO available 2022-05-05 08:57:27 +02:00
Frank
27902ee107 Update Range.lua
**RANGE**
- Fixed a couple of bugs
- Added new FSM events for strafing
- Updated docs
2022-05-04 22:36:41 +02:00
Applevangelist
8a8b806362 further event related stuff not working any more 2022-05-04 18:09:56 +02:00
Applevangelist
40bb181c78 Another nil check... 2022-05-04 13:29:40 +02:00
Applevangelist
8099847e29 Fixes for DEAD event and extra nil checks 2022-05-04 10:25:50 +02:00
Applevangelist
6e8edd95ec GROUP - making GetCoordinate() a bit more resilient
POINT - slight changes to ToStringBRAANATO
2022-04-29 18:48:41 +02:00
Applevangelist
5112c9598b COORDINATE - added bogey option to COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) 2022-04-29 12:18:26 +02:00
Applevangelist
749158c086 Range added nil check 2022-04-29 12:09:57 +02:00
Applevangelist
b3d4024f21 Nicefy docs 2022-04-28 17:10:26 +02:00
Applevangelist
c283b66c1d added MESSAGE:ToUnit), altered MESSAGE:ToClient() accordingly 2022-04-28 16:58:37 +02:00
Applevangelist
3209843318 Added POSITIONABLE:MessageToSetUnit and POSITIONABLE:MessageToUnit 2022-04-28 16:55:31 +02:00
Applevangelist
d35e5cc0f7 Added USERSOUND:ToUnit 2022-04-28 16:50:59 +02:00
Applevangelist
e5eeb592a2 Enums - corrected Hawkeye, added Super_Hercules 2022-04-28 13:10:34 +02:00
Applevangelist
3d38f4d17a Enums - added a couple of names 2022-04-26 10:08:50 +02:00
Applevangelist
2d91647e0b QOL changes from DEVELOP 2022-04-25 10:36:36 +02:00
Applevangelist
cac0f30673 Update Moose.files (#1717) 2022-04-25 10:35:38 +02:00
Applevangelist
c5ecba3389 Menu cleanup for Refresh() 2022-04-24 14:05:45 +02:00
Applevangelist
e0397dff47 GROUP - overwrite GetHeight() inherited from POSITIONABLE with something that is actually working for groups 2022-04-24 13:50:07 +02:00
Applevangelist
e08fb2e972 Make BRAA heading a 3digit number 2022-04-22 13:31:58 +02:00
Applevangelist
c02ae82003 Point - added ToStringBRAANATO 2022-04-21 18:59:31 +02:00
Applevangelist
e6fc301b0d Utils Typo 2022-04-20 19:14:36 +02:00
Applevangelist
a385ed57fb ZONE - added example to Scan, some minor changes
SET_GROUP - clarified return value to be a table, not a SET
2022-04-20 14:03:18 +02:00
Applevangelist
09dafe4b1d UTILS - added BearingToCardinal, ToStringBRAANATO 2022-04-20 14:02:16 +02:00
Applevangelist
ba8505c983 FIFO 2022-04-14 15:54:38 +02:00
Applevangelist
fba359d389 LIFO/FIFO enforce unique id 2022-04-14 15:06:14 +02:00
Applevangelist
c56763b68f FIFO:HasUniqueID(UniqueID) 2022-04-14 11:11:33 +02:00
Applevangelist
03c2943545 Nicefy docs 2022-04-14 08:53:37 +02:00
Applevangelist
98039b9048 UTISL - FiFo/LiFo stacks 2022-04-14 08:12:29 +02:00
Applevangelist
5065d3b068 UTILS - added FiFo
CTLD - correct imperial hover check messages
2022-04-13 16:13:39 +02:00
Applevangelist
6828f7e262 UTILS - corrected door check for AH64 2022-04-12 08:23:18 +02:00
Applevangelist
a685f3ffbd MSRS - honor port setting and coalition. Repaired command string for .bat sound file production
Added mission slash in SOUNDFILE
2022-04-10 18:29:09 +02:00
Applevangelist
e84156d2e9 SRS - add port to docu 2022-04-10 10:18:12 +02:00
Applevangelist
0ce1c31e1c CSAR - added SRS port option 2022-04-10 09:29:22 +02:00
Applevangelist
f7e14bb60c SET - logic correction in :Remove() 2022-04-08 11:27:31 +02:00
Penecruz
7b907df816 Airboss V/STOL Case III and grading (#1708)
* Update Airboss.lua

* Fix syntax error

C
2022-04-08 07:16:23 +02:00
Frank
7f5be2829c Update Database.lua
- Template statics saved under first statics unit name so they can be found.
2022-04-04 16:50:17 +02:00
Applevangelist
fa5afae783 A2A Dispatcher - nil checks to evade dead units 2022-04-04 12:58:32 +02:00
Frank
b17507d0fa Update SpawnStatic.lua
- Fixed isCargo flag not honored.
2022-04-04 12:18:45 +02:00
Applevangelist
5ed43a3190 CTLD - build object at the 1st crate location not randomly around helo 2022-04-04 11:31:35 +02:00
Applevangelist
d2a5144a23 AIRBASE, added ["Deir_ez-Zor"] = "Deir ez-Zor", 2022-03-30 12:06:25 +02:00
Applevangelist
fb2031d7ca docu nicefy 2022-03-29 12:01:28 +02:00
Applevangelist
4a42571925 CTLD - corrected error in setting Hercules min and max drop height, added documentation 2022-03-29 08:52:33 +02:00
Applevangelist
38413625c2 AIRBASE - Add'l AB for the Channels map 2022-03-28 10:12:39 +02:00
Frank
2d544b7a98 Merge pull request #1698 from gavinedwards/airboss.hermes.include
Added Hermes section to Airboss.  Will require tuning.
2022-03-26 23:34:54 +01:00
Lt Cdr Gavin Edwards
b1e5e1840e Adding leading lines that I accidentally truncated. 2022-03-26 15:23:38 -07:00
Lt Cdr Gavin Edwards
e6f9b4a125 Added Hermes section to Airboss. Will require tuning. 2022-03-26 11:54:53 -07:00
Applevangelist
5260b2b430 And don't forget Fahrenheit 2022-03-26 14:46:52 +01:00
Applevangelist
0213bc7aef Correcting Celcius to Celsius 2022-03-26 14:43:06 +01:00
Applevangelist
ca8b0899d0 docu changes 2022-03-23 07:56:52 +01:00
Applevangelist
a1f5c0ab9b CSAR/CTLD - added type to script 2022-03-22 10:38:20 +01:00
Applevangelist
b0e3f82d27 AIRBASE - added 10 new AB names in Syria 2022-03-18 09:48:50 +01:00
Applevangelist
327ab4766b changed descriptions 2022-03-18 07:59:58 +01:00
Applevangelist
3aee8a49c1 Added CONTROLLABLE SetSpeed() and SetAltitude() 2022-03-18 07:39:48 +01:00
Applevangelist
57de0b7351 docu fixes 2022-03-16 08:45:27 +01:00
Applevangelist
4df1e310a3 CSAR - remove timer check for "open the door" message to make behaviour more realistic 2022-03-14 09:12:24 +01:00
TommyC81
802a77238a Range re-formatting and documentation re-fixing (#1691)
* Update Range.lua

Code formatting.

* RANGE - Documentation fixes.
2022-03-12 09:47:14 +01:00
Applevangelist
85a7e18fae SCORING: Corrected calc error in summary scoring functions 2022-03-11 10:18:59 +01:00
Applevangelist
26b1fd3487 Update CTLD.lua (#1692)
minor nil check
2022-03-09 10:26:16 +01:00
Applevangelist
ae7a363012 CTLD - small extra nil check in _GetUnitCargoMass(Unit) 2022-03-03 12:34:43 +01:00
Applevangelist
473362af45 CTLD - small fix for finding crates when using engineers 2022-03-03 11:02:25 +01:00
Applevangelist
cde0d09f0a CSAR - remove double class 2022-02-21 19:36:22 +01:00
Applevangelist
94f093826b SEAD - adding workaround for AGM_154 which lost target data 2022-02-21 08:36:37 +01:00
Applevangelist
84f231ea08 CSAR - added wet feet check if also using csarUsePara (no landing event triggered) 2022-02-18 08:22:47 +01:00
Applevangelist
3d9bb14713 CSAR - added "wet feet" option for a 2nd template to be used over water 2022-02-17 17:41:32 +01:00
Applevangelist
6c6cdcf763 CTLD - fix list/build side effect from adding weight limits to helos 2022-02-16 10:06:04 +01:00
Applevangelist
00c8690e61 CTLD - corrected default weight limits when using CTLD:UnitCapabilities() - was setting loadable weight to zero 2022-02-15 18:07:26 +01:00
Applevangelist
a0d492cd2d added back GROUP:GetHighestThreat() 2022-02-15 14:41:31 +01:00
Applevangelist
ba5ccc1021 CTLD:SetTroopDropZoneRadius(Radius) 2022-02-13 12:08:23 +01:00
Applevangelist
a4163017d5 CSAR - CSAR:SpawnCSARAtZone(Zone ...) - Zone can now be a ZONE object as well as a string 2022-02-08 07:49:04 +01:00
Applevangelist
7f4a5c48ec CTLD - add subcategory option, added CTLD:AddCTLDZoneFromAirbase(AirbaseName, Type, Color, Active, HasBeacon) 2022-02-08 07:47:48 +01:00
Applevangelist
9f7588b245 DETECTION - added 3 missing functions 2022-02-04 08:54:02 +01:00
Applevangelist
63cbc0c55b RANGE - added option to save target sheet 2022-02-03 10:01:48 +01:00
Applevangelist
28eb7a678c CTLD - Added Hercules support for crates, troops & vehicles loaded with the help of the ground crew and dropped from the plane. Added weight checks for loaded crates. 2022-02-03 10:00:19 +01:00
Applevangelist
a95c49915a SET - correct error in intersection 2022-02-01 08:02:51 +01:00
Applevangelist
b7adc6add6 POINT - added function to name/stop fires and smoke 2022-01-30 09:47:11 +01:00
Applevangelist
2aeebf280b AI Dispatchers - add ability to add/remove resources to/from a squad 2022-01-24 09:54:16 +01:00
Applevangelist
8ac06979f0 CTLD added color options for smoke drops, droppable beacons w/ timer 2022-01-23 11:42:16 +01:00
Applevangelist
2d4f90d5eb Added new Callsigns as per 2.7.9 2022-01-23 11:37:07 +01:00
TommyC81
d7a44a639d Update Detection.lua (#1685)
Code formatting. Fix minor typos, errors, and references in documentation.
2022-01-23 11:21:59 +01:00
Applevangelist
7bfa05f47d DETECTION - corrected call for Vec2 in zone 2022-01-19 07:52:59 +01:00
Applevangelist
c7bbb09195 Added doors check for UH-60L 2022-01-16 17:07:44 +01:00
Applevangelist
41c9c15ae5 CTLD, CSAR - added support for UH-60L 2022-01-16 11:39:19 +01:00
Applevangelist
964831becf CTLD - make container shape configureable 2022-01-15 11:34:23 +01:00
Applevangelist
e847b92cce RAT - Docu corrections 2022-01-11 15:14:40 +01:00
TommyC81
c2ecd86bb4 Minor fixes (#1684)
* Update AI_A2A_Dispatcher.lua

Minor code formatting.

* Update Airbase.lua

Code formatting.
2022-01-10 15:10:30 +01:00
Applevangelist
70d922fad6 SHort name mina AP 2022-01-04 15:10:25 +01:00
Applevangelist
854bee0519 Mark visible as deprecated 2021-12-31 17:52:24 +01:00
Applevangelist
d54d991bdd Fix ATC new NTTR AFB names 2021-12-31 17:52:24 +01:00
Applevangelist
a4b600b97d Update Airbase.lua (#1680)
Change Nevada AFB names with latest stable patch
2021-12-31 17:44:01 +01:00
TommyC81
d6cfaa5050 Update Scoring.lua (#1674)
Code formatting and minor typo/documentation fixes.
2021-12-28 11:01:44 +01:00
TommyC81
00d1aec210 General fixes (#1673)
* General minor

Code formatting and minor typo/document fixes.

* Update Marker.lua

Code formatting and minor typo/document fixes. Note specifically the correction of "self.tocoaliton" to "self.tocoalition".
2021-12-28 11:01:05 +01:00
Applevangelist
f62e3391e1 SCORING - make treason and fratricide switchable 2021-12-28 08:25:31 +01:00
TommyC81
4a406604bd Core modules formatting (#1670)
* Update Fsm.lua

Code formatting and minor typo/documentation fixes.

* Update Goal.lua

Code formatting and minor typo/documentation fixes.

* Update Menu.lua

Code formatting and minor typo/documentation fixes.

* Update Message.lua

Code formatting and minor typo/documentation fixes.

* Update Report.lua

Code formatting and minor typo/documentation fixes.

* Update ScheduleDispatcher.lua

Code formatting and minor typo/documentation fixes.

* Update Scheduler.lua

Code formatting and minor typo/documentation fixes.

* Update Settings.lua

Code formatting and minor typo/documentation fixes.

* Update Spawn.lua

Code formatting and minor typo/documentation fixes.
2021-12-20 12:59:56 +01:00
TommyC81
55cee46a8d Core code formatting and typo fixes. (#1669)
* Update Beacon.lua

Code formatting and minor typo/text fixes.

* Update Database.lua

Code formatting and minor typo/text fixes.

* Update Event.lua

Code formatting and minor typo/text fixes.
2021-12-19 08:47:07 +01:00
TommyC81
607c52c0b7 Update CSAR.lua (#1665)
Code formatting and general typo/documentation fixes.
2021-12-17 09:07:32 +01:00
TommyC81
2694321256 Update CTLD.lua (#1666)
Code formatting. Minor typos and text fixes.
2021-12-17 09:07:24 +01:00
TommyC81
e8e790102a Update Base.lua (#1667)
Code Formatting. General whitespace and spelling.
2021-12-17 09:07:13 +01:00
Applevangelist
9c5561921b Noise reducing measures 2021-12-15 13:46:07 +01:00
Applevangelist
78fab9ab0c CSAR - make beacon length configureable 2021-12-14 09:50:34 +01:00
TommyC81
058c750bc6 Update Set.lua (#1663)
Additional minor code formatting and typo fixes.
2021-12-13 16:52:01 +01:00
Applevangelist
f29da39dff CSAR - override suppressmessages for menu driven information 2021-12-12 19:48:36 +01:00
Applevangelist
624a7c70c9 CTLD - corrected landheight of dropped smoke 2021-12-12 19:30:33 +01:00
TommyC81
0447ee2d9e Update Airboss.lua (#1664)
Code formatting. Typo fixing.
2021-12-12 19:15:30 +01:00
TommyC81
456fcd38d0 Code and documentation tweaks. (#1662)
* Update Point.lua

General code formatting.

* Update Set.lua

General code formatting.

* Update Positionable.lua

Code formatting, and documentation fixes.
2021-12-12 13:53:04 +01:00
Applevangelist
a3cab7097a SET - Typos 2021-12-11 19:41:02 +01:00
Applevangelist
848e2f1294 SET - Added Zone Filter for STATIC 2021-12-11 14:24:20 +01:00
Applevangelist
ef4dc48ea1 CTLD - added option for smoke/flare at position 2021-12-11 14:14:44 +01:00
Applevangelist
2138a33292 CSAR fixed KM message 2021-12-10 12:02:50 +01:00
TommyC81
a59343b987 Code formatting, spelling and documentation fixes. (#1661)
* Update Point.lua

Minor code formatting fix.

* Update Airboss.lua

Minor code formatting and documentation fixes.

* Update Set.lua

Code formatting, spelling and documentation fixes.

* Update ATIS.lua

Code formatting, spelling and documentation fixes.

* Update Task_A2A_Dispatcher.lua

Minor code formatting and documentation fixes. Added TODO re. possible unused variables.
2021-12-09 17:51:38 +01:00
TommyC81
a4ca4bdc99 Code and documentation fixes (#1659)
* Update .lua-format

Adjust for observed coding standards.

* Update ATIS.lua

Correct measurement units and spelling (also changed in Utils.lua).

* Update Utils.lua

Format the file, fix typos, adjust minor text. Rename "celcius" to "celsius". Rename "farenheit" to "fahrenheit".

* Update Warehouse.lua

Adjust measurement unit text.

* Update STTS.lua

Adjust formatting, minor typos, and fix error in documentation (missing blank rows) introduced in previous update.

* Update Range.lua

Adjust minor typos and code formatting. Adjust for celsius/fahrenheit typo correction.

* Update PseudoATC.lua

Adjust for celsius/fahrenheit typo correction in utils.lua.

* Update Point.lua

Code formatting, fix minor typos, adjust for celsius/fahrenheit corrrection in utils.lua.

* Update Range.lua

Minor documentation fix.
2021-12-08 19:52:29 +01:00
TommyC81
a57b9a9081 Update Range.lua (#1658)
Adjust some minor spelling and figure out the quirks of luadocumentor...
2021-12-07 18:13:51 +01:00
Applevangelist
249a6af456 Some false values seem to be in need of being set explicitly 2021-12-06 15:16:01 +01:00
Applevangelist
18685d1a94 Update Utils.lua (#1655)
Small bugfix for UTILS.LoadSetOfGroups
2021-12-06 14:57:49 +01:00
TommyC81
493b090534 LuaFormatter, RANGE formatting, and minor code fixes. (#1653)
* Create .lua-format

* Update Range.lua

Format code.

* Display distance in meters from bombtarget.

All other numbers, including menu lists etc. uses meters. Feet kept in parens.

* Fixed displaying of targetname when bombing.
2021-12-06 14:57:36 +01:00
Applevangelist
389adab9b8 SET - slight change to remove function 2021-12-06 10:26:32 +01:00
TommyC81
6360b8c58f General documentation and code fixes (#1650)
Documentation updates for correctness and clarity.
General code formatting updates.
Ajustment to POSITIONABLE:GetCoord to make use of existing POINT:UpdateFromVec3.
Added comments about clarifying the difference between POSITIONABLE:GetCoordinate() and POSITIONABLE:GetCoord() and to perhaps consider a renaming or merging the functions with an optional flag.
2021-12-04 18:50:05 +01:00
TommyC81
b0818977cf Code formatting. (#1651)
General code formatting and fixes of minor typos.
2021-12-04 18:49:57 +01:00
TommyC81
32deb160ef Formatting and typos (#1652)
* Formatting and typo fixes.

General formatting and typo fixes.

* Update STTS.lua

Keep class table on separate lines.
2021-12-04 18:49:47 +01:00
Applevangelist
2ba5215036 Merge pull request #1649 from TommyC81/RANGE_FIXES
Update Range.lua
2021-12-02 19:25:42 +01:00
Tommy Carlsson
73ea4c7b32 Update Range.lua
Fix documentation - including typos and updates to no longer correct text.
Remove duplicated RANGE.Defaults.goodthitrange value.
DCS seems to use headings 0-359 (i.e. <360), thus also deduct 360 from an exact 360 heading.
2021-12-02 20:49:48 +04:00
Applevangelist
46c37ff06a Merge pull request #1648 from TommyC81/A2G_DISPATCHER_FIXES
Update AI_A2G_Dispatcher.lua
2021-12-01 11:54:56 +01:00
Tommy Carlsson
ff4708b624 Update AI_A2G_Dispatcher.lua
General code formatting fixes, and correction of typos/examples.
2021-12-01 13:41:27 +04:00
Applevangelist
82b2c84f13 Merge pull request #1647 from TommyC81/A2G_DISPATCHER_DOCUMENTATION_2
Update AI_A2G_Dispatcher.lua
2021-12-01 09:17:46 +01:00
Tommy Carlsson
48e8b1a9b3 Update AI_A2G_Dispatcher.lua
More updates and fixes.

Update AI_A2G_Dispatcher.lua

General documentation updates.
2021-12-01 11:34:18 +04:00
Applevangelist
1ad7c54ace Merge pull request #1645 from TommyC81/A2G_Dispatcher_documentation
Update AI_A2G_Dispatcher.lua
2021-11-30 17:02:40 +01:00
Tommy Carlsson
9998c86c1f Update AI_A2G_Dispatcher.lua
Fix typos and incorrect references (leftovers) to A2A/CAP etc.
2021-11-30 19:37:26 +04:00
Applevangelist
01a707ae0a Small changes in GROUP 2021-11-29 07:57:07 +01:00
Applevangelist
570e8388fc Bug fixes 2021-11-27 17:30:25 +01:00
Applevangelist
9c5b5d4633 CSAR - added changes by Shagrat for Casevac
CSAR - don't make usePara default. Added coalition check if using the parachute landing event
2021-11-14 13:34:23 +01:00
Applevangelist
822bf13626 Merge pull request #1625 from FlightControl-Master/Applevangelist-unit-1
Wrapper Unit - fix for missile count
2021-11-14 12:59:51 +01:00
Applevangelist
c520de0087 Wrapper Unit - fix for missile count
Wrapper Unit - fix for missile count, issue #1624
2021-11-14 12:57:00 +01:00
Applevangelist
0e9076efa3 SEAD - align to dev changes, allow callback on SEAD events 2021-11-11 16:02:13 +01:00
Applevangelist
74cd5e3387 MANTIS - added docu and addition os SEAD events 2021-11-11 16:02:13 +01:00
Applevangelist
65c92be09e Merge pull request #1621 from FlightControl-Master/Applevangelist-casevac
Update CSAR.lua
2021-11-06 15:30:15 +01:00
Applevangelist
8e776cb3ab Update CSAR.lua
Adding CASEVAC option by Shagrat
2021-11-06 15:29:09 +01:00
Applevangelist
ab6cd2b751 Fix cleanup exisiting crates 2021-10-31 11:51:31 +01:00
Applevangelist
18c3d990fc Merge pull request #1618 from rollnthndr/master
Enabled SSML when using Google text-to-speech.
2021-10-31 08:15:25 +01:00
Rolln
19d5cb8ecb Added a command line option that will enable
SSML support when using Google text-to-speech.
2021-10-30 23:35:27 -06:00
Applevangelist
2b56a78255 Merge pull request #1617 from Penecruz/master
Airboss V/STOL updates
2021-10-30 22:05:21 +02:00
Penecruz
176d9df476 Merge branch 'FlightControl-Master:master' into master 2021-10-31 06:59:50 +11:00
Penecruz
c94275cb8b Airboss V/STOL updates
-Add additional Airboss V/STOL carrier HMAS Canberra L02
-Add Waveoff for AV-8B
-Add Cut Pass if Land without LSO clearance
-Changes to V/STOL groove timings
-Stabilise call when over the V/STOL landing spot
-larger abeam landing spot margin to allow decelerating to stable abeam and still be cleared to land
-Abeam area now extends further aft to allow LSO clearance 45-90 as per NATOPS
-Minor document changes
2021-10-31 06:41:58 +11:00
Applevangelist
45dbce3677 Speedmax returning 0 not nil 2021-10-30 16:33:18 +02:00
Applevangelist
18745158a3 Speedmax returning 0 not nil 2021-10-30 16:33:14 +02:00
Applevangelist
98c6c88391 Completed GetSetComplement 2021-10-29 18:32:13 +02:00
Frank
2e4fd72781 Update Fox.lua
Removed incomplete line `--@field #boolean`
2021-10-28 10:18:43 +02:00
Applevangelist
80ced88ef1 Fix for docs build 2021-10-28 08:31:14 +02:00
Applevangelist
8af3f89c14 Adjustments for Forrestal by Pene 2021-10-24 14:35:55 +02:00
Applevangelist
fe3079caad Added Bell-47 2021-10-22 17:04:23 +02:00
Applevangelist
61ac6b4131 Added Bell-47 2021-10-22 17:04:19 +02:00
Frank
36cb189512 Merge pull request #1612 from FlightControl-Master/FF/MasterDevel
AIRBOSS v1.2.0
2021-10-20 19:55:19 +02:00
Frank
15f9843878 AIRBOSS v1.2.0
- Added Forrestal carrier CV-59
2021-10-16 12:11:34 +02:00
Frank
67f847dd16 Update Group.lua
- Fixed SetInvisible and SetImmortal functions to acknowledge parameter false.
2021-10-12 22:16:18 +02:00
Applevangelist
8b9143d3f1 CTLD - added option to force opening of doors 2021-10-12 08:32:34 +02:00
Applevangelist
0388d47f23 CSAR - Added country options for spawned pilots 2021-10-12 08:31:55 +02:00
Applevangelist
de9b173d9b UTILS - added door check for Hercules 2021-10-07 18:14:29 +02:00
Applevangelist
2cecc526fb ZONE_CAPTURE_COALITION - fixed an issue when monitoring hits and SCENERY was delivered as hit UNIT 2021-10-05 19:10:21 +02:00
Applevangelist
968d178317 Update README.md 2021-10-02 10:18:13 +02:00
Applevangelist
3c477b872a CSAR - hovering rescued parameters added 2021-10-01 14:54:31 +02:00
Applevangelist
77e6088114 UTILS - corrected open door check MI-8 2021-10-01 14:54:16 +02:00
Applevangelist
edd6594953 CTLD: added user-friendly function to inject static cargos: CTLD:InjectStaticFromTemplate(Zone, Template, Mass) 2021-10-01 10:43:17 +02:00
Applevangelist
f8c05c99d0 RADIO - delete frequency check 2021-09-30 08:07:34 +02:00
Applevangelist
50f6d98b49 push for a new build 2021-09-29 09:25:26 +02:00
Frank
147eeb05f6 Merge pull request #1607 from FlightControl-Master/FF/MasterDevel
DATABASE
2021-09-29 09:22:41 +02:00
Frank
d8cb15a577 Update Airbase.lua 2021-09-29 09:01:47 +02:00
Frank
0daac876ea Update Airbase.lua
- Register oil rigs and gas platforms as helipads. DCS bug registers them as ship (Airbase.Category.SHIP instead of Airbase.Category.HELIPAD).
2021-09-29 09:00:53 +02:00
Frank
1832125022 Globals
- Moved _DATABASE:_RegisterAirbases() to Globals.lua
2021-09-29 08:58:46 +02:00
Applevangelist
c311c40b72 GROUP:GetAmmunition() - fix to also return bomb count (#1606)
GROUP:GetAmmunition() - fix to also return bomb count
2021-09-28 16:53:53 +02:00
Applevangelist
db516a2077 Fix "local" error 2021-09-27 15:48:45 +02:00
Applevangelist
ff8766669c Small fix for Airbase Parking Spot Finder 2021-09-26 09:51:14 +02:00
Applevangelist
06dc9a732e bugfix 2021-09-24 18:37:13 +02:00
Applevangelist
50c74d0852 Added option for slingload: enableslingload 2021-09-24 11:08:23 +02:00
Applevangelist
1c97eb6f3c SPAWNSTATIC - bugfix on canCargo, mass could be set but not transported into the template spawn 2021-09-24 11:04:54 +02:00
Applevangelist
69449430d1 CTLD - Added Statics as cargo (#1600)
CTLD - Added Statics as cargo, and the ability to load and save them (alongside your dropped buildable crates).
2021-09-22 15:54:37 +02:00
Applevangelist
663cd34aa3 CTLD - fix when using SAVE or LOAD w/o filename and path 2021-09-21 07:48:09 +02:00
Applevangelist
cfed6f5153 SET - Added SET_CLIENT:CountAlive() 2021-09-21 07:47:43 +02:00
Applevangelist
2b22d5288c CTLD - added persistence 2021-09-20 14:27:45 +02:00
Applevangelist
a64424ecc8 Positionable - Add IsSubmarine, Passenger seats for VAB Mephisto 2021-09-20 14:27:22 +02:00
Applevangelist
fd1b2ecb86 ZONE - Docu bug fix 2021-09-20 14:26:42 +02:00
Applevangelist
6cae3e62cf CTLD - small bug fix on stock removal 2021-09-12 17:37:32 +02:00
Applevangelist
05ce7e4513 CTLD - added alternative crate spawn by @mousepilot. Add menu item to list stock.Injected troops will not lead to cargo type duplication. 2021-09-11 15:20:20 +02:00
Applevangelist
136bd19f19 Bug fixing 2021-09-11 10:03:49 +02:00
Applevangelist
8873504daf CTLD: Align to Dev changes 2021-09-07 19:52:14 +02:00
Applevangelist
a844a5d697 CSAR: Align to Dev changes 2021-09-07 19:52:13 +02:00
Applevangelist
a49f4eaa21 Merge pull request #1597 from Penecruz/Airboss-V/Stol
Airboss v/stol
2021-09-06 07:12:20 +02:00
Penecruz
e6e2651f8c Bug fix to AV-8B grading WIP 2021-09-06 11:08:29 +10:00
Penecruz
b93ba13644 bug fix to V/Stol groove. 2021-09-06 08:20:37 +10:00
Frank
e4a51951b0 Merge pull request #1596 from Penecruz/Airboss-V/Stol
Airboss v/stol
2021-09-04 15:20:03 +02:00
Penecruz
ad56e39942 Docs AV-8B clarifications 2021-09-04 13:40:10 +10:00
Penecruz
ea09dc5a6e Vstol groove timing 2021-09-04 11:25:44 +10:00
Penecruz
8ecfd913a3 Av-8B specific deviation counts adj. 2021-09-04 11:25:25 +10:00
Penecruz
53367c786e AV-8B LIG and Unicorn fix 2021-09-04 11:24:47 +10:00
Applevangelist
db5797bb4e Bug fixing 2021-09-02 18:48:40 +02:00
Applevangelist
5e8fe97752 CTLD Added method to inject troops into the field. 2021-09-01 13:34:13 +02:00
Applevangelist
393fa0bfbb MANTIS - Changes from the dev branch merged 2021-08-28 14:01:37 +02:00
Applevangelist
4f51884b9d SEAD - make padding a variable (radar switch-back-on time) 2021-08-28 14:01:37 +02:00
Frank
4c5c320073 Merge pull request #1594 from Penecruz/Pene-LHA-and-LHD-edits
Pene lha and lhd edits
2021-08-28 10:52:12 +02:00
Penecruz
9098590568 Sound Pack Gabriella add 2021-08-28 14:51:30 +10:00
Penecruz
555bb7e68b Update instructions for AV-8B Harrier 2021-08-28 14:27:32 +10:00
Penecruz
c0a18957f0 AoA for harrier and JC Spot 5 timings 2021-08-28 13:31:16 +10:00
Penecruz
2cf939560e Allow for JC Spot 5 Voice over 2021-08-28 11:02:05 +10:00
Penecruz
9d3a7aae78 Add Landing Spot 5 to JC 2021-08-28 10:26:49 +10:00
Applevangelist
f6ed592f92 CSAR - remove noise 2021-08-27 18:47:13 +02:00
Applevangelist
c98757d13c CTLD - added ENGINEERING 2021-08-27 18:46:59 +02:00
Applevangelist
17378f509e SEAD - Code cleanup and enabled delayed switch off 2021-08-27 18:46:45 +02:00
Applevangelist
7f18ea0e7a UNIT/GROUP - added function to get the skill of a unit. SEAD - added functionality to calculate time-2-impact of HARMS and adjust behaviour accordingly 2021-08-27 14:56:16 +02:00
Frank
5172619cb1 Merge pull request #1593 from Penecruz/Pene-LHA-and-LHD-edits
Pene-LHA-LHD-additions
2021-08-26 08:22:11 +02:00
Penecruz
3962529698 Update Airboss.lua 2021-08-26 09:33:40 +10:00
Penecruz
6481d5d41e Update Airboss.lua 2021-08-25 17:59:33 +10:00
Applevangelist
6cc3d73c04 Changed priority to show bomb target height in ft if no Player Settings 2021-08-22 12:02:52 +02:00
Applevangelist
e541e39403 Bug fixes. Added support for Ships as load zones 2021-08-22 12:02:26 +02:00
Applevangelist
c7ea45e5fd Clean up UTF-8 mess 2021-08-18 18:01:04 +02:00
Applevangelist
20f28b3d2c Fix for SAM pattern matching not working 2021-08-18 15:01:14 +02:00
Applevangelist
f3f63ab8aa Fix for degree sign extra char 2021-08-18 11:52:13 +02:00
Applevangelist
e91090cfff more corrections 2021-08-18 11:47:25 +02:00
Applevangelist
1a7fb3c13e Fix for degree sign extra char 2021-08-18 11:36:33 +02:00
Applevangelist
59857ed79d CSAR - added changes from Development for AFB landings and safer calculation of distances of lost pilots 2021-08-18 09:29:58 +02:00
Frank
4797665939 Update Range.lua
- Added demo by shagrat
2021-08-02 22:02:21 +02:00
Applevangelist
b89749036d Merge pull request #1584 from FlightControl-Master/Applevangelist-patch-3-1
Update Utils.lua
2021-08-02 19:04:57 +02:00
Applevangelist
c6268488de Update Utils.lua 2021-08-02 19:03:07 +02:00
Applevangelist
de04369703 Various for AI_CARGO, CTLD, CSAR, align with dev 2021-07-29 12:46:16 +02:00
Applevangelist
05b6f19a87 Merge pull request #1583 from FlightControl-Master/Applevangelist-patch-2
Update CSAR.lua
2021-07-28 19:47:48 +02:00
Applevangelist
2753df8216 Update CSAR.lua
Fix for CSAR message to all not working, added option to suppress all messaging, make destroys silent to not affect scoring
2021-07-28 19:45:47 +02:00
Applevangelist
33d761503d Fix for crates dropping is loosing track of troops 2021-07-25 13:26:07 +02:00
Applevangelist
b52272e18e Merge pull request #1581 from FlightControl-Master/Applevangelist-patch-1
Update ATIS.lua
2021-07-24 15:57:15 +02:00
Applevangelist
70fa1cf19f Update ATIS.lua
Correct degree output extra chars
2021-07-24 15:54:54 +02:00
Applevangelist
7ca7caea75 Updates with changes from develop 2021-07-24 15:50:10 +02:00
Applevangelist
277c26821e Updated according to develop version. Added troop extract 2021-07-18 14:54:37 +02:00
Applevangelist
22826b4cd1 Update CTLD.lua
Added correct Mi-8MT unit typename
2021-07-17 16:00:40 +02:00
Applevangelist
8cc1c24b64 Update CSAR.lua
Changes as per development version. Taking care of dead pilots correctly, added FSM event KIA
2021-07-17 15:58:11 +02:00
Applevangelist
0db35a0e9f Updated according to develop version 2021-07-16 14:00:21 +02:00
Applevangelist
b4707bb3eb Update Mantis.lua (#1568)
* Update Mantis.lua

Added state tracker so that Red/Green Events only get triggered when a state actually changes.

* Update Mantis.lua
2021-07-14 15:37:47 +02:00
Applevangelist
bbfc2e9ed7 Added beacon frequency and laser code generator functions 2021-07-14 08:44:55 +02:00
Applevangelist
2fb0ab1aed Reflect DEVELOP changes in MASTER 2021-07-14 08:41:14 +02:00
Applevangelist
6fbf050b72 Slightly updated versions as the new emissions on/off doesn't work really. 2021-07-12 18:16:00 +02:00
Applevangelist
29210f670c Added Fisher-Yeats table shuffle and Helicopter door check 2021-07-11 18:37:46 +02:00
Applevangelist
18b45c9621 Added CTLD and CSAR 2021-07-10 17:11:00 +02:00
Applevangelist
9356d67d74 Added CTLD and CSAR 2021-07-10 17:10:53 +02:00
Applevangelist
5bb2b05f71 Added CTLD and CSAR 2021-07-10 17:10:46 +02:00
Frank
89308f7d06 Update Airbase.lua
- Fixed Andersen
2021-07-06 21:49:51 +02:00
Frank
353d6dfec0 Update Airbase.lua
- Corrected Mariana Islands airbase names
- Fixed bug in Syria airbase name `["Thalah"]="Tha'lah"`
2021-07-02 08:51:51 +02:00
Celso Dantas
3f5e322948 Update Airboss debug msg with BRC/Final heading (#1559)
Including the BRC and Final Heading on the debug message is useful for squadrons relying on that msg to know what is the end heading of the carrier.

The carrier tends to turns a few times before ending up on the final heading as it adjust into its track. Thus, having the BRC/Final Heading on the last message "Starting aircraft recovery Case %d ops." is useful.
2021-06-26 13:33:51 +02:00
Frank
0e8732fd44 ATIS
- ATIS MSRS uses coalition of airbase
- update coalition if base was captured
2021-06-19 22:27:12 +02:00
Applevangelist
77a3c7369d Update Shorad.lua 2021-06-18 12:00:15 +02:00
Frank
8d8070bbd7 Update Globals.lua
Added CR at the end.
2021-06-13 10:59:46 +02:00
Frank
72df687ce7 Merge pull request #1549 from FlightControl-Master/FF/MasterDevel
Sound Update
2021-06-12 22:22:02 +02:00
Frank
246935622c Update ATIS.lua 2021-06-12 21:54:42 +02:00
Frank
078573629d SRS
- added coordinate
- added google
2021-06-11 23:41:37 +02:00
cammel tech
973b8323c9 Ratmanager documentation add :SetTspawn(dt) infite spawns - see discord func.rat discussion (#1548)
Co-authored-by: wob3155@posteo.de <wob3155@posteo.de>
2021-06-11 13:38:25 +02:00
Frank
6d61c5ee94 Update Utils.lua 2021-06-10 23:33:14 +02:00
Frank
3e8db6a1fa ATIS
Improved time TTS format
2021-06-10 23:23:16 +02:00
Applevangelist
832d6b1c08 Update Group.lua (#1546)
Added invisible and immortal commands on GROUP level.
2021-06-10 11:20:01 +02:00
Frank
e7936950f4 ATIS 2021-06-10 00:18:49 +02:00
Frank
831e986ee8 Merge branch 'master' into FF/MasterDevel 2021-06-09 13:18:46 +02:00
Frank
8a44fae3d4 Update SRS.lua
- VBS script
2021-06-09 13:01:48 +02:00
Frank
0c9390914a Update SRS.lua 2021-06-08 23:51:03 +02:00
Frank
4fa525a4ae Update Airbase.lua 2021-06-08 22:06:33 +02:00
Applevangelist
858b00336b Update Spawn.lua 2021-06-07 18:07:14 +02:00
Applevangelist
82d78c98bb Update Spawn.lua (#1544) 2021-06-07 15:16:04 +02:00
Applevangelist
06c3f7998b Added function for message duration (#1542)
... and correct flash status setting
2021-06-07 15:09:30 +02:00
Frank
e03e87f501 Sound update docs 2021-06-05 23:44:25 +02:00
Frank
cf83abfe90 Sound update docs 2021-06-05 23:40:50 +02:00
Frank
5a00f461e9 Sound update 2021-06-04 23:04:49 +02:00
Frank
bb43b08190 Sound update 2021-06-01 23:20:38 +02:00
Frank
05f95796f6 Sound update 2021-05-31 23:47:43 +02:00
Frank
cfcd7d7588 Sound update 2021-05-30 22:36:52 +02:00
Frank
4c3d44a63b Sound update 2021-05-30 01:36:22 +02:00
Frank
fcd75a34eb Merge branch 'master' into FF/MasterDevel 2021-05-28 23:00:50 +02:00
Frank
4827b73bb1 Sound update 2021-05-28 23:00:43 +02:00
Frank
2d7e7d55a9 Merge pull request #1540 from FlightControl-Master/Applevangelist-submarine
Update Controllable.lua
2021-05-28 22:29:59 +02:00
Frank
3129c8c8ea Sound update 4 2021-05-27 23:19:40 +02:00
Applevangelist
6e37300d9b Update Controllable.lua
Added function `IsSubmarine()`removed type from `OptionDisperseOnAttack()`
2021-05-26 08:44:30 +02:00
Frank
6b747e924b Sound update 3 2021-05-25 23:32:54 +02:00
Applevangelist
85fef96d00 ZONE_POLYGON_BASE:Boundary added (#1537)
* ZONE_POLYGON_BASE:Boundary added

ZONE_POLYGON_BASE:Boundary added

* Update Zone.lua

Change Radius default value
2021-05-24 10:00:16 +02:00
Frank
e55bb21401 Sound 2 2021-05-23 23:32:26 +02:00
Frank
7a3fb95851 Sound Update 1
- Moved all related files to new Moose folder "Sound/"
2021-05-22 23:26:18 +02:00
Frank
d39074bf3e Sound 2021-05-22 00:35:00 +02:00
Applevangelist
47d814e409 CONTROLLABLE:PatrolRouteRandom fix for Subs (#1536)
* CONTROLLABLE:PatrolRouteRandom fix for Subs

fixes issue #1535

* Update Controllable.lua

* Update Controllable.lua
2021-05-21 12:35:32 +02:00
Frank
41b01a508d Removed debug coordinate marks for quad zones 2021-05-14 21:57:19 +02:00
Frank
0410ae6877 Mariana Islands 2021-05-13 23:30:34 +02:00
Applevangelist
1ae41319fa Update README.md 2021-05-12 09:18:31 +02:00
Frank
0265152c12 Merge pull request #1534 from FlightControl-Master/FF/MasterDevel
ZONES and COORDINATES: New functions to draw on F10 map
2021-05-11 11:57:17 +02:00
Frank
a893d46cb9 Update Event.lua 2021-05-11 11:54:27 +02:00
Frank
b68f271fb7 Update Event.lua 2021-05-11 11:51:25 +02:00
Frank
057e231a9d Emission 2021-05-11 11:41:26 +02:00
Frank
b6fedbd97d Merge branch 'master' into FF/MasterDevel 2021-05-11 11:18:35 +02:00
Frank
7cd29501a9 Templates update 2021-05-11 00:21:50 +02:00
Frank
59e4f48726 COORDINATES and ZONES
- Quad zones defined in the ME are now registered as ZONE_POLYGON_BASE
- Added draw functions to COORDINATE (line, circle, arrow, rectangle, text)
2021-05-10 17:47:42 +02:00
Frank
20fe2ee505 Update Event.lua 2021-05-10 08:33:15 +02:00
Frank
8b4ba430fb Update Airbase.lua
- Fixed #1524
2021-05-07 21:55:56 +02:00
Frank
3b0949de72 Fixed Ground Speed Issues
COORDINATE
- Fixed issue with ground units not honoring set speed by setting `ETA_locked=false` in `COORDINATE:WaypointGround` function (used by other classes and routines in MOOSE). See #1484
- Also set `ETA_locked=false` for `COORINATE:WaypointAir` and `COORDINATE:WaypointNaval`
2021-05-07 21:46:03 +02:00
Frank
ebb4623bb5 Merge branch 'master' into FF/MasterDevel 2021-05-07 17:42:07 +02:00
Applevangelist
2309487429 Merge pull request #1531 from FlightControl-Master/AA-4
Update Shorad.lua
2021-05-03 17:32:40 +02:00
Applevangelist
a3decc33c9 Update Shorad.lua
Add option to defend a SET_STATIC - e.g. group of bunkers or other static objects.
2021-05-03 17:30:18 +02:00
Applevangelist
b2d87a8780 Update Set.lua
correct typo in function name
2021-05-03 09:27:56 +02:00
Applevangelist
5034005f6b slightly higher landing speed 2021-05-02 15:29:42 +02:00
Applevangelist
c045ba0c6d Update Sead.lua 2021-05-02 11:35:30 +02:00
Applevangelist
d0fb4d44ef Update Sead.lua
test GH Desktop
2021-05-02 11:28:50 +02:00
Frank
d1f3e3f4bb Update Templates.lua 2021-05-02 00:08:17 +02:00
Frank
45f578b5a3 Merge branch 'master' into FF/MasterDevel 2021-05-01 21:37:28 +02:00
Applevangelist
620f4b96b9 Merge pull request #1530 from FlightControl-Master/AA-4
Update Mantis.lua
2021-05-01 17:32:24 +02:00
Applevangelist
39d3f85b71 Update Mantis.lua
Adjustments for 2.7
2021-05-01 17:23:17 +02:00
Applevangelist
9a7df65faa Update Shorad.lua
Adjust to Event changes
2021-05-01 17:18:01 +02:00
Applevangelist
596eb4c7e4 Update Sead.lua
Adjust to Event changes
2021-05-01 17:13:07 +02:00
Frank
2bce305451 Merge branch 'master' into FF/MasterDevel 2021-05-01 17:07:56 +02:00
Applevangelist
2bcc531e73 Update Event.lua (#1526)
Updates for Event 33 and 6 - also makes Scoring work again
2021-05-01 14:26:03 +02:00
Frank
2e66a854b1 Events and Templates 2021-05-01 00:55:43 +02:00
Applevangelist
57f6096aa1 Update Controllable.lua (#1518)
Error line 1466
2021-04-25 20:24:41 +02:00
Frank
dc64b98c59 Update Act_Route.lua
- Fixed bug in ACT_GROUP:GetRouteText
2021-04-23 13:27:31 +02:00
Applevangelist
e6dde7335a Merge pull request #1517 from FlightControl-Master/aa
Update Controllable.lua
2021-04-23 12:02:59 +02:00
Applevangelist
bbb2174957 Update Controllable.lua
added `OptionDisperseOnAttack` for GROUND units
2021-04-23 12:00:49 +02:00
Applevangelist
b37b50a0c6 Merge pull request #1515 from FlightControl-Master/AA-3
Update Controllable.lua
2021-04-23 09:53:58 +02:00
Applevangelist
459ff8038b Update Controllable.lua
Some Docu updates
2021-04-23 09:51:58 +02:00
Applevangelist
f2da862442 Merge pull request #1514 from FlightControl-Master/aa-1
Update .gitignore
2021-04-22 12:47:37 +02:00
Applevangelist
9c6c2ec5ca Update .gitignore 2021-04-22 12:46:37 +02:00
Applevangelist
aa13583aa7 Update README.md
added badge
2021-04-22 12:39:35 +02:00
Applevangelist
002f1e09ef Merge pull request #1513 from FlightControl-Master/AA-2
Update Airboss.lua
2021-04-22 10:39:04 +02:00
Applevangelist
5091ed7d6d Update Airboss.lua 2021-04-22 10:26:02 +02:00
Applevangelist
2475f22937 Create .luacheckrc 2021-04-22 10:08:57 +02:00
Applevangelist
f4f12f786c Update Airboss.lua
Escape error
2021-04-21 18:52:44 +02:00
Applevangelist
eb659446df Merge pull request #1510 from FlightControl-Master/AA
Update DCS.lua
2021-04-21 15:50:26 +02:00
Applevangelist
62c42adebc Update DCS.lua 2021-04-21 15:48:54 +02:00
Applevangelist
0fc91595b8 Merge pull request #1508 from FlightControl-Master/Applevangelist-patch-2
Update Group.lua
2021-04-20 17:31:00 +02:00
Applevangelist
18fca6461b Update Group.lua 2021-04-20 17:27:48 +02:00
Applevangelist
869b819a4f Merge pull request #1506 from IdefixRC/master
Create Airbase.lua
2021-04-20 16:23:19 +02:00
IdefixRC
8ae6192a5f Create Airbase.lua
New Syria Airbases added to list: H4, Gaziantep, Rosh Pina, Sayqal, Shayrat, Tiyas, Tha'lah and Naqoura
2021-04-20 15:43:45 +08:00
Applevangelist
3b2af6ea07 Update appveyor.yml 2021-04-19 20:49:17 +02:00
Applevangelist
82da7a8f24 Update appveyor.yml 2021-04-19 20:47:16 +02:00
Applevangelist
3749686882 Update appveyor.yml 2021-04-19 20:46:30 +02:00
Applevangelist
b2eba4911a Update appveyor.yml 2021-04-19 20:44:50 +02:00
Applevangelist
40700a387c Update appveyor.yml 2021-04-19 20:42:24 +02:00
Applevangelist
0577940d58 Update appveyor.yml 2021-04-19 20:38:50 +02:00
Frank
1d6be07cff Update appveyor.yml 2021-04-18 22:59:20 +02:00
Frank
2d6545429a Update Unit.lua
- Appveyor test
2021-04-18 22:16:46 +02:00
Frank
9f095a1d28 Update appveyor.yml
- Token for my appveyor account.
2021-04-18 22:14:51 +02:00
Frank
91b6da70af Update Event.lua 2021-04-18 14:49:08 +02:00
Frank
235e40cb26 Merge pull request #1505 from Applevangelist/revert-1491-patch-83
Revert "Update Group.lua"
2021-04-18 14:38:07 +02:00
Frank
8ba3a30278 Update appveyor.yml 2021-04-18 14:34:36 +02:00
Applevangelist
3675ddaf04 Revert "Update Group.lua" 2021-04-18 12:09:37 +02:00
Frank
62a59e7b2a Merge pull request #1503 from FlightControl-Master/FF/MasterDevel
Update ATIS.lua
2021-04-17 23:38:25 +02:00
Frank
1f949463aa Update ATIS.lua 2021-04-17 23:36:49 +02:00
Frank
6d53a30b1f Update appveyor.yml 2021-04-17 23:36:14 +02:00
Frank
5aeb480069 Update ATIS.lua 2021-04-17 23:29:53 +02:00
Frank
4b6aa3b56f Update appveyor.yml 2021-04-17 23:29:31 +02:00
Frank
4209569f29 Merge pull request #1502 from FlightControl-Master/FF/MasterDevel
Update Unit.lua
2021-04-17 23:05:42 +02:00
Frank
7106efbc04 Update Unit.lua 2021-04-17 22:56:54 +02:00
Frank
4e878e49a7 Merge pull request #1501 from FlightControl-Master/FF/MasterDevel
Update Unit.lua
2021-04-17 22:20:32 +02:00
Frank
3e470e557d Update Unit.lua 2021-04-17 22:19:27 +02:00
Frank
5f567fcf66 Merge pull request #1500 from FlightControl-Master/FF/MasterDevel
Ff/master devel
2021-04-17 21:46:46 +02:00
Frank
2289e16d91 Update Unit.lua 2021-04-17 21:45:17 +02:00
Frank
40415ef933 Update Unit.lua 2021-04-17 21:31:55 +02:00
Frank
6519c30b3a Update Unit.lua 2021-04-17 21:26:26 +02:00
Frank
42d253a9ca Update Unit.lua 2021-04-17 21:22:09 +02:00
Frank
38b40e2931 Update Unit.lua 2021-04-17 21:16:34 +02:00
Frank
f7a7ea14a3 Merge pull request #1495 from FlightControl-Master/FF/MasterDevel
Update Unit.lua
2021-04-17 19:49:38 +02:00
Frank
41e0f92cc0 Update Unit.lua 2021-04-17 19:49:04 +02:00
Frank
2c5e9789db Merge pull request #1494 from FlightControl-Master/FF/MasterDevel
Update ATIS.lua
2021-04-17 10:29:19 +02:00
Frank
e198aae1dd Update ATIS.lua 2021-04-17 10:28:18 +02:00
Frank
d040cc0d03 DCS 2.7
- Added COORDINATE:LineToAll and COORDINATE:CircleToAll
- Added altitude parameter to CONTROLLABLE:TaskFireAtPoint
- Added emission on/off functions to UNIT
2021-04-16 23:05:02 +02:00
Pikes
f078e94a7a Merge pull request #1493 from Applevangelist/patch-86
Update DCS.lua
2021-04-16 17:57:05 +01:00
Pikes
9973099f57 Merge pull request #1492 from Applevangelist/patch-85
Update Unit.lua
2021-04-16 17:56:50 +01:00
Pikes
9ca7785a64 Merge pull request #1491 from Applevangelist/patch-83
Update Group.lua
2021-04-16 17:56:33 +01:00
Applevangelist
e7d5fd6c1b Update DCS.lua
enableEmissions
2021-04-16 12:46:45 +02:00
Applevangelist
9eed35ace7 Update Unit.lua
Added changes for EnableEmission
2021-04-16 12:40:32 +02:00
Applevangelist
c7ec1b4e5e Update Group.lua
Added code for EnableEmission
2021-04-16 12:37:35 +02:00
Frank
34ffebb46a Merge pull request #1489 from Applevangelist/patch-81
Events - Workaround for 2.7
2021-04-15 14:53:30 +02:00
Applevangelist
e8a2167934 Events - Workaround for 2.7
Same as for Event 31
2021-04-15 13:01:02 +02:00
Frank
fa342a5b6b ATIS v0.9.1
- DCS 2.7 weather presetss
2021-04-14 23:42:36 +02:00
Frank
4ebcaf913c Merge pull request #1488 from Applevangelist/patch-80
Update Shorad.lua
2021-04-07 20:52:29 +02:00
Frank
7a4c9806a4 Merge pull request #1487 from Applevangelist/patch-79
Update Mantis.lua
2021-04-07 20:52:16 +02:00
Applevangelist
bddc1d7fdc Update Shorad.lua
Added option to use AI on/off instead of changing the alert state
2021-04-07 19:32:34 +02:00
Applevangelist
cb813b70e1 Update Mantis.lua
Added option to use AI on/off instead of changing the alert state
2021-04-07 19:31:10 +02:00
Frank
f161f08fc1 Update Event.lua
- Fixed typo
2021-03-29 19:55:11 +02:00
Frank
3e22411328 Events
- Added new DCS events
2021-03-29 18:01:42 +02:00
Frank
b00778bb86 Update AI_A2A_Cap.lua
Fixes #1474
2021-03-29 09:53:30 +02:00
Frank
564f95781e Update Positionable.lua
- Added cargo bay limits for ground (by kappa) and naval units.
2021-03-28 20:29:15 +02:00
Frank
6c2f8aa6ff Merge pull request #1477 from Applevangelist/patch-73
Utils - added Convert knots to alitude corrected KIAS
2021-03-22 12:04:24 +01:00
Applevangelist
2e342e4341 Utils - added Convert knots to alitude corrected KIAS
Added - Convert knots to alitude corrected KIAS, e.g. for tankers.
2021-03-22 11:47:39 +01:00
Frank
3c6089884e Update Airboss.lua
- Added MP wire correction function.
2021-03-02 21:19:30 +01:00
Frank
884ea866e2 AIRBOSS v1.1.6
Adjusted and (hopefully improved) parameters.
- Angled deck 9.0° --> 9.1359° (from DCS config file)
USS Stennis:
- Deck height 19.00 --> 19.06 meters (from DCS config file)
Supercarriers:
- Deck height 20.0 -->20.1394 meters (from DCS config file)
- stern coordinate 1.0 meters more on starboard side (LUR calls).
2021-02-28 23:25:32 +01:00
Frank
1af1ada7dc Merge pull request #1463 from Applevangelist/patch-65
Update Mantis.lua
2021-02-24 13:46:24 +01:00
Applevangelist
c72c3fd091 Update Mantis.lua
Added a smarter switch-on distance for SHORAD, added user function to set AWACS detection range, change default SAM firing range to 95%
2021-02-24 12:55:54 +01:00
Frank
0848718313 Merge pull request #1462 from Applevangelist/patch-64
Error in NewFromLLDD - altitude isn't set if given
2021-02-23 10:37:25 +01:00
Applevangelist
832abd7860 Error in NewFromLLDD - altitude isn't set if given
Thanks to tomekldc
2021-02-23 10:34:55 +01:00
Frank
12e745af54 Merge pull request #1460 from Applevangelist/patch-62
Update AI_Cargo_Helicopter.lua
2021-02-19 14:41:37 +01:00
Applevangelist
f18c818b8e Update AI_Cargo_Helicopter.lua 2021-02-19 10:04:30 +01:00
Applevangelist
4deca4f816 Update AI_Cargo_Helicopter.lua
Helicopters don't drive, they fly... ;)
2021-02-19 09:59:12 +01:00
Frank
b023ecfb99 Merge pull request #1459 from Applevangelist/patch-61
Update AI_Cargo_Helicopter.lua
2021-02-18 17:21:28 +01:00
Applevangelist
cdb491bb5f Update AI_Cargo_Helicopter.lua
Added docu on Pseudo Function, make Home() function clear and Height useful
2021-02-18 17:05:36 +01:00
Frank
3ac47c967e Merge pull request #1458 from Applevangelist/patch-60
Update Shorad.lua
2021-02-17 15:44:47 +01:00
Frank
e577062720 Merge pull request #1457 from Applevangelist/patch-59
Update Mantis.lua
2021-02-17 15:44:34 +01:00
Applevangelist
95dc7ad2b4 Update Shorad.lua
code example added
2021-02-17 14:04:59 +01:00
Applevangelist
fccf58cd6c Update Mantis.lua
added code examples
2021-02-17 14:02:37 +01:00
Frank
6791e4d4ba Update Set.lua
- Improved docs for `:FilterPrefixes()`
2021-02-17 13:11:22 +01:00
Frank
11f8ac4820 Merge pull request #1456 from Applevangelist/patch-58
Update Mantis.lua
2021-02-17 11:00:43 +01:00
Frank
8f2672f928 Merge pull request #1455 from Applevangelist/patch-57
Update Shorad.lua
2021-02-17 11:00:33 +01:00
Frank
fb66d74ab7 ENUMS
- ENUMS: Added Phonetic and ISOLang by A101Wayz

- Fixed CALLSIGN.JTAC.Warrior (was CALLSIGN.JTAC.Warrier)
2021-02-17 10:26:13 +01:00
Applevangelist
37c061bcbd Update Mantis.lua
Nicefy doc output, Shorad link doc
2021-02-17 09:14:56 +01:00
Applevangelist
9491b18ff4 Update Shorad.lua
Own picture and quote, cleanup docs, link missions, less overhead by avoiding checks on friendly fire.
2021-02-17 09:13:35 +01:00
Frank
8cff2eaac0 Merge pull request #1454 from Applevangelist/patch-56
Update Moose.files
2021-02-16 17:56:30 +01:00
Frank
f64550f7c8 Merge pull request #1453 from Applevangelist/patch-55
Include Shorad.lua
2021-02-16 17:56:18 +01:00
Frank
0addafa627 Merge pull request #1452 from Applevangelist/patch-54
Shorad.lua - Initial release
2021-02-16 17:56:06 +01:00
Frank
b643d73f7f Merge pull request #1451 from Applevangelist/patch-53
Update Sead.lua
2021-02-16 17:55:53 +01:00
Applevangelist
798c5b292a Update Moose.files 2021-02-16 16:30:46 +01:00
Applevangelist
688c57d91d Update Moose.files 2021-02-16 16:29:13 +01:00
Applevangelist
d4c8d5d91b Include Shorad.lua 2021-02-16 16:26:25 +01:00
Applevangelist
154cc9fbeb Shorad.lua - Initial release
:)
2021-02-16 16:25:22 +01:00
Frank
327bac3058 Merge pull request #1450 from Applevangelist/patch-52
Integrate option for Shorad in Mantis
2021-02-16 16:23:14 +01:00
Applevangelist
c0e58e9c92 Update Sead.lua
Handle new shortened weapon names in DCS, make handling more graceful
2021-02-16 16:14:53 +01:00
Applevangelist
e8bfab515c Integrate option for Shorad in Mantis
Also, correct some documentation errors
2021-02-16 16:13:10 +01:00
Frank
695f204493 Merge pull request #1442 from Applevangelist/patch-49
Update Task_A2G_Dispatcher.lua
2021-01-28 15:47:22 +01:00
Frank
a7202ccd0d Merge pull request #1443 from Applevangelist/patch-50
Update Designate.lua
2021-01-28 12:24:46 +01:00
Applevangelist
c71c6c5178 Update Designate.lua
Use of added injection of Task Name into DetectedItem; useful when using also Designate with same Detection Object to ensure menu entries are harmonized.
2021-01-28 10:10:39 +01:00
Applevangelist
155b06edc8 Update Task_A2G_Dispatcher.lua
Added injection of Task Name into DetectedItem; useful when using also Designate with same Detection Object to ensure menu entries are harmonized.
2021-01-28 10:04:00 +01:00
Frank
64b8f2fb46 Merge pull request #1441 from Applevangelist/patch-48
Update Mantis.lua
2021-01-26 12:39:14 +01:00
Applevangelist
7a07e15032 Update Mantis.lua 2021-01-26 11:06:08 +01:00
Frank
3d74312d43 Merge pull request #1440 from Applevangelist/patch-47
Update Mantis.lua
2021-01-26 09:27:42 +01:00
Applevangelist
6baf85d429 Update Mantis.lua
Some Doc corrections
2021-01-26 09:26:39 +01:00
Frank
311ab19389 Merge pull request #1438 from Applevangelist/patch-46
Update Set.lua
2021-01-22 23:29:38 +01:00
Applevangelist
e9f92d2250 Update Set.lua
Break loop after x tries
2021-01-22 18:34:05 +01:00
Frank
d469374ed5 Merge pull request #1436 from Applevangelist/patch-44
ATIS.lua update time calculation over midnight (logic error)
2021-01-21 17:11:07 +01:00
Applevangelist
a6ff84c09a ATIS.lua update time calculation over midnight (logic error)
Actually line 1322 + time here, sind it's negative ... :)
2021-01-21 15:34:50 +01:00
Frank
3ed8ca63a8 Merge pull request #1435 from Applevangelist/patch-43
Update ATIS.lua
2021-01-20 11:16:54 +01:00
Applevangelist
89aa08829e Update ATIS.lua
Fix time to be negative if early AM when using time to ZULU or time to GMT difference
2021-01-20 11:05:14 +01:00
Frank
5146106885 Update Controllable.lua 2021-01-19 16:56:26 +01:00
Frank
f05fc956db Merge pull request #1434 from Applevangelist/patch-42
Update AI_A2G_Dispatcher.lua
2021-01-16 15:12:46 +01:00
Applevangelist
d10adb5de8 Update AI_A2G_Dispatcher.lua
Corrected Docu line 434
2021-01-16 14:50:36 +01:00
Frank
45aac21b63 Merge pull request #1430 from Applevangelist/patch-40
Update AI_A2A_Dispatcher.lua
2021-01-13 10:20:22 +01:00
Frank
b52643bb36 Merge pull request #1431 from Applevangelist/patch-41
Update Controllable.lua
2021-01-13 10:20:05 +01:00
Frank
036010c39d Merge pull request #1429 from Applevangelist/patch-39
MANTIS 0.3.6
2021-01-13 10:19:17 +01:00
Applevangelist
9440d2b2c3 Update Controllable.lua
missing # in docu
2021-01-13 08:47:20 +01:00
Applevangelist
29727ec9a6 Update AI_A2A_Dispatcher.lua
Closed gap in Docu
2021-01-13 08:44:10 +01:00
Applevangelist
eb0b43cea8 MANTIS 0.3.6
-- clean up docs
-- added a function to set a new SAM range whilst running
2021-01-13 08:32:45 +01:00
Frank
a6e16fb7ad Update Airbase.lua
Fixed bug in white list
2021-01-12 17:40:21 +01:00
Frank
616b80710a Merge pull request #1428 from FlightControl-Master/FF/MasterDevel
AIRBASE
2021-01-12 13:50:27 +01:00
Frank
94f206af68 AIRBASE
- Added black and white parking spot lists.
2021-01-12 13:49:35 +01:00
Frank
ae6613fae1 CARGO 2021-01-12 00:42:00 +01:00
Frank
b61cfc7390 Update Set.lua 2021-01-09 23:47:45 +01:00
Frank
e2c5688670 Merge pull request #1425 from Applevangelist/patch-37
Update AI_Cargo.lua
2021-01-05 21:18:38 +01:00
Frank
1d016d9fce Merge pull request #1424 from Applevangelist/patch-36
Update AI_Cargo_Helicopter.lua
2021-01-05 21:18:18 +01:00
Applevangelist
cbc5b5bb89 Update AI_Cargo.lua
Fix for another dead end
2021-01-05 11:27:37 +01:00
Applevangelist
218c7736e0 Update AI_Cargo_Helicopter.lua 2021-01-05 11:25:03 +01:00
Applevangelist
8e46e41b34 Update AI_Cargo_Helicopter.lua
Fix for missed FSM call
2021-01-05 09:54:10 +01:00
Frank
bcf639d076 Merge pull request #1423 from Applevangelist/patch-35
Update AI_A2A_Dispatcher.lua
2021-01-03 16:50:07 +01:00
Applevangelist
697d12aefc Update AI_A2A_Dispatcher.lua
#1422
2021-01-03 16:31:07 +01:00
Frank
89afd2b4c0 Merge pull request #1421 from FlightControl-Master/FF/MasterDevel
Events and Database
2021-01-03 00:10:00 +01:00
Frank
3358f98bc4 Some stuff 2021-01-02 23:53:36 +01:00
Frank
417af6a93c Client 2021-01-02 21:45:19 +01:00
Frank
9a2f6c7faa Merge pull request #1420 from Applevangelist/patch-34
Update AI_Cargo_Helicopter.lua
2021-01-02 19:47:06 +01:00
Frank
e52b0edc4e Merge pull request #1419 from Applevangelist/patch-33
Update AI_Cargo.lua
2021-01-02 19:46:10 +01:00
Applevangelist
c51a65f058 Update AI_Cargo_Helicopter.lua
Removed unnecessary FSM transitions, added height to Waypoints to avoid (blue) Helis to creep over the ground
2021-01-02 18:56:27 +01:00
Applevangelist
5b2e67df19 Update AI_Cargo.lua
Some small additions from learnings from AI CARGO HELI
2021-01-02 18:53:58 +01:00
Frank
7b7605e501 Client 2021-01-02 01:21:45 +01:00
Frank
49d7d9ee2e Merge pull request #1417 from Applevangelist/patch-32
Update AI_Cargo_Helicopter.lua
2021-01-01 17:27:28 +01:00
Applevangelist
774c8971c8 Update AI_Cargo_Helicopter.lua
Fixed FSM dead ends
2021-01-01 17:24:00 +01:00
Frank
545c0886fa Merge pull request #1416 from Applevangelist/patch-31
Update AI_Cargo_Helicopter.lua - MI-26 > 5m
2021-01-01 16:39:31 +01:00
Applevangelist
721d027fb3 Update AI_Cargo_Helicopter.lua
MI-26 is slightly over 5m blocking boarding of Cargo
2021-01-01 16:37:55 +01:00
Frank
5185ea35fe Merge pull request #1415 from acrojason/naval-cargo
Multiple ship types, set default cargobay limit, fixed doc bug
2021-01-01 02:21:41 +01:00
acrojason
ec039f2999 Multiple ship types, set default cargobay limit, fixed doc bug 2020-12-31 17:04:21 -08:00
Frank
c0fc649f8b Client 2021-01-01 01:46:14 +01:00
Frank
c49497e68c Merge branch 'master' into FF/MasterDevel 2020-12-31 21:39:37 +01:00
Frank
57bc6de12b Merge pull request #1414 from acrojason/naval-cargo
Naval cargo
2020-12-31 20:38:30 +01:00
acrojason
1b80d68f50 Add support for naval logistics 2020-12-31 10:15:32 -08:00
Frank
787151597c Client 2020-12-31 17:17:15 +01:00
Frank
f725039da5 Client and Event updates 2020-12-31 00:51:54 +01:00
Frank
a4f15a0830 Merge branch 'master' into FF/MasterDevel 2020-12-30 12:43:31 +01:00
Frank
084f00afad Update SpawnStatic.lua
- fixed static spawns
2020-12-29 23:38:27 +01:00
Frank
87f4ebee64 Clients 2020-12-29 01:10:40 +01:00
Frank
a0276821d9 Merge pull request #1412 from FlightControl-Master/FF/MasterDevel
Arty v1.20
2020-12-27 22:39:56 +01:00
Frank
01a5b523da Arty v1.2.0
- fixed cargo issue
2020-12-27 17:59:39 +01:00
Frank
13477e17ec Merge pull request #1411 from Applevangelist/patch-30
Mantis.lua 0.3.5
2020-12-27 17:54:59 +01:00
Applevangelist
c32cec4c5b Mantis.lua 0.3.5
Added option to use Awacs separate from EWR network with own detection range
Extended documentation
Added refresher for SAM table
2020-12-27 16:24:13 +01:00
Frank
e08df3f9ce Update Timer.lua 2020-12-23 16:09:49 +01:00
Frank
f958130b92 Merge pull request #1408 from Applevangelist/patch-28
Mantis 0.3.0 - added advanced mode options
2020-12-23 10:53:27 +01:00
Applevangelist
9a19dd254e Mantis 0.3.0 - added advanced mode options
Mantis 0.3.0 - added advanced mode options
2020-12-22 15:19:29 +01:00
Frank
4869ae4baa cargo 2020-12-21 22:52:51 +01:00
Frank
1fd41eaa55 Merge pull request #1405 from Applevangelist/patch-25
Update Sead.lua to 0.2.2
2020-12-21 15:13:40 +01:00
Frank
25a7260d7f Merge pull request #1404 from Applevangelist/patch-24
Update to 0.2.6
2020-12-21 15:13:13 +01:00
Applevangelist
478934ddd1 Update Sead.lua to 0.2.2
-- corrected error when a single string instead of a table is given to SEAD:New()
-- added a function to extend the watched set of SAMs
2020-12-21 10:31:06 +01:00
Applevangelist
3d7234de19 Update to 0.2.6
Corrected error in dupe function naming, added doc about default values
2020-12-21 10:02:41 +01:00
Frank
fed1fb2839 Update Artillery.lua
- fixed bug in :NewFromCargo
2020-12-20 23:48:13 +01:00
Frank
08ba001b45 Update AI_Air_Engage.lua
Issue #1403
2020-12-19 01:18:01 +01:00
Frank
ce5aaf0b48 Update Airbase.lua
- Changed Persion Gulf Airbase Names
2020-12-18 13:52:08 +01:00
Frank
ee6954a4b9 Update AI_Air_Engage.lua
- Changed :Count to :CountAlive for AttackSetUnit. Issue #1393
2020-12-17 13:54:59 +01:00
Frank
6b6e845a18 Merge pull request #1402 from Applevangelist/patch-23
Update to Mantis 0.2.5
2020-12-17 12:20:07 +01:00
Applevangelist
c01b94518f Update to Mantis 0.2.5
Changelog
-- changed HQ to be a GROUP object instead of creating a CommandCenter. Allows user to use their own CC.
-- added functionality to autorelocate HQ and EWR group(s) at random intervals between 30 and 60 mins
-- added verbosity option for logging
-- tidied up documentation
2020-12-17 11:16:30 +01:00
Frank
9cf5f74e5e Merge pull request #1401 from Applevangelist/patch-22
Add new Routing Function to Controllable.lua
2020-12-16 11:31:14 +01:00
Applevangelist
4fda0414a0 Add new Routing Function to Controllable.lua
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
-- @param #CONTROLLABLE self
-- @param  #number speed Speed of the controllable, default 20
-- @param  #number radius Radius of the relocation zone, default 500
-- @param  #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param  #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
2020-12-16 10:10:46 +01:00
Frank
effa4af3a1 Merge pull request #1400 from Applevangelist/patch-21
Update Mantis.lua
2020-12-15 13:00:50 +01:00
Applevangelist
80b7e49eed Update Mantis.lua
Added picture link, cleaned up unnecessary globals
2020-12-15 11:59:10 +01:00
Frank
63748c3841 Merge pull request #1397 from Applevangelist/patch-19
Update Modules.lua
2020-12-14 13:01:38 +01:00
Frank
1c68b66047 Merge pull request #1396 from Applevangelist/patch-18
Create Mantis.lua
2020-12-14 12:51:04 +01:00
Frank
6c9dce70e7 SPAWNSTATIC
- Added new way to spawn FARPs
- Added FARP callsign enum.
2020-12-14 12:36:02 +01:00
Applevangelist
5515aa43d6 Update Modules.lua
added Mantis.lua to Functional
2020-12-14 10:42:11 +01:00
Frank
47c461e504 SPAWNSTATIC
- Attempt to SPAWN FARPS. Does not work yet.
2020-12-14 00:08:30 +01:00
Applevangelist
65174ceaae Create Mantis.lua
New Mantis extended Air-Defense Class
2020-12-13 16:06:57 +01:00
Frank
2500cfb3c7 RANGE v2.3.0
- Performance optimizations
2020-12-03 23:17:40 +01:00
Frank
7e673271ee Merge pull request #1391 from Applevangelist/patch-16
Issue ##1383 never ending flash messages
2020-12-02 21:26:31 +01:00
Applevangelist
e165cb156c Update Task.lua
-- stop message flashing, if any #1383 & #1312
2020-12-02 17:25:57 +01:00
Applevangelist
82bfb0cc55 Issue ##1383 never ending flash messages
Messages appears once and then goes away.
2020-12-01 19:47:29 +01:00
Frank
b0fe0663f1 Merge pull request #1389 from Applevangelist/patch-14
Taskinfo.lua - Issue #1388 - don't just assume this is a string
2020-12-01 18:07:40 +01:00
Applevangelist
1bd5193786 Taskinfo.lua - Issue #1388 - don't just assume this is a string
fixes the catchall at the end in case a task is unassigned - sometimes it's not a string. #1388
2020-12-01 16:10:55 +01:00
Frank
64c3fb985a Merge pull request #1386 from Applevangelist/patch-12
Update Task_A2G_Dispatcher.lua
2020-11-26 17:21:19 +01:00
Frank
1536c94319 Merge pull request #1387 from Applevangelist/patch-13
Update Task_A2A_Dispatcher.lua
2020-11-26 17:20:57 +01:00
Applevangelist
e7efd89d7a Update Task_A2A_Dispatcher.lua
Added user function to change flash message behaviour
2020-11-26 10:30:07 +01:00
Applevangelist
c486167b01 Update Task_A2G_Dispatcher.lua
Option to suppress flashing messages
2020-11-26 10:28:51 +01:00
Frank
e5201f3363 Merge pull request #1384 from Applevangelist/patch-11
Update AI_Air.lua
2020-11-25 22:56:39 +01:00
Applevangelist
ff4927dbb7 Update AI_Air.lua 2020-11-25 12:38:31 +01:00
Applevangelist
5842562ce1 Update AI_Air.lua
Resolves AI reeping low back to base, and possible also issue #1305
2020-11-24 16:23:22 +01:00
Frank
5f545fd3c5 Merge pull request #1382 from Applevangelist/patch-10
Update Sead.lua
2020-11-23 13:37:05 +01:00
Applevangelist
aab2f20280 Update Sead.lua
Feature Request #1355
Added a couple of new missiles, SD-10 and AGM84
Cleaned up the logic
2020-11-23 12:32:00 +01:00
Frank
76ac68ce51 Merge pull request #1381 from Applevangelist/patch-9
Update Controllable.lua
2020-11-22 20:55:59 +01:00
Applevangelist
f034e3680c Update Controllable.lua 2020-11-22 17:39:56 +01:00
Applevangelist
e5c57269bb Update Controllable.lua
Added option OptionAAAttackRange and OptionEngageRange
2020-11-22 17:24:56 +01:00
Frank
ab5167717d Update Airboss.lua
- Added F-14A-135-GR by Heatblur to the list of supported aircraft.
2020-11-20 10:17:45 +01:00
Frank
176abf74a1 Merge pull request #1371 from Applevangelist/patch-2
Update Task_A2A_Dispatcher.lua
2020-11-20 09:49:17 +01:00
Frank
da4f13d0d7 Merge pull request #1374 from Applevangelist/patch-5
Update Task_Capture_Dispatcher.lua
2020-11-20 09:15:30 +01:00
Applevangelist
7ddec7609a Update Task_Capture_Dispatcher.lua
Last annoying flash gordon
2020-11-19 15:59:35 +01:00
Applevangelist
53c3996aef Update Task_A2A_Dispatcher.lua
Add boolean in new to suppress "... has new tasks..." flashes
2020-11-19 15:51:09 +01:00
Frank
bf61f22dd1 Fixed UnHandleEvent for UNIT
#1365
2020-11-15 23:00:03 +01:00
Frank
5e8a36802c Github issues
#1360
#1361
2020-10-31 21:21:25 +01:00
Frank
b34b537a26 Update AI_Formation.lua
- Issue #1358
2020-10-28 09:46:03 +01:00
Frank
812fb99e66 Small Updates
AIRBOSS
- Added USS Harry S. Truman (CVN-75) [Super Carrier Module]
- Fixed little bug in stop time

RADIOQUEUE
- Allowing GROUND units as relay. Only ships cannot be used.
2020-10-26 16:16:04 +01:00
Frank
9cb3111dd5 Github issues addressed
- Fixes #1342
- Fixes #1333
- Fixes #1331
2020-10-26 09:13:49 +01:00
Frank
1312555690 Github Issues Addressed
**GROUP**
#1354
**ARTY v1.1.8**
#1356
#1357
2020-10-20 23:26:32 +02:00
Frank
aa4f8452fa Update Airbase.lua
- Fixed a bug inthe `AIRBASE:GetRunwayData()` function.
2020-10-03 23:11:05 +02:00
Frank
e2c1097ec5 Update Airboss.lua
- Fix for DCS bug that skill is not set in env.mission. Should not happen in current DCS version anyway.
2020-10-03 21:41:35 +02:00
acrojason
4b8b13dd68 Initial changes to support Naval Cargo 2020-09-23 09:40:42 -07:00
acrojason
18fd9cdc3d Initial AI_Cargo_Dispatcher_Ship 2020-06-04 21:09:35 -07:00
acrojason
fdcdf52d9a Initial creation of AI_Cargo_Ship 2020-06-04 08:46:30 -07:00
acrojason
5dcefff28d Updated Warehouse to accommodate Naval transport 2020-06-04 08:29:01 -07:00
122 changed files with 46574 additions and 24771 deletions

View File

@@ -26,24 +26,25 @@ init:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
install: install:
- cmd:
# Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache.
# - call choco install 7zip.commandline call choco install 7zip.commandline
# - call choco install lua51 call choco install lua51
# - call choco install luarocks call choco install luarocks
# - call refreshenv call refreshenv
# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
# - cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
# - cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
# - cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
# - call luarocks install luasrcdiet call luarocks install luasrcdiet
# - call luarocks install checks call luarocks install checks
# - call luarocks install luadocumentor call luarocks install luadocumentor
# - call luarocks install luacheck call luarocks install luacheck
#cache: cache:
# - C:\ProgramData\chocolatey\lib C:\ProgramData\chocolatey\lib
# - C:\ProgramData\chocolatey\bin C:\ProgramData\chocolatey\bin
@@ -51,8 +52,9 @@ build_script:
- ps: | - ps: |
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{ {
echo "Hello World!"
$apiUrl = 'https://ci.appveyor.com/api' $apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw' $token = 'v2.6hcv3ige78kg3yvg4ge8'
$headers = @{ $headers = @{
"Authorization" = "Bearer $token" "Authorization" = "Bearer $token"
"Content-type" = "application/json" "Content-type" = "application/json"
@@ -65,7 +67,7 @@ build_script:
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{ {
$apiUrl = 'https://ci.appveyor.com/api' $apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw' $token = 'v2.6hcv3ige78kg3yvg4ge8'
$headers = @{ $headers = @{
"Authorization" = "Bearer $token" "Authorization" = "Bearer $token"
"Content-type" = "application/json" "Content-type" = "application/json"

2
.gitignore vendored
View File

@@ -18,6 +18,8 @@ local.properties
# External tool builders # External tool builders
.externalToolBuilders/ .externalToolBuilders/
# AppVeyor
.appveyor/
# CDT-specific # CDT-specific
.cproject .cproject

54
.luacheckrc Normal file
View File

@@ -0,0 +1,54 @@
ignore = {
"011", -- A syntax error.
"021", -- An invalid inline option.
"022", -- An unpaired inline push directive.
"023", -- An unpaired inline pop directive.
"111", -- Setting an undefined global variable.
"112", -- Mutating an undefined global variable.
"113", -- Accessing an undefined global variable.
"121", -- Setting a read-only global variable.
"122", -- Setting a read-only field of a global variable.
"131", -- Unused implicitly defined global variable.
"142", -- Setting an undefined field of a global variable.
"143", -- Accessing an undefined field of a global variable.
"211", -- Unused local variable.
"212", -- Unused argument.
"213", -- Unused loop variable.
"221", -- Local variable is accessed but never set.
"231", -- Local variable is set but never accessed.
"232", -- An argument is set but never accessed.
"233", -- Loop variable is set but never accessed.
"241", -- Local variable is mutated but never accessed.
"311", -- Value assigned to a local variable is unused.
"312", -- Value of an argument is unused.
"313", -- Value of a loop variable is unused.
"314", -- Value of a field in a table literal is unused.
"321", -- Accessing uninitialized local variable.
"331", -- Value assigned to a local variable is mutated but never accessed.
"341", -- Mutating uninitialized local variable.
"411", -- Redefining a local variable.
"412", -- Redefining an argument.
"413", -- Redefining a loop variable.
"421", -- Shadowing a local variable.
"422", -- Shadowing an argument.
"423", -- Shadowing a loop variable.
"431", -- Shadowing an upvalue.
"432", -- Shadowing an upvalue argument.
"433", -- Shadowing an upvalue loop variable.
"511", -- Unreachable code.
"512", -- Loop can be executed at most once.
"521", -- Unused label.
"531", -- Left-hand side of an assignment is too short.
"532", -- Left-hand side of an assignment is too long.
"541", -- An empty do end block.
"542", -- An empty if branch.
"551", -- An empty statement.
"561", -- Cyclomatic complexity of a function is too high.
"571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step.
"611", -- A line consists of nothing but whitespace.
"612", -- A line contains trailing whitespace.
"613", -- Trailing whitespace in a string.
"614", -- Trailing whitespace in a comment.
"621", -- Inconsistent indentation (SPACE followed by TAB).
"631", -- Line is too long.
}

10
.vs/VSWorkspaceState.json Normal file
View File

@@ -0,0 +1,10 @@
{
"ExpandedNodes": [
"",
"\\Moose Development",
"\\Moose Development\\Moose",
"\\Moose Development\\Moose\\Ops"
],
"SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua",
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View File

@@ -0,0 +1,33 @@
# See https://github.com/Koihik/LuaFormatter
# Use '-- LuaFormatter off' and '-- LuaFormatter on' around code blocks to inhibit formatting
column_limit: 500
indent_width: 2
use_tab: false
continuation_indent_width: 2
keep_simple_control_block_one_line: false
keep_simple_function_one_line: false
align_args: true
break_after_functioncall_lp: false
break_before_functioncall_rp: false
align_parameter: true
chop_down_parameter: true
break_after_functiondef_lp: false
break_before_functiondef_rp: false
align_table_field: true
break_after_table_lb: true
break_before_table_rb: true
chop_down_table: true
chop_down_kv_table: true
column_table_limit: 500
table_sep: ','
extra_sep_at_table_end: true
break_after_operator: true
single_quote_to_double_quote: false
double_quote_to_single_quote: false
spaces_before_call: 1
spaces_inside_functiondef_parens: true
spaces_inside_functioncall_parens: true
spaces_inside_table_braces: true
spaces_around_equals_in_field: true
line_breaks_after_function_body: 1

View File

@@ -0,0 +1,17 @@
{
"Lua.workspace.preloadFileSize": 1000,
"Lua.diagnostics.disable": [
"undefined-doc-name"
],
"Lua.diagnostics.globals": [
"BASE",
"lfs",
"__Moose",
"trigger",
"coord",
"missionCommands"
],
"Lua.completion.displayContext": 5,
"Lua.runtime.version": "Lua 5.1",
"Lua.completion.callSnippet": "Both"
}

View File

@@ -40,8 +40,8 @@
-- --
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
-- --
@@ -71,7 +71,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. -- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set the Range of Engagement -- ## 3. Set the Range of Engagement
-- --
@@ -143,7 +143,7 @@ end
-- @return #AI_A2A_CAP -- @return #AI_A2A_CAP
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType ) function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType ) return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
end end

File diff suppressed because it is too large Load Diff

View File

@@ -42,8 +42,8 @@
-- --
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
-- --
@@ -73,7 +73,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. -- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set the Range of Engagement -- ## 3. Set the Range of Engagement
-- --

View File

@@ -39,8 +39,8 @@
-- --
-- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG)
-- --
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG)
-- --
@@ -68,7 +68,7 @@
-- * **RTB** ( Group ): Route the AI to the home base. -- * **RTB** ( Group ): Route the AI to the home base.
-- * **Detect** ( Group ): The AI is detecting targets. -- * **Detect** ( Group ): The AI is detecting targets.
-- * **Detected** ( Group ): The AI has detected new 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. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set or Get the AI controllable -- ## 3. Set or Get the AI controllable
-- --
@@ -100,8 +100,8 @@
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL -- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
-- --
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. -- 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. -- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, -- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
-- while a new AI is targetted to the AI_A2A_PATROL. -- while a new AI is targetted to the AI_A2A_PATROL.
-- Once the time is finished, the old AI will return to the base. -- Once the time is finished, the old AI will return to the base.
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place. -- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
@@ -109,7 +109,7 @@
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL -- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
-- --
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on. -- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). -- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place. -- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
-- --
-- === -- ===

File diff suppressed because it is too large Load Diff

View File

@@ -42,8 +42,8 @@
-- --
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
-- --

View File

@@ -253,6 +253,9 @@ function AI_AIR:New( AIGroup )
self.IdleCount = 0 self.IdleCount = 0
self.RTBSpeedMaxFactor = 0.6
self.RTBSpeedMinFactor = 0.5
return self return self
end end
@@ -370,11 +373,11 @@ 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. --- 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. -- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold 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_AIR. -- When the fuel threshold 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. -- Once the time is finished, the old AI will return to the base.
-- @param #AI_AIR self -- @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 FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. -- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_AIR self -- @return #AI_AIR self
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime ) function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
@@ -387,14 +390,14 @@ function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
return self return self
end end
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. --- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on. -- However, damage cannot be foreseen early on.
-- Therefore, when the damage treshold is reached, -- Therefore, when the damage threshold is reached,
-- the AI will return immediately to the home base (RTB). -- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated. -- 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. -- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
-- @param #AI_AIR self -- @param #AI_AIR self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. -- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_AIR self -- @return #AI_AIR self
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold ) function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
@@ -476,7 +479,7 @@ function AI_AIR:onafterStatus()
local Fuel = self.Controllable:GetFuelMin() local Fuel = self.Controllable:GetFuelMin()
-- If the fuel in the controllable is below the treshold percentage, -- If the fuel in the controllable is below the threshold percentage,
-- then send for refuel in case of a tanker, otherwise RTB. -- then send for refuel in case of a tanker, otherwise RTB.
if Fuel < self.FuelThresholdPercentage then if Fuel < self.FuelThresholdPercentage then
@@ -576,6 +579,19 @@ function AI_AIR.RTBHold( AIGroup, Fsm )
end end
--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6.
-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket).
-- A random value in this bracket is then applied in the waypoint routing generation.
-- @param #AI_AIR self
-- @param #number MinFactor Lower bracket factor. Defaults to 0.5.
-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6.
-- @return #AI_AIR self
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
self.RTBSpeedMaxFactor = MaxFactor or 0.6
self.RTBSpeedMinFactor = MinFactor or 0.5
return self
end
--- @param #AI_AIR self --- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
@@ -585,29 +601,38 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance() self:ClearTargetDistance()
--AIGroup:ClearTasks() --AIGroup:ClearTasks()
AIGroup:OptionProhibitAfterburner(true)
local EngageRoute = {} local EngageRoute = {}
--- Calculate the target route point. --- Calculate the target route point.
local FromCoord = AIGroup:GetCoordinate() local FromCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.HomeAirbase:GetCoordinate() local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
local ToTargetVec3 = ToTargetCoord:GetVec3()
if not self.RTBMinSpeed and not self.RTBMaxSpeed then ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
local RTBSpeedMax = AIGroup:GetSpeedMax() local RTBSpeedMax = AIGroup:GetSpeedMax()
self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 ) local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6
local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5
self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor)
end end
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) ) --local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
local Distance = FromCoord:Get2DDistance( ToTargetCoord ) local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) --local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
local ToAirbaseCoord = ToTargetCoord2
if Distance < 5000 then if Distance < 5000 then
self:I( "RTB and near the airbase!" ) self:I( "RTB and near the airbase!" )
self:Home() self:Home()

View File

@@ -10,11 +10,11 @@
-- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. -- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations.
-- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. -- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets.
-- * Define and use a detection network controlled by recce. -- * Define and use a detection network controlled by recce.
-- * Define AIR defense squadrons at airbases, farps and carriers. -- * Define AIR defense squadrons at airbases, FARPs and carriers.
-- * Enable airbases for AIR defenses. -- * Enable airbases for AIR defenses.
-- * Add different planes and helicopter templates to squadrons. -- * Add different planes and helicopter templates to squadrons.
-- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. -- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition.
-- * Add multiple squadrons to different airbases, farps or carriers. -- * Add multiple squadrons to different airbases, FARPs or carriers.
-- * Define different ranges to engage upon. -- * Define different ranges to engage upon.
-- * Establish an automatic in air refuel process for planes using refuel tankers. -- * Establish an automatic in air refuel process for planes using refuel tankers.
-- * Setup default settings for all squadrons and AIR defenses. -- * Setup default settings for all squadrons and AIR defenses.
@@ -40,7 +40,7 @@
-- --
-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. -- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system.
-- --
-- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions. -- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions.
-- --
-- --
-- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? -- ## 1. Which coalition am I modeling an AIR defense system for? blue or red?
@@ -128,7 +128,7 @@
-- Depending on the defense type, different payloads will be needed. See further points on squadron definition. -- Depending on the defense type, different payloads will be needed. See further points on squadron definition.
-- --
-- --
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? -- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs?
-- --
-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. -- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**.
-- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. -- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses.
@@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER
-- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups.
-- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set.
-- --
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen.
-- --
-- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network
-- configuration and setup the AIR defense detection mechanism. -- configuration and setup the AIR defense detection mechanism.
@@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway.
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines.
-- --
-- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency.
-- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the
-- A2A defense system, as no new CAP or GCI planes can takeoff. -- A2A defense system, as no new CAP or GCI planes can takeoff.
-- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft.
@@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER
-- --
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron.
-- --
-- ## 4. Set the **fuel treshold**. -- ## 4. Set the **fuel threshold**.
-- --
-- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: -- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken:
-- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible.
-- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron.
-- --
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons.
-- --
-- ## 6. Other configuration options -- ## 6. Other configuration options
-- --
@@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER
-- --
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons.
-- --
-- ## 10.5. Default RTB fuel treshold. -- ## 10.5. Default RTB fuel threshold.
-- --
-- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
-- --
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons.
-- --
-- ## 10.6. Default RTB damage treshold. -- ## 10.6. Default RTB damage threshold.
-- --
-- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
-- --
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons.
-- --
-- ## 10.7. Default settings for **patrol**. -- ## 10.7. Default settings for **patrol**.
-- --
@@ -829,7 +829,7 @@ do -- AI_AIR_DISPATCHER
-- --
-- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected.
-- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher.
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed.
-- --
-- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- When the tanker specified is alive and in the air, the tanker will be used for refuelling.
-- --
@@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER
-- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 )
-- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 )
-- --
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left.
-- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 )
-- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- A2ADispatcher:SetDefaultTanker( "Tanker" )
-- --
@@ -882,10 +882,7 @@ do -- AI_AIR_DISPATCHER
-- As a result, the GCI and CAP will stop! -- As a result, the GCI and CAP will stop!
-- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes
-- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players.
-- --
--
--
--
-- @field #AI_AIR_DISPATCHER -- @field #AI_AIR_DISPATCHER
AI_AIR_DISPATCHER = { AI_AIR_DISPATCHER = {
ClassName = "AI_AIR_DISPATCHER", ClassName = "AI_AIR_DISPATCHER",
@@ -914,10 +911,10 @@ do -- AI_AIR_DISPATCHER
--- Enumerator for spawns at airbases --- Enumerator for spawns at airbases
-- @type AI_AIR_DISPATCHER.Takeoff -- @type AI_AIR_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff --- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defnes Landing location. --- Defnes Landing location.
-- @field #AI_AIR_DISPATCHER.Landing -- @field #AI_AIR_DISPATCHER.Landing
AI_AIR_DISPATCHER.Landing = { AI_AIR_DISPATCHER.Landing = {
@@ -925,7 +922,7 @@ do -- AI_AIR_DISPATCHER
AtRunway = 2, AtRunway = 2,
AtEngineShutdown = 3, AtEngineShutdown = 3,
} }
--- A defense queue item description --- A defense queue item description
-- @type AI_AIR_DISPATCHER.DefenseQueueItem -- @type AI_AIR_DISPATCHER.DefenseQueueItem
-- @field Squadron -- @field Squadron
@@ -936,7 +933,7 @@ do -- AI_AIR_DISPATCHER
-- @field Functional.Detection#DETECTION_BASE AttackerDetection -- @field Functional.Detection#DETECTION_BASE AttackerDetection
-- @field DefenderGrouping -- @field DefenderGrouping
-- @field #string SquadronName The name of the squadron. -- @field #string SquadronName The name of the squadron.
--- Queue of planned defenses to be launched. --- Queue of planned defenses to be launched.
-- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers.
-- And some of these platforms have very limited amount of "launching" platforms. -- And some of these platforms have very limited amount of "launching" platforms.
@@ -945,40 +942,39 @@ do -- AI_AIR_DISPATCHER
-- This guarantees that launched defenders are also directly existing ... -- This guarantees that launched defenders are also directly existing ...
-- @type AI_AIR_DISPATCHER.DefenseQueue -- @type AI_AIR_DISPATCHER.DefenseQueue
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... -- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue --- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
AI_AIR_DISPATCHER.DefenseQueue = {} AI_AIR_DISPATCHER.DefenseQueue = {}
--- Defense approach types --- Defense approach types
-- @type #AI_AIR_DISPATCHER.DefenseApproach -- @type #AI_AIR_DISPATCHER.DefenseApproach
AI_AIR_DISPATCHER.DefenseApproach = { AI_AIR_DISPATCHER.DefenseApproach = {
Random = 1, Random = 1,
Distance = 2, Distance = 2,
} }
--- AI_AIR_DISPATCHER constructor. --- AI_AIR_DISPATCHER constructor.
-- This is defining the AIR DISPATCHER for one coaliton. -- This is defining the AIR DISPATCHER for one coalition.
-- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units.
-- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network.
-- @return #AI_AIR_DISPATCHER self -- @return #AI_AIR_DISPATCHER self
-- @usage -- @usage
-- --
-- -- Setup the Detection, using DETECTION_AREAS. -- -- Setup the Detection, using DETECTION_AREAS.
-- -- First define the SET of GROUPs that are defining the EWR network. -- -- First define the SET of GROUPs that are defining the EWR network.
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
-- DetectionSetGroup = SET_GROUP:New() -- DetectionSetGroup = SET_GROUP:New()
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
-- DetectionSetGroup:FilterStart() -- DetectionSetGroup:FilterStart()
-- --
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
-- --
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
function AI_AIR_DISPATCHER:New( Detection ) function AI_AIR_DISPATCHER:New( Detection )
-- Inherits from DETECTION_MANAGER -- Inherits from DETECTION_MANAGER
@@ -1435,17 +1431,17 @@ do -- AI_AIR_DISPATCHER
end end
--- Set the default damage treshold when defenders will RTB. --- Set the default damage threshold when defenders will RTB.
-- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB.
-- @return #AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER
-- @usage -- @usage
-- --
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
-- -- Now Setup the default damage treshold. -- -- Now Setup the default damage threshold.
-- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. -- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
-- --
function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold )
@@ -1989,7 +1985,7 @@ do -- AI_AIR_DISPATCHER
--- Defines the default amount of extra planes that will take-off as part of the defense system. --- Defines the default amount of extra planes that will take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
@@ -2028,7 +2024,7 @@ do -- AI_AIR_DISPATCHER
--- Defines the amount of extra planes that will take-off as part of the defense system. --- Defines the amount of extra planes that will take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron. -- @param #string SquadronName The name of the squadron.
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
@@ -2068,7 +2064,7 @@ do -- AI_AIR_DISPATCHER
--- Gets the overhead of planes as part of the defense system, in comparison with the attackers. --- Gets the overhead of planes as part of the defense system, in comparison with the attackers.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron. -- @param #string SquadronName The name of the squadron.
-- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
@@ -2674,17 +2670,17 @@ do -- AI_AIR_DISPATCHER
return self return self
end end
--- Set the default fuel treshold when defenders will RTB or Refuel in the air. --- Set the default fuel threshold when defenders will RTB or Refuel in the air.
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
-- @return #AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER
-- @usage -- @usage
-- --
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
-- -- Now Setup the default fuel treshold. -- -- Now Setup the default fuel threshold.
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
-- --
function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold )
@@ -2695,18 +2691,18 @@ do -- AI_AIR_DISPATCHER
end end
--- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air.
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron. -- @param #string SquadronName The name of the squadron.
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
-- @return #AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER
-- @usage -- @usage
-- --
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
-- -- Now Setup the default fuel treshold. -- -- Now Setup the default fuel threshold.
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
-- --
function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold )
@@ -2726,7 +2722,7 @@ do -- AI_AIR_DISPATCHER
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
-- -- Now Setup the default fuel treshold. -- -- Now Setup the default fuel threshold.
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
-- --
-- -- Now Setup the default tanker. -- -- Now Setup the default tanker.
@@ -2749,7 +2745,7 @@ do -- AI_AIR_DISPATCHER
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
-- --
-- -- Now Setup the squadron fuel treshold. -- -- Now Setup the squadron fuel threshold.
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
-- --
-- -- Now Setup the squadron tanker. -- -- Now Setup the squadron tanker.
@@ -2847,7 +2843,7 @@ do -- AI_AIR_DISPATCHER
-- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount )
-- First, count the active AIGroups Units, targetting the DetectedSet -- First, count the active AIGroups Units, targeting the DetectedSet
local DefendersEngaged = 0 local DefendersEngaged = 0
local DefendersTotal = 0 local DefendersTotal = 0

View File

@@ -42,8 +42,8 @@
-- --
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
-- --
@@ -418,6 +418,7 @@ end
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
-- @param #string To The To State string. -- @param #string To The To State string.
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } ) self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
@@ -425,7 +426,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:CountAlive()
if AttackCount > 0 then if AttackCount > 0 then
@@ -510,6 +511,7 @@ end
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
-- @param #string To The To State string. -- @param #string To The To State string.
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
@@ -517,8 +519,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:CountAlive()
self:I({AttackCount = AttackCount}) self:T({AttackCount = AttackCount})
if AttackCount > 0 then if AttackCount > 0 then
@@ -531,6 +533,10 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
if not TargetCoord then
self:Return()
return
end
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )

View File

@@ -39,8 +39,8 @@
-- --
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
-- --
@@ -70,7 +70,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new 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.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. -- * **@{#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. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set the Range of Engagement -- ## 3. Set the Range of Engagement
-- --

View File

@@ -49,7 +49,7 @@
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
-- using a random speed within the given altitude and speed limits. -- 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. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. -- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- --
-- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG) -- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG)
-- --
@@ -87,7 +87,7 @@
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone. -- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
-- It can be notified to go RTB through the **RTB** event. -- It can be notified to go RTB through the **RTB** event.
-- --
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG) -- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG)
-- --
@@ -117,7 +117,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task. -- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task.
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** -- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
-- --
@@ -515,8 +515,8 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
--- Calculate the current route point. --- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2() local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -602,7 +602,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
self:SetRefreshTimeInterval( 2 ) self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated() self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting self:__Target( -2 ) -- Start targeting
end end
end end

View File

@@ -9,7 +9,7 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing) -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
-- --
-- === -- ===
-- --

View File

@@ -4,13 +4,13 @@
-- --
-- * Patrol AI airplanes within a given zone. -- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected. -- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel treshold to RTB on time. -- * Manage a fuel threshold to RTB on time.
-- * Engage the enemy when detected. -- * Engage the enemy when detected.
-- --
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol) -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol)
-- --
-- === -- ===
-- --
@@ -65,8 +65,8 @@
-- --
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- ![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. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
-- --
@@ -96,7 +96,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. -- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set the Range of Engagement -- ## 3. Set the Range of Engagement
-- --
@@ -428,8 +428,10 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
--- Calculate the current route point. --- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2() local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). if not CurrentVec2 then return self end
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(

View File

@@ -11,7 +11,7 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support) -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support)
-- --
-- === -- ===
-- --
@@ -49,7 +49,7 @@
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
-- using a random speed within the given altitude and speed limits. -- 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. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. -- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
-- --
-- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) -- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG)
-- --
@@ -87,7 +87,7 @@
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. -- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
-- It can be notified to go RTB through the **RTB** event. -- It can be notified to go RTB through the **RTB** event.
-- --
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) -- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG)
-- --
@@ -117,7 +117,7 @@
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task. -- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- === -- ===
-- --
@@ -459,8 +459,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
--- Calculate the current route point. --- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2() local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
self:SetRefreshTimeInterval( 2 ) self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated() self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting self:__Target( -2 ) -- Start targeting
end end
end end

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. --- **AI** - Models the intelligent transportation of infantry and other cargo.
-- --
-- === -- ===
-- --
@@ -35,10 +35,9 @@ AI_CARGO = {
--- Creates a new AI_CARGO object. --- Creates a new AI_CARGO object.
-- @param #AI_CARGO self -- @param #AI_CARGO self
-- @param Wrapper.Group#GROUP Carrier -- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
-- @param Core.Set#SET_CARGO CargoSet -- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
-- @param #number CombatRadius -- @return #AI_CARGO self
-- @return #AI_CARGO
function AI_CARGO:New( Carrier, CargoSet ) function AI_CARGO:New( Carrier, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
@@ -48,18 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet )
self:SetStartState( "Unloaded" ) self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" ) -- Board
self:AddTransition( "Loaded", "Deploy", "*" ) self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Reload", "*" )
self:AddTransition( "*", "Board", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
self:AddTransition( "*", "Load", "Boarding" ) -- Unload
self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" ) self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "Boarding", "Loaded", "Boarding" ) self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "Boarding", "PickedUp", "Loaded" ) self:AddTransition( "*", "Unboard", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" ) self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
--- Pickup Handler OnBefore for AI_CARGO --- Pickup Handler OnBefore for AI_CARGO
-- @function [parent=#AI_CARGO] OnBeforePickup -- @function [parent=#AI_CARGO] OnBeforePickup
@@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
end end
end end

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. --- **AI** - Models the intelligent transportation of cargo using ground vehicles.
-- --
-- === -- ===
-- --
@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
self:AddTransition( "*", "Guard", "Unloaded" ) self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Home", "*" ) self:AddTransition( "*", "Home", "*" )
self:AddTransition( "*", "Reload", "Boarding" ) self:AddTransition( "*", "Reload", "Boarding" )
self:AddTransition( "*", "Deployed", "*" )
self:AddTransition( "*", "PickedUp", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" ) self:AddTransition( "*", "Destroyed", "Destroyed" )
self:SetCombatRadius( CombatRadius ) self:SetCombatRadius( CombatRadius )
@@ -157,6 +158,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
return self return self
end end
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
self:SetPickupOffRoad(Offroad, Formation)
self:SetDeployOffRoad(Offroad, Formation)
return self
end
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
-- @param #AI_CARGO_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_APC self
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Find a free Carrier within a radius. --- Find a free Carrier within a radius.
-- @param #AI_CARGO_APC self -- @param #AI_CARGO_APC self
@@ -350,10 +390,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
end end
--- Pickup task function. Triggers Load event.
--- @param #AI_CARGO_APC -- @param Wrapper.Group#GROUP APC The cargo carrier group.
-- @param Wrapper.Group#GROUP APC -- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) -- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
-- @param #number Speed Speed (not used).
-- @param Core.Zone#ZONE PickupZone Pickup zone.
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
@@ -362,8 +405,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
end end
end end
--- Deploy task function. Triggers Unload event.
function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone ) -- @param Wrapper.Group#GROUP APC The cargo carrier group.
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
-- @param Core.Zone#ZONE DeployZone Deploy zone.
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
APC:F( { "AI_CARGO_APC._Deploy:", APC } ) APC:F( { "AI_CARGO_APC._Deploy:", APC } )
@@ -392,12 +439,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He
self.RoutePickup = true self.RoutePickup = true
local _speed=Speed or APC:GetSpeedMax()*0.5 local _speed=Speed or APC:GetSpeedMax()*0.5
-- Route on road.
local Waypoints = {}
if self.pickupOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
end
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone ) local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints] local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
@@ -428,18 +483,34 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He
self.RouteDeploy = true self.RouteDeploy = true
local _speed=Speed or APC:GetSpeedMax()*0.5 -- Set speed in km/h.
local speedmax=APC:GetSpeedMax()
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) local _speed=Speed or speedmax*0.5
_speed=math.min(_speed, speedmax)
-- Route on road.
local Waypoints = {}
if self.deployOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
end
-- Task function
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
self:F({Waypoints = Waypoints}) -- Last waypoint
local Waypoint = Waypoints[#Waypoints] local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
-- Set task function
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
-- Route group
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
-- Call parent function.
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
end end

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo). --- **AI** - Models the intelligent transportation of cargo using airplanes.
-- --
-- === -- ===
-- --
@@ -408,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone
end end
--- Route the airplane from one airport or it's current position to another airbase. --- Route the airplane from one airport or it's current position to another airbase.
-- @param #AI_CARGO_AIRPLANE self -- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed. -- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
@@ -438,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled
-- To point. -- To point.
local AirbasePointVec2 = Airbase:GetPointVec2() local AirbasePointVec2 = Airbase:GetPointVec2()
local ToWaypoint = AirbasePointVec2:WaypointAir( local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
POINT_VEC3.RoutePointAltType.BARO,
"Land", --ToWaypoint["airdromeId"] = Airbase:GetID()
"Landing", --ToWaypoint["speed_locked"] = true
Speed or Airplane:GetSpeedMax()*0.8
)
ToWaypoint["airdromeId"] = Airbase:GetID()
ToWaypoint["speed_locked"] = true
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned. -- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. --- **AI** - Models the intelligent transportation of infantry and other cargo.
-- --
-- ## Features: -- ## Features:
-- --
@@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
-- The Pickup sequence ... -- The Pickup sequence ...
-- Check if this Carrier need to go and Pickup something... -- Check if this Carrier need to go and Pickup something...
-- So, if the cargo bay is not full yet with cargo to be loaded ... -- So, if the cargo bay is not full yet with cargo to be loaded ...
self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then
-- ok, so there is a free Carrier -- ok, so there is a free Carrier
-- now find the first cargo that is Unloaded -- now find the first cargo that is Unloaded

View File

@@ -1,4 +1,4 @@
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs. --- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
-- --
-- ## Features: -- ## Features:
-- --
@@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon
return self return self
end end
--- AI cargo
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param Wrapper.Group#GROUP APC The APC carrier.
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet ) function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius ) local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
return aicargoapc
end end
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). --- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
-- This is only valid for APCs and trucks etc, thus ground vehicles. -- This is only valid for APCs and trucks etc, thus ground vehicles.
-- @param #AI_CARGO_DISPATCHER_APC self -- @param #AI_CARGO_DISPATCHER_APC self
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. -- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
-- When the combat radius is 0, no defense will happen of the carrier. -- When the combat radius is 0 (default), no defense will happen of the carrier.
-- When the combat radius is not provided, no defense will happen! -- When the combat radius is not provided, no defense will happen!
-- @return #AI_CARGO_DISPATCHER_APC -- @return #AI_CARGO_DISPATCHER_APC
-- @usage -- @usage
-- --
-- -- Disembark the infantry when the carrier is under attack. -- -- Disembark the infantry when the carrier is under attack.
-- AICargoDispatcher:SetCombatRadius( true ) -- AICargoDispatcher:SetCombatRadius( 500 )
-- --
-- -- Keep the cargo in the carrier when the carrier is under attack. -- -- Keep the cargo in the carrier when the carrier is under attack.
-- AICargoDispatcher:SetCombatRadius( false ) -- AICargoDispatcher:SetCombatRadius( 0 )
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius ) function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
self.CombatRadius = CombatRadius or 0 self.CombatRadius = CombatRadius or 0
@@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
return self return self
end end
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
self:SetPickupOffRoad(Offroad, Formation)
self:SetDeployOffRoad(Offroad, Formation)
return self
end
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param #boolean Offroad If true, carrier will not use roads.
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
-- @return #AI_CARGO_DISPATCHER_APC self
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end

View File

@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
self:SetPickupSpeed( 350, 150 ) self:SetPickupSpeed( 350, 150 )
self:SetDeploySpeed( 350, 150 ) self:SetDeploySpeed( 350, 150 )
self:SetPickupRadius( 0, 0 ) self:SetPickupRadius( 40, 12 )
self:SetDeployRadius( 0, 0 ) self:SetDeployRadius( 40, 12 )
self:SetPickupHeight( 500, 200 ) self:SetPickupHeight( 500, 200 )
self:SetDeployHeight( 500, 200 ) self:SetDeployHeight( 500, 200 )
@@ -186,6 +186,9 @@ end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet ) function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
dispatcher:SetLandingSpeedAndHeight(27, 6)
return dispatcher
end end

View File

@@ -0,0 +1,193 @@
--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships
--
-- ## Features:
--
-- * Transport cargo to various deploy zones using naval vehicles.
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
-- * Define a deploy zone of various types to determine the destination of the cargo.
-- * Ships will follow shipping lanes as defined in the Mission Editor.
-- * Multiple ships can transport multiple cargo as a single group.
--
-- ===
--
-- ## Test Missions:
--
-- NEED TO DO
--
-- ===
--
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Ship
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_DISPATCHER_SHIP
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo transportation capability for AI groups.
--
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
--
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
--
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
--
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
--
--
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
--
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
--
-- ---
--
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
--
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
-- * Monitoring => Monitor => Monitoring
-- * Monitoring => Stop => Idle
--
-- * Monitoring => Pickup => Monitoring
-- * Monitoring => Load => Monitoring
-- * Monitoring => Loading => Monitoring
-- * Monitoring => Loaded => Monitoring
-- * Monitoring => PickedUp => Monitoring
-- * Monitoring => Deploy => Monitoring
-- * Monitoring => Unload => Monitoring
-- * Monitoring => Unloaded => Monitoring
-- * Monitoring => Deployed => Monitoring
-- * Monitoring => Home => Monitoring
--
--
-- ## 2.1) AI_CARGO_DISPATCHER States.
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_CARGO_DISPATCHER Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
-- * **Home**: A Carrier is going home.
--
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
--
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
--
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
--
-- ---
--
-- # 3) Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- # 4) Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- # 5) Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_SHIP
AI_CARGO_DISPATCHER_SHIP = {
ClassName = "AI_CARGO_DISPATCHER_SHIP"
}
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
-- @param #AI_CARGO_DISPATCHER_SHIP self
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
-- @return #AI_CARGO_DISPATCHER_SHIP
-- @usage
--
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
--
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
--
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
-- AICargoDispatcherShip:Start()
--
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
self:SetPickupSpeed( 60, 10 )
self:SetDeploySpeed( 60, 10 )
self:SetPickupRadius( 500, 6000 )
self:SetDeployRadius( 500, 6000 )
self:SetPickupHeight( 0, 0 )
self:SetDeployHeight( 0, 0 )
self:SetShippingLane( ShippingLane )
self:SetMonitorTimeInterval( 600 )
return self
end
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
self.ShippingLane = ShippingLane
return self
end
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
end

View File

@@ -1,4 +1,4 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo). --- **AI** - Models the intelligent transportation of cargo using helicopters.
-- --
-- === -- ===
-- --
@@ -33,7 +33,7 @@
-- --
-- ## Infantry health. -- ## Infantry health.
-- --
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. -- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards. -- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. -- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. -- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
@@ -64,25 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" ) self:SetStartState( "Unloaded" )
-- Boarding
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
self:AddTransition( "Unloaded", "Pickup", "*" ) -- Unboarding
self:AddTransition( "Loaded", "Deploy", "*" ) self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Destroyed", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) -- RTB
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Boarding" )
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Home" , "*" ) self:AddTransition( "*", "Home" , "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER --- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
@@ -100,13 +99,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @param Core.Point#COORDINATE Coordinate -- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
--- Pickup Trigger for AI_CARGO_HELICOPTER --- Pickup Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup -- @function [parent=#AI_CARGO_HELICOPTER] Pickup
-- @param #AI_CARGO_HELICOPTER self -- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate -- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
@@ -122,7 +139,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed. -- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @return #boolean -- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
@@ -132,21 +149,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @param Core.Point#COORDINATE Coordinate -- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Deploy Trigger for AI_CARGO_HELICOPTER --- Deploy Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy -- @function [parent=#AI_CARGO_HELICOPTER] Deploy
-- @param #AI_CARGO_HELICOPTER self -- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
-- @param #number Delay Delay in seconds. -- @param #number Delay Delay in seconds.
-- @param #AI_CARGO_HELICOPTER self -- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
--- Home Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Home
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height (optional) Height the Helicopter should be flying at.
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
-- @param #number Delay Delay in seconds.
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height (optional) Height the Helicopter should be flying at.
-- We need to capture the Crash events for the helicopters. -- We need to capture the Crash events for the helicopters.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
@@ -173,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self:SetCarrier( Helicopter ) self:SetCarrier( Helicopter )
self.landingspeed = 15 -- kph
self.landingheight = 5.5 -- meter
return self return self
end end
@@ -221,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
return self return self
end end
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
-- @param #AI_CARGO_HELICOPTER self
-- @param #number speed Landing speed in kph(!), e.g. 15
-- @param #number height Landing height in meters(!), e.g. 5.5
-- @return #AI_CARGO_HELICOPTER self
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
--
-- BASE:TraceOn()
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
--
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
-- Adjust if necessary.
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
local _speed = speed or 15
local _height = height or 5.5
self.landingheight = _height
self.landingspeed = _speed
return self
end
--- @param #AI_CARGO_HELICOPTER self --- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter -- @param Wrapper.Group#GROUP Helicopter
@@ -228,7 +288,7 @@ end
-- @param Event -- @param Event
-- @param To -- @param To
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
self:F({From, Event, To})
Helicopter:F( { Name = Helicopter:GetName() } ) Helicopter:F( { Name = Helicopter:GetName() } )
if Helicopter and Helicopter:IsAlive() then if Helicopter and Helicopter:IsAlive() then
@@ -237,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
-- 1 - When the helo lands normally on the ground. -- 1 - When the helo lands normally on the ground.
-- 2 - when the helo is hit and goes RTB or even when it is destroyed. -- 2 - when the helo is hit and goes RTB or even when it is destroyed.
-- For point 2, this is an issue, the infantry may not unload in this case! -- For point 2, this is an issue, the infantry may not unload in this case!
-- So we check if the helo is on the ground, and velocity< 5. -- So we check if the helo is on the ground, and velocity< 15.
-- Only then the infantry can unload (and load too, for consistency)! -- Only then the infantry can unload (and load too, for consistency)!
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
if self.RoutePickup == true then if self.RoutePickup == true then
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
--self:Load( Helicopter:GetPointVec2() ) --self:Load( Helicopter:GetPointVec2() )
self:Load( self.PickupZone ) self:Load( self.PickupZone )
self.RoutePickup = false self.RoutePickup = false
@@ -251,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
end end
if self.RouteDeploy == true then if self.RouteDeploy == true then
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
self:Unload( self.DeployZone ) self:Unload( self.DeployZone )
self.RouteDeploy = false self.RouteDeploy = false
end end
@@ -269,7 +329,7 @@ end
-- @param Core.Point#COORDINATE Coordinate -- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed -- @param #number Speed
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
self:F({From, Event, To, Coordinate, Speed, DeployZone})
local HelicopterInZone = false local HelicopterInZone = false
if Helicopter and Helicopter:IsAlive() == true then if Helicopter and Helicopter:IsAlive() == true then
@@ -308,7 +368,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina
-- true -- true
-- ) -- )
-- Route[#Route+1] = WaypointFrom -- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir( local WaypointTo = CoordinateTo:WaypointAir(
"RADIO", "RADIO",
POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointType.TurningPoint,
@@ -348,28 +412,17 @@ end
-- @param Core.Point#COORDINATE Coordinate -- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed -- @param #number Speed
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate ) function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
self:F({From, Event, To, Coordinate})
if Helicopter and Helicopter:IsAlive() then if Helicopter and Helicopter:IsAlive() then
local Route = {} local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate() local CoordinateTo = Coordinate
-- local WaypointFrom = CoordinateFrom:WaypointAir( local landheight = CoordinateTo:GetLandHeight() -- get target height
-- "RADIO", CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
-- POINT_VEC3.RoutePointType.TurningPoint,
-- POINT_VEC3.RoutePointAction.TurningPoint, local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
local Tasks = {} local Tasks = {}
@@ -379,7 +432,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
-- Now route the helicopter -- Now route the helicopter
Helicopter:Route( Route, 0 ) Helicopter:Route(Route, 0)
end end
end end
@@ -395,7 +448,7 @@ end
-- @param #boolean Deployed Cargo is deployed. -- @param #boolean Deployed Cargo is deployed.
-- @return #boolean True if all cargo has been unloaded. -- @return #boolean True if all cargo has been unloaded.
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } ) self:F( { From, Event, To, DeployZone = DeployZone } )
self:Orbit( Helicopter:GetCoordinate(), 50 ) self:Orbit( Helicopter:GetCoordinate(), 50 )
@@ -408,7 +461,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone ) self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
end end
--- On after Pickup event. --- On after Pickup event.
@@ -418,11 +470,12 @@ end
-- @param Event -- @param Event
-- @param To -- @param To
-- @param Core.Point#COORDINATE Coordinate Pickup place. -- @param Core.Point#COORDINATE Coordinate Pickup place.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. -- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
self:F({Coordinate, Speed, Height, PickupZone })
if Helicopter and Helicopter:IsAlive() ~= nil then if Helicopter and Helicopter:IsAlive() ~= nil then
Helicopter:Activate() Helicopter:Activate()
@@ -436,25 +489,16 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin
--- Calculate the target route point. --- Calculate the target route point.
local CoordinateFrom = Helicopter:GetCoordinate() local CoordinateFrom = Helicopter:GetCoordinate()
local CoordinateTo = Coordinate
--- Create a route point of type air. --- Create a route point of type air.
local WaypointFrom = CoordinateFrom:WaypointAir( local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
_speed,
true
)
--- Create a route point of type air. --- Create a route point of type air.
local WaypointTo = CoordinateTo:WaypointAir( local CoordinateTo = Coordinate
"RADIO", local landheight = CoordinateTo:GetLandHeight() -- get target height
POINT_VEC3.RoutePointType.TurningPoint, CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
POINT_VEC3.RoutePointAction.TurningPoint,
_speed, local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
true
)
Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
@@ -495,10 +539,10 @@ end
-- @param Event -- @param Event
-- @param To -- @param To
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the deploy coordinate. -- @param #number Height Height in meters to move to the deploy coordinate.
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
if Helicopter and Helicopter:IsAlive() ~= nil then if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteDeploy = true self.RouteDeploy = true
@@ -514,25 +558,17 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
--- Create a route point of type air. --- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate() local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir( local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
_speed,
true
)
Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom
--- Create a route point of type air. --- Create a route point of type air.
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir( local CoordinateTo = Coordinate
"RADIO", local landheight = CoordinateTo:GetLandHeight() -- get target height
POINT_VEC3.RoutePointType.TurningPoint, CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
POINT_VEC3.RoutePointAction.TurningPoint,
_speed, local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
true
)
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
@@ -542,7 +578,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
local Tasks = {} local Tasks = {}
-- The _Deploy function does not exist.
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone ) Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) --Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
@@ -566,11 +604,12 @@ end
-- @param Event -- @param Event
-- @param To -- @param To
-- @param Core.Point#COORDINATE Coordinate Home place. -- @param Core.Point#COORDINATE Coordinate Home place.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate. -- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
self:F({From, Event, To, Coordinate, Speed, Height})
if Helicopter and Helicopter:IsAlive() ~= nil then if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteHome = true self.RouteHome = true
@@ -579,30 +618,23 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
--- Calculate the target route point. --- Calculate the target route point.
Coordinate.y = Height --Coordinate.y = Height
Height = Height or 50
Speed = Speed or Helicopter:GetSpeedMax()*0.5 Speed = Speed or Helicopter:GetSpeedMax()*0.5
--- Create a route point of type air. --- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate() local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir(
"RADIO", local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
Speed ,
true
)
Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom
--- Create a route point of type air. --- Create a route point of type air.
local CoordinateTo = Coordinate local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir( local landheight = CoordinateTo:GetLandHeight() -- get target height
"RADIO", CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint, local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
Speed ,
true
)
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
@@ -613,12 +645,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks ) Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo
-- Now route the helicopter -- Now route the helicopter
Helicopter:Route( Route, 0 ) Helicopter:Route(Route, 0)
end end
end end

View File

@@ -0,0 +1,397 @@
--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
--
-- ===
--
-- @module AI.AI_Cargo_Ship
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_SHIP
-- @extends AI.AI_Cargo#AI_CARGO
--- Brings a dynamic cargo handling capability for an AI naval group.
--
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
-- Please consult the @{Cargo.Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
-- cargo and the specified boarding radius.
--
-- ## Defending the Ship when enemies are nearby
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
--
-- ## Infantry or cargo **health**.
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
-- between warehouses.
--
-- ## Control the Ships on the map.
--
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
--
-- ## Cargo deployment.
--
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
--
--
-- @field #AI_CARGO_SHIP
AI_CARGO_SHIP = {
ClassName = "AI_CARGO_SHIP",
Coordinate = nil -- Core.Point#COORDINATE
}
--- Creates a new AI_CARGO_SHIP object.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
self:AddTransition( "*", "Home", "*" )
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
self:SetShippingLane ( ShippingLane )
self:SetCarrier( Ship )
return self
end
--- Set the Carrier
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
function CargoCarrier:OnEventDead( EventData )
self:F({"dead"})
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- Better hope they can swim!
AICargoTroops:Destroyed()
end
end
end
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
self.Coalition = self.CargoCarrier:GetCoalition()
self:SetControllable( CargoCarrier )
return self
end
--- FInd a free Carrier within a radius
-- @param #AI_CARGO_SHIP self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttributes( "Trucks" ) then
return NearUnit:GetGroup()
end
end
end
return nil
end
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
self.ShippingLane = ShippingLane
return self
end
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
self.CombatRadius = CombatRadius or 0
return self
end
--- Follow Infantry to the Carrier
-- @param #AI_CARGO_SHIP self
-- @param #AI_CARGO_SHIP Me
-- @param Wrapper.Unit#UNIT ShipUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_SHIP
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
if ShipUnit:IsAlive() then
-- Check if the Cargo is near the CargoCarrier
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
-- Cargo does not need to navigate to Carrier
Me:Guard()
else
self:F( { InfantryGroup = InfantryGroup:GetName() } )
if InfantryGroup:IsAlive() then
self:F( { InfantryGroup = InfantryGroup:GetName() } )
local Waypoints = {}
-- Calculate new route
local FromCoord = InfantryGroup:GetCoordinate()
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
self:F({Waypoints=Waypoints})
local Waypoint = Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
end
end
end
end
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
if self.CombatRadius > 0 then
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
-- This is to avoid Unloading the Ship in the middle of the sea.
if Ship and Ship:IsAlive() then
if self.CarrierCoordinate then
if self:IsTransporting() == true then
local Coordinate = Ship:GetCoordinate()
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) then
-- There are no enemies within combat radius. Reload the CargoCarrier.
self:Reload()
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat radius. Unload the CargoCarrier.
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
else
if self:Is( "Unloaded" ) then
--self:Follow()
end
self:F( "I am here" .. self:GetCurrentState() )
if self:Is( "Following" ) then
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
if Cargo:IsAlive() then
if not Cargo:IsNear( ShipUnit, 40 ) then
ShipUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( ShipUnit, 25 ) then
ShipUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = Ship:GetCoordinate()
end
self:__Monitor( -5 )
end
end
--- Check if cargo ship is alive and trigger Load event
-- @param Wrapper.Group#Group Ship
-- @param #AI_CARGO_SHIP self
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
if Ship:IsAlive() then
self:Load( PickupZone )
end
end
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
-- @param Wrapper.Group#GROUP Ship
-- @param #AI_CARGO_SHIP self
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
if Ship:IsAlive() then
self:Unload( DeployZone )
end
end
--- on after Pickup event.
-- @param AI_CARGO_SHIP Ship
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate of the pickup point
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
if Ship and Ship:IsAlive() then
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
end
end
--- On after Deploy event.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP SHIP
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
if Ship and Ship:IsAlive() then
Speed = Speed or Ship:GetSpeedMax()*0.8
local lane = self.ShippingLane
if lane then
local Waypoints = {}
for i=1, #lane do
local coord = lane[i]
local Waypoint = coord:WaypointGround(_speed)
table.insert(Waypoints, Waypoint)
end
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
local Waypoint = Waypoints[#Waypoints]
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
Ship:Route(Waypoints, 1)
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end
--- On after Unload event.
-- @param #AI_CARGO_SHIP self
-- @param Wrapper.Group#GROUP Ship
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
local UnboardInterval = 5
local UnboardDelay = 5
if Ship and Ship:IsAlive() then
for _, ShipUnit in pairs( Ship:GetUnits() ) do
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
Ship:RouteStop()
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
if Cargo:IsLoaded() then
local unboardCoord = DeployZone:GetRandomPointVec2()
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
if not Defend == true then
Cargo:SetDeployed( true )
end
end
end
end
end
end
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
if Ship and Ship:IsAlive() then
self.RouteHome = true
Speed = Speed or Ship:GetSpeedMax()*0.8
local lane = self.ShippingLane
if lane then
local Waypoints = {}
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
for i=#lane, 1, -1 do
local coord = lane[i]
local Waypoint = coord:WaypointGround(_speed)
table.insert(Waypoints, Waypoint)
end
local Waypoint = Waypoints[#Waypoints]
Ship:Route(Waypoints, 1)
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end

View File

@@ -184,11 +184,13 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
self.FollowGroupSet:ForEachGroup( self.FollowGroupSet:ForEachGroup(
function( FollowGroup ) function( FollowGroup )
self:E("Following") --self:E("Following")
FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation ) FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation )
end end
) )
self:SetFlightModeFormation()
self:SetFlightRandomization( 2 ) self:SetFlightRandomization( 2 )
self:SetStartState( "None" ) self:SetStartState( "None" )
@@ -997,7 +999,7 @@ function AI_FORMATION:SetFlightModeMission( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission )
else else
self.EscortGroupSet:ForSomeGroupAlive( self.FollowGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup --- @param Core.Group#GROUP EscortGroup
function( FollowGroup ) function( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
@@ -1021,7 +1023,7 @@ function AI_FORMATION:SetFlightModeAttack( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack )
else else
self.EscortGroupSet:ForSomeGroupAlive( self.FollowGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup --- @param Core.Group#GROUP EscortGroup
function( FollowGroup ) function( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
@@ -1045,7 +1047,7 @@ function AI_FORMATION:SetFlightModeFormation( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation )
else else
self.EscortGroupSet:ForSomeGroupAlive( self.FollowGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup --- @param Core.Group#GROUP EscortGroup
function( FollowGroup ) function( FollowGroup )
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
@@ -1138,8 +1140,8 @@ end
-- @param DCS#Vec3 CV2 Vec3 -- @param DCS#Vec3 CV2 Vec3
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
FollowGroup:OptionROTEvadeFire() FollowGroup:OptionROTEvadeFire()

View File

@@ -4,7 +4,7 @@
-- --
-- * Patrol AI airplanes within a given zone. -- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected. -- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel treshold to RTB on time. -- * Manage a fuel threshold to RTB on time.
-- --
-- === -- ===
-- --
@@ -16,7 +16,7 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling) -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
-- --
-- === -- ===
-- --
@@ -72,8 +72,8 @@
-- --
-- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG)
-- --
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. -- Until a fuel or damage threshold 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. -- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
-- --
-- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG)
-- --
@@ -101,7 +101,7 @@
-- * **RTB** ( Group ): Route the AI to the home base. -- * **RTB** ( Group ): Route the AI to the home base.
-- * **Detect** ( Group ): The AI is detecting targets. -- * **Detect** ( Group ): The AI is detecting targets.
-- * **Detected** ( Group ): The AI has detected new 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. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
-- --
-- ## 3. Set or Get the AI controllable -- ## 3. Set or Get the AI controllable
-- --
@@ -133,8 +133,8 @@
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE -- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
-- --
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. -- 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. -- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, -- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
-- while a new AI is targetted to the AI_PATROL_ZONE. -- while a new AI is targetted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base. -- Once the time is finished, the old AI will return to the base.
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place. -- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place.
@@ -142,7 +142,7 @@
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE -- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
-- --
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. -- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). -- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place. -- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place.
-- --
-- === -- ===
@@ -581,11 +581,11 @@ function AI_PATROL_ZONE:ClearDetectedUnits()
end 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. --- 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. -- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold 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_PATROL_ZONE. -- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base. -- Once the time is finished, the old AI will return to the base.
-- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. -- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. -- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_PATROL_ZONE self -- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
@@ -596,14 +596,14 @@ function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFu
return self return self
end end
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. --- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on. -- However, damage cannot be foreseen early on.
-- Therefore, when the damage treshold is reached, -- Therefore, when the damage threshold is reached,
-- the AI will return immediately to the home base (RTB). -- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated. -- 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. -- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
-- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. -- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_PATROL_ZONE self -- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold ) function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
@@ -725,8 +725,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
return return
end end
local life = self.Controllable:GetLife() or 0
if self.Controllable:IsAlive() then if self.Controllable:IsAlive() and life > 1 then
-- Determine if the AIControllable is within the PatrolZone. -- Determine if the AIControllable is within the PatrolZone.
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
@@ -743,8 +743,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
if self.Controllable:InAir() == false then if self.Controllable:InAir() == false then
self:T( "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() local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). if not CurrentVec2 then return end
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() --Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -758,8 +759,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
else else
self:T( "In the air, finding route path within PatrolZone" ) self:T( "In the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2() local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). if not CurrentVec2 then return end
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -870,9 +872,10 @@ function AI_PATROL_ZONE:onafterRTB()
--- Calculate the current route point. --- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2() local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return end
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir( local CurrentRoutePoint = CurrentPointVec3:WaypointAir(

View File

@@ -190,7 +190,7 @@ do -- ACT_ROUTE
self:F( { ZoneName = ZoneName } ) self:F( { ZoneName = ZoneName } )
local Zone = Zone -- Core.Zone#ZONE local Zone = Zone -- Core.Zone#ZONE
local ZoneCoord = Zone:GetCoordinate() local ZoneCoord = Zone:GetCoordinate()
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate ) local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
self:F( { ShortestDistance, ShortestReferenceName } ) self:F( { ShortestDistance, ShortestReferenceName } )
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
ShortestDistance = ZoneDistance ShortestDistance = ZoneDistance

View File

@@ -1,4 +1,4 @@
--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers. --- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers.
-- --
-- === -- ===
-- --
@@ -194,7 +194,7 @@
-- * is of type `Workmaterials` -- * is of type `Workmaterials`
-- * will report when a carrier is within 500 meters -- * will report when a carrier is within 500 meters
-- * will board to carriers when the carrier is within 500 meters from the cargo object -- * will board to carriers when the carrier is within 500 meters from the cargo object
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding -- * will disappear when the cargo is within 25 meters from the carrier during boarding
-- --
-- So the overall syntax of the #CARGO naming tag and arguments are: -- So the overall syntax of the #CARGO naming tag and arguments are:
-- --
@@ -206,27 +206,29 @@
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.
-- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. -- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters.
-- --
-- ## 5.2) The \#CARGO tag to create CARGO_CRATE objects: -- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects:
-- --
-- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor. -- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor.
-- --
-- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts. -- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts.
-- --
-- `Static #CARGO(T=Workmaterials,RR=500,NR=25)` -- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)`
-- --
-- This will create a CARGO_CRATE object: -- This will create a CARGO_CRATE object:
-- --
-- * with the group name `Static #CARGO` -- * with the group name `Static #CARGO`
-- * is of type `Workmaterials` -- * is of type `Workmaterials`
-- * is of category `CRATE` (as opposed to `SLING`)
-- * will report when a carrier is within 500 meters -- * will report when a carrier is within 500 meters
-- * will board to carriers when the carrier is within 500 meters from the cargo object -- * will board to carriers when the carrier is within 500 meters from the cargo object
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding -- * will disappear when the cargo is within 25 meters from the carrier during boarding
-- --
-- So the overall syntax of the #CARGO naming tag and arguments are: -- So the overall syntax of the #CARGO naming tag and arguments are:
-- --
-- `StaticName #CARGO(T=CargoTypeName,RR=Range,NR=Range)` -- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)`
-- --
-- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. -- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object.
-- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectivly.
-- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. -- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier.
-- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. -- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters.
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.
@@ -1066,18 +1068,26 @@ do -- CARGO_REPRESENTABLE
--- CARGO_REPRESENTABLE Constructor. --- CARGO_REPRESENTABLE Constructor.
-- @param #CARGO_REPRESENTABLE self -- @param #CARGO_REPRESENTABLE self
-- @param #string Type -- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object.
-- @param #string Name -- @param #string Type Type name
-- @param #number LoadRadius (optional) -- @param #string Name Name.
-- @param #number NearRadius (optional) -- @param #number LoadRadius (optional) Radius in meters.
-- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier.
-- @return #CARGO_REPRESENTABLE -- @return #CARGO_REPRESENTABLE
function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius ) function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius )
-- Inherit CARGO.
local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE
self:F( { Type, Name, LoadRadius, NearRadius } ) self:F( { Type, Name, LoadRadius, NearRadius } )
local Desc = CargoObject:GetDesc() -- Descriptors.
self:I( { Desc = Desc } ) local Desc=CargoObject:GetDesc()
self:T({Desc=Desc})
-- Weight.
local Weight = math.random( 80, 120 ) local Weight = math.random( 80, 120 )
-- Adjust weight..
if Desc then if Desc then
if Desc.typeName == "2B11 mortar" then if Desc.typeName == "2B11 mortar" then
Weight = 210 Weight = 210
@@ -1086,13 +1096,8 @@ do -- CARGO_REPRESENTABLE
end end
end end
-- Set weight.
self:SetWeight( Weight ) self:SetWeight( Weight )
-- local Box = CargoUnit:GetBoundingBox()
-- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z )
-- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } )
--self:SetVolume( VolumeUnit )
return self return self
end end

View File

@@ -1,4 +1,4 @@
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object. --- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
-- --
-- === -- ===
-- --
@@ -56,6 +56,8 @@ do -- CARGO_GROUP
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene. -- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
-- @return #CARGO_GROUP Cargo group object. -- @return #CARGO_GROUP Cargo group object.
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
-- Inherit CAROG_REPORTABLE
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
self:F( { Type, Name, LoadRadius } ) self:F( { Type, Name, LoadRadius } )
@@ -76,6 +78,9 @@ do -- CARGO_GROUP
local GroupName = CargoGroup:GetName() local GroupName = CargoGroup:GetName()
self.CargoName = Name self.CargoName = Name
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) ) self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
-- Deactivate late activation.
self.CargoTemplate.lateActivation=false
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
self.GroupTemplate.name = self.CargoName .. "#CARGO" self.GroupTemplate.name = self.CargoName .. "#CARGO"
@@ -481,7 +486,7 @@ do -- CARGO_GROUP
-- @param #string Event -- @param #string Event
-- @param #string From -- @param #string From
-- @param #string To -- @param #string To
-- @param Core.Point#POINT_VEC2 -- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... )
--self:F( { From, Event, To, ToPointVec2 } ) --self:F( { From, Event, To, ToPointVec2 } )
@@ -491,7 +496,10 @@ do -- CARGO_GROUP
self.CargoSet:ForEach( self.CargoSet:ForEach(
function( Cargo ) function( Cargo )
--Cargo:UnLoad( ToPointVec2 ) --Cargo:UnLoad( ToPointVec2 )
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) local RandomVec2=nil
if ToPointVec2 then
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
end
Cargo:UnBoard( RandomVec2 ) Cargo:UnBoard( RandomVec2 )
end end
) )

View File

@@ -1,4 +1,4 @@
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object. --- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
-- --
-- === -- ===
-- --
@@ -46,14 +46,17 @@ do -- CARGO_UNIT
-- @param #number NearRadius (optional) -- @param #number NearRadius (optional)
-- @return #CARGO_UNIT -- @return #CARGO_UNIT
function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
self:I( { Type, Name, LoadRadius, NearRadius } )
self:T( CargoUnit ) -- Inherit CARGO_REPRESENTABLE.
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
-- Debug info.
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
-- Set cargo object.
self.CargoObject = CargoUnit self.CargoObject = CargoUnit
self:T( self.ClassName ) -- Set event prio.
self:SetEventPriority( 5 ) self:SetEventPriority( 5 )
return self return self
@@ -100,7 +103,12 @@ do -- CARGO_UNIT
-- Respawn the group... -- Respawn the group...
if self.CargoObject then if self.CargoObject then
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) if CargoCarrier:IsShip() then
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
else
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
end
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
self.CargoCarrier = nil self.CargoCarrier = nil

View File

@@ -0,0 +1,943 @@
--- **Core** - A* Pathfinding.
--
-- **Main Features:**
--
-- * Find path from A to B.
-- * Pre-defined as well as custom valid neighbour functions.
-- * Pre-defined as well as custom cost functions.
-- * Easy rectangular grid setup.
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Astar
-- @image CORE_Astar.png
--- ASTAR class.
-- @type ASTAR
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table nodes Table of nodes.
-- @field #number counter Node counter.
-- @field #number Nnodes Number of nodes.
-- @field #number nvalid Number of nvalid calls.
-- @field #number nvalidcache Number of cached valid evals.
-- @field #number ncost Number of cost evaluations.
-- @field #number ncostcache Number of cached cost evals.
-- @field #ASTAR.Node startNode Start node.
-- @field #ASTAR.Node endNode End node.
-- @field Core.Point#COORDINATE startCoord Start coordinate.
-- @field Core.Point#COORDINATE endCoord End coordinate.
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
-- @field #table CostArg Optional arguments passed to the cost function.
-- @extends Core.Base#BASE
--- *When nothing goes right... Go left!*
--
-- ===
--
-- # The ASTAR Concept
--
-- Pathfinding algorithm.
--
--
-- # Start and Goal
--
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
--
-- ## Start
--
-- The start
--
-- ## Goal
--
--
-- # Nodes
--
-- ## Rectangular Grid
--
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
--
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
--
-- ## Valid Surfaces
--
-- Certain unit types can only travel on certain surfaces types, for example
--
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
-- * Ground units can only traval on land.
--
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
--
-- ## Box Width (BoxHY)
--
-- The box width needs to be large enough to capture all paths you want to consider.
--
-- ## Space in X
--
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
--
-- ## Grid Spacing
--
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
-- However, if the value is too large, the algorithm might fail to get a valid path.
--
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
--
-- # Valid Neighbours
--
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
--
-- ## Line of Sight
--
-- For naval
--
--
-- # Heuristic Cost
--
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
--
--
-- # Calculate the Path
--
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
-- describe the optimal path from the start node to the end node.
--
-- By default, the start and end node are include in the table that is returned.
--
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
--
-- Common reasons that a path cannot be found are:
--
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
-- * There simply is no valid path ==> you are screwed :(
--
--
-- # Examples
--
-- ## Strait of Hormuz
--
-- Carrier Group finds its way through the Stait of Hormuz.
--
-- ##
--
--
--
-- @field #ASTAR
ASTAR = {
ClassName = "ASTAR",
Debug = nil,
lid = nil,
nodes = {},
counter = 1,
Nnodes = 0,
ncost = 0,
ncostcache = 0,
nvalid = 0,
nvalidcache = 0,
}
--- Node data.
-- @type ASTAR.Node
-- @field #number id Node id.
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
-- @field #number surfacetype Surface type.
-- @field #table valid Cached valid/invalid nodes.
-- @field #table cost Cached cost.
--- ASTAR infinity.
-- @field #number INF
ASTAR.INF=1/0
--- ASTAR class version.
-- @field #string version
ASTAR.version="0.4.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add more valid neighbour functions.
-- TODO: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ASTAR object.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:New()
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
self.lid="ASTAR | "
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set coordinate from where to start.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
-- @return #ASTAR self
function ASTAR:SetStartCoordinate(Coordinate)
self.startCoord=Coordinate
return self
end
--- Set coordinate where you want to go.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate end coordinate.
-- @return #ASTAR self
function ASTAR:SetEndCoordinate(Coordinate)
self.endCoord=Coordinate
return self
end
--- Create a node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
-- @return #ASTAR.Node The node.
function ASTAR:GetNodeFromCoordinate(Coordinate)
local node={} --#ASTAR.Node
node.coordinate=Coordinate
node.surfacetype=Coordinate:GetSurfaceType()
node.id=self.counter
node.valid={}
node.cost={}
self.counter=self.counter+1
return node
end
--- Add a node to the table of grid nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @return #ASTAR self
function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
return self
end
--- Add a node to the table of grid nodes specifying its coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
-- @return #ASTAR.Node The node.
function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node)
return node
end
--- Check if the coordinate of a node has is at a valid surface type.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
-- @return #boolean If true, surface type of node is valid.
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
if SurfaceTypes then
if type(SurfaceTypes)~="table" then
SurfaceTypes={SurfaceTypes}
end
for _,surface in pairs(SurfaceTypes) do
if surface==Node.surfacetype then
return true
end
end
return false
else
return true
end
end
--- Add a function to determine if a neighbour of a node is valid.
-- @param #ASTAR self
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
self.ValidNeighbourFunc=NeighbourFunction
self.ValidNeighbourArg={}
if arg then
self.ValidNeighbourArg=arg
end
return self
end
--- Set valid neighbours to require line of sight between two nodes.
-- @param #ASTAR self
-- @param #number CorridorWidth Width of LoS corridor in meters.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourDistance(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourRoad(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
return self
end
--- Set the function which calculates the "cost" to go from one to another node.
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
-- Very often the distance between nodes is a good measure for the cost.
-- @param #ASTAR self
-- @param #function CostFunction Function that returns the "cost".
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetCostFunction(CostFunction, ...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
--- Set heuristic cost to go from one node to another to be their 2D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Grid functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a rectangular grid of nodes between star and end coordinate.
-- The coordinate system is oriented along the line between start and end point.
-- @param #ASTAR self
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
-- @return #ASTAR self
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
-- Note that internally
-- x coordinate is z: x-->z Line from start to end
-- y coordinate is x: y-->x Perpendicular
-- Grid length and width.
local Dz=SpaceX or 10000
local Dx=BoxHY and BoxHY/2 or 20000
-- Increments.
local dz=deltaX or 2000
local dx=deltaY or dz
-- Heading from start to end coordinate.
local angle=self.startCoord:HeadingTo(self.endCoord)
--Distance between start and end.
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
-- Origin of map. Needed to translate back to wanted position.
local co=COORDINATE:New(0, 0, 0)
local do1=co:Get2DDistance(self.startCoord)
local ho1=co:HeadingTo(self.startCoord)
-- Start of grid.
local xmin=-Dx
local zmin=-Dz
-- Number of grid points.
local nz=dist/dz+1
local nx=2*Dx/dx+1
-- Debug info.
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
self:T(self.lid..text)
-- Loop over x and z coordinate to create a 2D grid.
for i=1,nx do
-- x coordinate perpendicular to z.
local x=xmin+dx*(i-1)
for j=1,nz do
-- z coordinate connecting start and end.
local z=zmin+dz*(j-1)
-- Rotate 2D.
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
-- Coordinate of the node.
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
-- Create a node at this coordinate.
local node=self:GetNodeFromCoordinate(c)
-- Check if node has valid surface type.
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
if MarkGrid then
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
end
-- Add node to grid.
self:AddNode(node)
end
end
end
-- Debug info.
local text=string.format("Done building grid!")
self:T2(self.lid..text)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Valid neighbour functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function to check if two nodes have line of sight (LoS).
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number corridor (Optional) Width of corridor in meters.
-- @return #boolean If true, two nodes have LoS.
function ASTAR.LoS(nodeA, nodeB, corridor)
local offset=1
local dx=corridor and corridor/2 or nil
local dy=dx
local cA=nodeA.coordinate:GetVec3()
local cB=nodeB.coordinate:GetVec3()
cA.y=offset
cB.y=offset
local los=land.isVisible(cA, cB)
if los and corridor then
-- Heading from A to B.
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
los=land.isVisible(Ap, Bp)
if los then
local Am=UTILS.VecTranslate(cA, dx, heading-90)
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
los=land.isVisible(Am, Bm)
end
end
return los
end
--- Function to check if two nodes have a road connection.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #boolean If true, two nodes are connected via a road.
function ASTAR.Road(nodeA, nodeB)
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
return true
else
return false
end
end
--- Function to check if distance between two nodes is less than a threshold distance.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number distmax Max distance in meters. Default is 2000 m.
-- @return #boolean If true, distance between the two nodes is below threshold.
function ASTAR.DistMax(nodeA, nodeB, distmax)
distmax=distmax or 2000
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
return dist<=distmax
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Heuristic cost functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic cost is given by the 2D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist2D(nodeA, nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
return dist
end
--- Heuristic cost is given by the 3D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist3D(nodeA, nodeB)
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
return dist
end
--- Heuristic cost is given by the distance between the nodes on road.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.DistRoad(nodeA, nodeB)
-- Get the path.
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
local dist=0
for i=2,#path do
local b=path[i] --DCS#Vec2
local a=path[i-1] --DCS#Vec2
dist=dist+UTILS.VecDist2D(a,b)
end
return dist
end
return math.huge
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find the closest node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate.
-- @return #ASTAR.Node Cloest node to the coordinate.
-- @return #number Distance to closest node in meters.
function ASTAR:FindClosestNode(Coordinate)
local distMin=math.huge
local closeNode=nil
for _,_node in pairs(self.nodes) do
local node=_node --#ASTAR.Node
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
distMin=dist
closeNode=node
end
end
return closeNode, distMin
end
--- Find the start node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindStartNode()
local node, dist=self:FindClosestNode(self.startCoord)
self.startNode=node
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
end
return self
end
--- Add a node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindEndNode()
local node, dist=self:FindClosestNode(self.endCoord)
self.endNode=node
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main A* pathfinding function
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
-- @param #ASTAR self
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
-- @return #table Table of nodes from start to finish.
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
-- Sets.
local openset = {}
local closedset = {}
local came_from = {}
local g_score = {}
local f_score = {}
openset[start.id]=true
local Nopen=1
-- Initial scores.
g_score[start.id]=0
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
-- Set start time.
local T0=timer.getAbsTime()
-- Debug message.
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
self:T(self.lid..text)
local Tstart=UTILS.GetOSTime()
-- Loop while we still have an open set.
while Nopen > 0 do
-- Get current node.
local current=self:_LowestFscore(openset, f_score)
-- Check if we are at the end node.
if current.id==goal.id then
local path=self:_UnwindPath({}, came_from, goal)
if not ExcludeEndNode then
table.insert(path, goal)
end
if ExcludeStartNode then
table.remove(path, 1)
end
local Tstop=UTILS.GetOSTime()
local dT=nil
if Tstart and Tstop then
dT=Tstop-Tstart
end
-- Debug message.
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
if dT then
text=text..string.format(", OS Time %.6f sec", dT)
end
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
self:T(self.lid..text)
return path
end
-- Move Node from open to closed set.
openset[current.id]=nil
Nopen=Nopen-1
closedset[current.id]=true
-- Get neighbour nodes.
local neighbors=self:_NeighbourNodes(current, nodes)
-- Loop over neighbours.
for _,neighbor in pairs(neighbors) do
if self:_NotIn(closedset, neighbor.id) then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
came_from[neighbor]=current
g_score[neighbor.id]=tentative_g_score
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
if self:_NotIn(openset, neighbor.id) then
-- Add to open set.
openset[neighbor.id]=true
Nopen=Nopen+1
end
end
end
end
end
-- Debug message.
local text=string.format("WARNING: Could NOT find valid path!")
self:E(self.lid..text)
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
return nil -- no valid path
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- A* pathfinding helper functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number "Cost" to go from node A to node B.
function ASTAR:_HeuristicCost(nodeA, nodeB)
-- Counter.
self.ncost=self.ncost+1
-- Get chached cost if available.
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA, nodeB)
end
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
return cost
end
--- Check if going from a node to a neighbour is possible.
-- @param #ASTAR self
-- @param #ASTAR.Node node A node.
-- @param #ASTAR.Node neighbor Neighbour node.
-- @return #boolean If true, transition between nodes is possible.
function ASTAR:_IsValidNeighbour(node, neighbor)
-- Counter.
self.nvalid=self.nvalid+1
local valid=node.valid[neighbor.id]
if valid~=nil then
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
self.nvalidcache=self.nvalidcache+1
return valid
end
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
else
valid=true
end
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid -- Symmetric problem.
return valid
end
--- Calculate 2D distance between two nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number Distance between nodes in meters.
function ASTAR:_DistNodes(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
--- Function that calculates the lowest F score.
-- @param #ASTAR self
-- @param #table set The set of nodes IDs.
-- @param #number f_score F score.
-- @return #ASTAR.Node Best node.
function ASTAR:_LowestFscore(set, f_score)
local lowest, bestNode = ASTAR.INF, nil
for nid,node in pairs(set) do
local score=f_score[nid]
if score<lowest then
lowest, bestNode = score, nid
end
end
return self.nodes[bestNode]
end
--- Function to get valid neighbours of a node.
-- @param #ASTAR self
-- @param #ASTAR.Node theNode The node.
-- @param #table nodes Possible neighbours.
-- @param #table Valid neighbour nodes.
function ASTAR:_NeighbourNodes(theNode, nodes)
local neighbors = {}
for _,node in pairs(nodes) do
if theNode.id~=node.id then
local isvalid=self:_IsValidNeighbour(theNode, node)
if isvalid then
table.insert(neighbors, node)
end
end
end
return neighbors
end
--- Function to check if a node is not in a set.
-- @param #ASTAR self
-- @param #table set Set of nodes.
-- @param #ASTAR.Node theNode The node to check.
-- @return #boolean If true, the node is not in the set.
function ASTAR:_NotIn(set, theNode)
return set[theNode]==nil
end
--- Unwind path function.
-- @param #ASTAR self
-- @param #table flat_path Flat path.
-- @param #table map Map.
-- @param #ASTAR.Node current_node The current node.
-- @return #table Unwinded path.
function ASTAR:_UnwindPath( flat_path, map, current_node )
if map [current_node] then
table.insert (flat_path, 1, map[current_node])
return self:_UnwindPath(flat_path, map, map[current_node])
else
return flat_path
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,478 @@
--- **Core** - TACAN and other beacons.
--
-- ===
--
-- ## Features:
--
-- * Provide beacon functionality to assist pilots.
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Beacon
-- @image Core_Radio.JPG
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
--
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the (aircraft) TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## Aircraft TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON.ActivateTACAN}() to set the beacon parameters and start the beacon.
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON.RadioBeacon}() to set the beacon parameters and start the beacon.
-- 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,
}
--- 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 glide slope.
-- @field #number PRGM_LOCALIZER PRGM localizer.
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
-- @field #number BROADCAST_STATION Broadcast station.
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional 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 object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
-- 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
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
return nil
end
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @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})
Mode=Mode or "Y"
-- 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
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- 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=="X" then
--self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable})
System=BEACON.System.TACAN_TANKER_X
else
System=BEACON.System.TACAN_TANKER_Y
end
end
-- 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 scheduler.
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 scheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a LINK4 BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Frequency LINK4 FRequency in MHz, eg 336.
-- @param #string Morse The ID 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:ActivateLink4(Frequency, Morse, Duration)
self:F({Frequency=Frequency, Morse=Morse, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"LINK4 BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:CommandDeactivateLink4(Duration)
end
return self
end
--- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead.
-- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
-- @param #boolean Bearing Can the BEACON be homed on ?
-- @param #number BeaconDuration 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:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration})
local IsValid = true
if not self.Positionable:IsAir() then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
IsValid = false
end
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = BEACON.System.TACAN_TANKER_Y
else
System = BEACON.System.TACAN_AA_MODE_Y
end
if IsValid then -- Starts the BEACON
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = BEACON.Type.TACAN,
system = System,
callsign = Message,
AA = true,
frequency = Frequency,
bearing = Bearing,
modeChannel = "Y",
}
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id = 'DeactivateBeacon',
params = {
}
})
end
end
--- Activates a general purpose Radio Beacon
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
-- Although any frequency could be used, only a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc.
-- The following e.g. can home in on these specific frequencies :
-- * **Mi8**
-- * R-828 -> 20-60MHz
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
-- * ARK9 -> 150-1300KHz
-- * **Huey**
-- * AN/ARC-131 -> 30-76 Mhz FM
-- @param #BEACON self
-- @param #string FileName The name of the audio file
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a beacon for a unit in distress.
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
-- -- The beacon they use is battery-powered, and only lasts for 5 min
-- local UnitInDistress = UNIT:FindByName("Unit1")
-- local UnitBeacon = UnitInDistress:GetBeacon()
--
-- -- Set the beacon and start it
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
-- Check the 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
IsValid = true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
end
-- Check the Frequency
if type(Frequency) ~= "number" and IsValid then
self:E({"Frequency invalid. ", Frequency})
IsValid = false
end
Frequency = Frequency * 1000000 -- Conversion to Hz
-- Check the modulation
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
IsValid = false
end
-- Check the Power
if type(Power) ~= "number" and IsValid then
self:E({"Power is invalid. ", Power})
IsValid = false
end
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
function()
self:StopRadioBeacon()
end, {}, BeaconDuration)
end
end
end
--- Stops the Radio Beacon
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
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,295 @@
--- **Core** - Define any or all conditions to be evaluated.
--
-- **Main Features:**
--
-- * Add arbitrary numbers of conditon functions
-- * Evaluate *any* or *all* conditions
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Condition
-- @image Core_Conditon.png
--- CONDITON class.
-- @type CONDITION
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #boolean isAny General functions are evaluated as any condition.
-- @field #boolean negateResult Negeate result of evaluation.
-- @field #table functionsGen General condition functions.
-- @field #table functionsAny Any condition functions.
-- @field #table functionsAll All condition functions.
--
-- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* - William Shakespeare
--
-- ===
--
-- # The CONDITION Concept
--
--
--
-- @field #CONDITION
CONDITION = {
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
}
--- Condition function.
-- @type CONDITION.Function
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
--- CONDITION class version.
-- @field #string version
CONDITION.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Make FSM.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new CONDITION object.
-- @param #CONDITION self
-- @param #string Name (Optional) Name used in the logs.
-- @return #CONDITION self
function CONDITION:New(Name)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
self.name=Name or "Condition X"
self.lid=string.format("%s | ", self.name)
return self
end
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
-- @param #CONDITION self
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
-- @return #CONDITION self
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
--- Negate result.
-- @param #CONDITION self
-- @param #boolean Negate If `true`, result is negated else not.
-- @return #CONDITION self
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
--
-- @usage
-- local function isAequalB(a, b)
-- return a==b
-- end
--
-- myCondition:AddFunction(isAequalB, a, b)
--
-- @return #CONDITION self
function CONDITION:AddFunction(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsGen, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAny(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAny, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAll(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAll, condition)
return self
end
--- Evaluate conditon functions.
-- @param #CONDITION self
-- @param #boolean AnyTrue If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
-- @return #boolean Result of condition functions.
function CONDITION:Evaluate(AnyTrue)
-- Check if at least one function was given.
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
if self.negateResult then
return true
else
return false
end
end
-- Any condition for gen.
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
-- Is any?
local isAny=self:_EvalConditionsAny(self.functionsAny)
-- Is all?
local isAll=self:_EvalConditionsAll(self.functionsAll)
-- Result.
local result=isGen and isAny and isAll
-- Negate result.
if self.negateResult then
result=not result
end
-- Debug message.
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result)))
return result
end
--- Check if all given condition are true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
function CONDITION:_EvalConditionsAll(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any false will return false.
if not istrue then
return false
end
end
-- All conditions were true.
return true
end
--- Check if any of the given conditions is true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
function CONDITION:_EvalConditionsAny(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any true will return true.
if istrue then
return true
end
end
-- No condition was true.
if gotone then
return false
else
-- No functions passed.
return true
end
end
--- Create conditon function object.
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION.Function Condition function.
function CONDITION:_CreateCondition(Function, ...)
local condition={} --#CONDITION.Function
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -24,7 +24,7 @@
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: -- ### Contributions: **funkyfranky**
-- --
-- === -- ===
-- --
@@ -33,6 +33,9 @@
--- @type DATABASE --- @type DATABASE
-- @field #string ClassName Name of the class.
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
-- @field #table CLIENTS Clients.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
@@ -121,13 +124,12 @@ function DATABASE:New()
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
--self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM
self:HandleEvent( EVENTS.Hit, self.AccountHits ) self:HandleEvent( EVENTS.Hit, self.AccountHits )
self:HandleEvent( EVENTS.NewCargo ) self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.DeleteCargo )
self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.NewZone )
self:HandleEvent( EVENTS.DeleteZone ) 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.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
@@ -136,41 +138,10 @@ function DATABASE:New()
self:_RegisterClients() self:_RegisterClients()
self:_RegisterStatics() self:_RegisterStatics()
--self:_RegisterPlayers() --self:_RegisterPlayers()
self:_RegisterAirbases() --self:_RegisterAirbases()
self.UNITS_Position = 0 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 )
--self:T( { "UnitData:", UnitData, UnitName, PlayerName, PlayerUnit } )
if PlayerName and PlayerName ~= "" then
if self.PLAYERS[PlayerName] == nil or self.PLAYERS[PlayerName] ~= UnitName then
--self:E( { "Add player for unit:", UnitName, PlayerName } )
self:AddPlayer( UnitName, PlayerName )
--_EVENTDISPATCHER:CreateEventPlayerEnterUnit( PlayerUnit )
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( PlayerUnit )
end
end
end
end
end
end
--self:E( "Scheduling" )
--PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
return self return self
end end
@@ -187,14 +158,16 @@ end
--- Adds a Unit based on the Unit Name in the DATABASE. --- Adds a Unit based on the Unit Name in the DATABASE.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string DCSUnitName Unit name.
-- @return Wrapper.Unit#UNIT The added unit.
function DATABASE:AddUnit( DCSUnitName ) function DATABASE:AddUnit( DCSUnitName )
if not self.UNITS[DCSUnitName] then if not self.UNITS[DCSUnitName] then
-- Debug info.
self:T( { "Add UNIT:", DCSUnitName } ) self:T( { "Add UNIT:", DCSUnitName } )
local UnitRegister = UNIT:Register( DCSUnitName )
self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName )
table.insert( self.UNITS_Index, DCSUnitName ) -- Register unit
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
end end
return self.UNITS[DCSUnitName] return self.UNITS[DCSUnitName]
@@ -204,12 +177,13 @@ end
--- Deletes a Unit from the DATABASE based on the Unit Name. --- Deletes a Unit from the DATABASE based on the Unit Name.
-- @param #DATABASE self -- @param #DATABASE self
function DATABASE:DeleteUnit( DCSUnitName ) function DATABASE:DeleteUnit( DCSUnitName )
self.UNITS[DCSUnitName] = nil self.UNITS[DCSUnitName] = nil
end end
--- Adds a Static based on the Static Name in the DATABASE. --- Adds a Static based on the Static Name in the DATABASE.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string DCSStaticName Name of the static.
-- @return Wrapper.Static#STATIC The static object.
function DATABASE:AddStatic( DCSStaticName ) function DATABASE:AddStatic( DCSStaticName )
if not self.STATICS[DCSStaticName] then if not self.STATICS[DCSStaticName] then
@@ -224,8 +198,7 @@ end
--- Deletes a Static from the DATABASE based on the Static Name. --- Deletes a Static from the DATABASE based on the Static Name.
-- @param #DATABASE self -- @param #DATABASE self
function DATABASE:DeleteStatic( DCSStaticName ) function DATABASE:DeleteStatic( DCSStaticName )
self.STATICS[DCSStaticName] = nil
--self.STATICS[DCSStaticName] = nil
end end
--- Finds a STATIC based on the StaticName. --- Finds a STATIC based on the StaticName.
@@ -238,16 +211,6 @@ function DATABASE:FindStatic( StaticName )
return StaticFound return StaticFound
end end
--- Finds a AIRBASE based on the AirbaseName.
-- @param #DATABASE self
-- @param #string AirbaseName
-- @return Wrapper.Airbase#AIRBASE The found AIRBASE.
function DATABASE:FindAirbase( AirbaseName )
local AirbaseFound = self.AIRBASES[AirbaseName]
return AirbaseFound
end
--- Adds a Airbase based on the Airbase Name in the DATABASE. --- Adds a Airbase based on the Airbase Name in the DATABASE.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string AirbaseName The name of the airbase. -- @param #string AirbaseName The name of the airbase.
@@ -319,24 +282,92 @@ do -- Zones
-- @return #DATABASE self -- @return #DATABASE self
function DATABASE:_RegisterZones() function DATABASE:_RegisterZones()
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
local ZoneName = ZoneData.name local ZoneName = ZoneData.name
self:I( { "Register ZONE:", Name = ZoneName } ) -- Color
local Zone = ZONE:New( ZoneName ) local color=ZoneData.color or {1, 0, 0, 0.15}
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone ) -- Create new Zone
local Zone=nil --Core.Zone#ZONE_BASE
if ZoneData.type==0 then
---
-- Circular zone
---
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
Zone=ZONE:New(ZoneName)
else
---
-- Quad-point zone
---
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
--for i,vec2 in pairs(ZoneData.verticies) do
-- local coord=COORDINATE:NewFromVec2(vec2)
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
--end
end
if Zone then
-- Store color of zone.
Zone.Color=color
-- Store zone ID.
Zone.ZoneID=ZoneData.zoneId
-- Store zone properties (if any)
local ZoneProperties = ZoneData.properties or nil
Zone.Properties = {}
if ZoneName and ZoneProperties then
for _,ZoneProp in ipairs(ZoneProperties) do
if ZoneProp.key then
Zone.Properties[ZoneProp.key] = ZoneProp.value
end
end
end
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
end
end end
-- Polygon zones defined by late activated groups.
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("#ZONE_POLYGON") then if ZoneGroupName:match("#ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) -- Debug output
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
-- Create a new polygon zone.
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
-- Set color.
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
-- Store name in DB.
self.ZONENAMES[ZoneName] = ZoneName self.ZONENAMES[ZoneName] = ZoneName
-- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon ) self:AddZone( ZoneName, Zone_Polygon )
end end
end end
@@ -432,7 +463,6 @@ do -- cargo
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! 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 for CargoGroupName, CargoGroup in pairs( Groups ) do
self:I( { Cargo = CargoGroupName } )
if self:IsCargo( CargoGroupName ) then if self:IsCargo( CargoGroupName ) then
local CargoInfo = CargoGroupName:match("#CARGO(.*)") local CargoInfo = CargoGroupName:match("#CARGO(.*)")
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
@@ -489,6 +519,8 @@ end
--- Adds a CLIENT based on the ClientName in the DATABASE. --- Adds a CLIENT based on the ClientName in the DATABASE.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string ClientName Name of the Client unit.
-- @return Wrapper.Client#CLIENT The client object.
function DATABASE:AddClient( ClientName ) function DATABASE:AddClient( ClientName )
if not self.CLIENTS[ClientName] then if not self.CLIENTS[ClientName] then
@@ -626,6 +658,9 @@ function DATABASE:Spawn( SpawnTemplate )
end end
--- Set a status to a Group within the Database, this to check crossing events for example. --- Set a status to a Group within the Database, this to check crossing events for example.
-- @param #DATABASE self
-- @param #string GroupName Group name.
-- @param #string Status Status.
function DATABASE:SetStatusGroup( GroupName, Status ) function DATABASE:SetStatusGroup( GroupName, Status )
self:F2( Status ) self:F2( Status )
@@ -633,8 +668,11 @@ function DATABASE:SetStatusGroup( GroupName, Status )
end end
--- Get a status to a Group within the Database, this to check crossing events for example. --- Get a status to a Group within the Database, this to check crossing events for example.
-- @param #DATABASE self
-- @param #string GroupName Group name.
-- @return #string Status or an empty string "".
function DATABASE:GetStatusGroup( GroupName ) function DATABASE:GetStatusGroup( GroupName )
self:F2( Status ) self:F2( GroupName )
if self.Templates.Groups[GroupName] then if self.Templates.Groups[GroupName] then
return self.Templates.Groups[GroupName].Status return self.Templates.Groups[GroupName].Status
@@ -648,7 +686,8 @@ end
-- @param #table GroupTemplate -- @param #table GroupTemplate
-- @param DCS#coalition.side CoalitionSide The coalition.side of the object. -- @param DCS#coalition.side CoalitionSide The coalition.side of the object.
-- @param DCS#Object.Category CategoryID The Object.category of the object. -- @param DCS#Object.Category CategoryID The Object.category of the object.
-- @param DCS#country.id CountryID the country.id of the object -- @param DCS#country.id CountryID the country ID of the object.
-- @param #string GroupName (Optional) The name of the group. Default is `GroupTemplate.name`.
-- @return #DATABASE self -- @return #DATABASE self
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
@@ -704,6 +743,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
end end
-- Debug info.
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
Category = self.Templates.Groups[GroupTemplateName].CategoryID, Category = self.Templates.Groups[GroupTemplateName].CategoryID,
@@ -713,6 +753,10 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
) )
end end
--- Get group template.
-- @param #DATABASE self
-- @param #string GroupName Group name.
-- @return #table Group template table.
function DATABASE:GetGroupTemplate( GroupName ) function DATABASE:GetGroupTemplate( GroupName )
local GroupTemplate = self.Templates.Groups[GroupName].Template local GroupTemplate = self.Templates.Groups[GroupName].Template
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
@@ -723,13 +767,18 @@ end
--- Private method that registers new Static Templates within the DATABASE Object. --- Private method that registers new Static Templates within the DATABASE Object.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #table StaticTemplate -- @param #table StaticTemplate Template table.
-- @param #number CoalitionID Coalition ID.
-- @param #number CategoryID Category ID.
-- @param #number CountryID Country ID.
-- @return #DATABASE self -- @return #DATABASE self
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID ) function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) local StaticTemplate = UTILS.DeepCopy( StaticTemplate )
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) local StaticTemplateGroupName = env.getValueDictByKey(StaticTemplate.name)
local StaticTemplateName=StaticTemplate.units[1].name
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
@@ -737,14 +786,15 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CoalitionID = CoalitionID
StaticTemplate.CountryID = CountryID StaticTemplate.CountryID = CountryID
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateGroupName
self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1]
self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID
self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID = CountryID self.Templates.Statics[StaticTemplateName].CountryID = CountryID
self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, -- Debug info.
self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
Category = self.Templates.Statics[StaticTemplateName].CategoryID, Category = self.Templates.Statics[StaticTemplateName].CategoryID,
Country = self.Templates.Statics[StaticTemplateName].CountryID Country = self.Templates.Statics[StaticTemplateName].CountryID
@@ -753,48 +803,115 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
self:AddStatic( StaticTemplateName ) self:AddStatic( StaticTemplateName )
return self
end end
--- Get static group template.
--- @param #DATABASE self -- @param #DATABASE self
-- @param #string StaticName Name of the static.
-- @return #table Static template table.
function DATABASE:GetStaticGroupTemplate( StaticName ) function DATABASE:GetStaticGroupTemplate( StaticName )
local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate if self.Templates.Statics[StaticName] then
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName))
return nil
end
end end
--- @param #DATABASE self --- Get static unit template.
-- @param #DATABASE self
-- @param #string StaticName Name of the static.
-- @return #table Static template table.
function DATABASE:GetStaticUnitTemplate( StaticName ) function DATABASE:GetStaticUnitTemplate( StaticName )
local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate if self.Templates.Statics[StaticName] then
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
return nil
end
end end
--- Get group name from unit name.
-- @param #DATABASE self
-- @param #string UnitName Name of the unit.
-- @return #string Group name.
function DATABASE:GetGroupNameFromUnitName( UnitName ) function DATABASE:GetGroupNameFromUnitName( UnitName )
return self.Templates.Units[UnitName].GroupName if self.Templates.Units[UnitName] then
return self.Templates.Units[UnitName].GroupName
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end end
--- Get group template from unit name.
-- @param #DATABASE self
-- @param #string UnitName Name of the unit.
-- @return #table Group template.
function DATABASE:GetGroupTemplateFromUnitName( UnitName ) function DATABASE:GetGroupTemplateFromUnitName( UnitName )
return self.Templates.Units[UnitName].GroupTemplate if self.Templates.Units[UnitName] then
return self.Templates.Units[UnitName].GroupTemplate
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end end
--- Get group template from unit name.
-- @param #DATABASE self
-- @param #string UnitName Name of the unit.
-- @return #table Group template.
function DATABASE:GetUnitTemplateFromUnitName( UnitName )
if self.Templates.Units[UnitName] then
return self.Templates.Units[UnitName]
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
--- Get coalition ID from client name.
-- @param #DATABASE self
-- @param #string ClientName Name of the Client.
-- @return #number Coalition ID.
function DATABASE:GetCoalitionFromClientTemplate( ClientName ) function DATABASE:GetCoalitionFromClientTemplate( ClientName )
return self.Templates.ClientsByName[ClientName].CoalitionID return self.Templates.ClientsByName[ClientName].CoalitionID
end end
--- Get category ID from client name.
-- @param #DATABASE self
-- @param #string ClientName Name of the Client.
-- @return #number Category ID.
function DATABASE:GetCategoryFromClientTemplate( ClientName ) function DATABASE:GetCategoryFromClientTemplate( ClientName )
return self.Templates.ClientsByName[ClientName].CategoryID return self.Templates.ClientsByName[ClientName].CategoryID
end end
--- Get country ID from client name.
-- @param #DATABASE self
-- @param #string ClientName Name of the Client.
-- @return #number Country ID.
function DATABASE:GetCountryFromClientTemplate( ClientName ) function DATABASE:GetCountryFromClientTemplate( ClientName )
return self.Templates.ClientsByName[ClientName].CountryID return self.Templates.ClientsByName[ClientName].CountryID
end end
--- Airbase --- Airbase
--- Get coalition ID from airbase name.
-- @param #DATABASE self
-- @param #string AirbaseName Name of the airbase.
-- @return #number Coalition ID.
function DATABASE:GetCoalitionFromAirbase( AirbaseName ) function DATABASE:GetCoalitionFromAirbase( AirbaseName )
return self.AIRBASES[AirbaseName]:GetCoalition() return self.AIRBASES[AirbaseName]:GetCoalition()
end end
--- Get category from airbase name.
-- @param #DATABASE self
-- @param #string AirbaseName Name of the airbase.
-- @return #number Category.
function DATABASE:GetCategoryFromAirbase( AirbaseName ) function DATABASE:GetCategoryFromAirbase( AirbaseName )
return self.AIRBASES[AirbaseName]:GetCategory() return self.AIRBASES[AirbaseName]:GetCategory()
end end
@@ -831,33 +948,38 @@ end
function DATABASE:_RegisterGroupsAndUnits() function DATABASE:_RegisterGroupsAndUnits()
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSGroupId, DCSGroup in pairs( CoalitionData ) do for DCSGroupId, DCSGroup in pairs( CoalitionData ) do
if DCSGroup:isExist() then if DCSGroup:isExist() then
-- Group name.
local DCSGroupName = DCSGroup:getName() local DCSGroupName = DCSGroup:getName()
self:I( { "Register Group:", DCSGroupName } ) -- Add group.
self:I(string.format("Register Group: %s", tostring(DCSGroupName)))
self:AddGroup( DCSGroupName ) self:AddGroup( DCSGroupName )
-- Loop over units in group.
for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do
-- Get unit name.
local DCSUnitName = DCSUnit:getName() local DCSUnitName = DCSUnit:getName()
self:I( { "Register Unit:", DCSUnitName } )
-- Add unit.
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
self:AddUnit( DCSUnitName ) self:AddUnit( DCSUnitName )
end end
else else
self:E( { "Group does not exist: ", DCSGroup } ) self:E({"Group does not exist: ", DCSGroup})
end end
end end
end end
self:T("Groups:")
for GroupName, Group in pairs( self.GROUPS ) do
self:T( { "Group:", GroupName } )
end
return self return self
end end
@@ -867,25 +989,27 @@ end
function DATABASE:_RegisterClients() function DATABASE:_RegisterClients()
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
self:T( { "Register Client:", ClientName } ) self:I(string.format("Register Client: %s", tostring(ClientName)))
self:AddClient( ClientName ) local client=self:AddClient( ClientName )
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
end end
return self return self
end end
--- @param #DATABASE self --- Private method that registeres all static objects.
-- @param #DATABASE self
function DATABASE:_RegisterStatics() function DATABASE:_RegisterStatics()
local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)}
self:I( { Statics = CoalitionsData } )
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSStaticId, DCSStatic in pairs( CoalitionData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do
if DCSStatic:isExist() then if DCSStatic:isExist() then
local DCSStaticName = DCSStatic:getName() local DCSStaticName = DCSStatic:getName()
self:T( { "Register Static:", DCSStaticName } ) self:I(string.format("Register Static: %s", tostring(DCSStaticName)))
self:AddStatic( DCSStaticName ) self:AddStatic( DCSStaticName )
else else
self:E( { "Static does not exist: ", DCSStatic } ) self:E( { "Static does not exist: ", DCSStatic } )
@@ -902,7 +1026,7 @@ end
function DATABASE:_RegisterAirbases() function DATABASE:_RegisterAirbases()
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
-- Get the airbase name. -- Get the airbase name.
local DCSAirbaseName = DCSAirbase:getName() local DCSAirbaseName = DCSAirbase:getName()
@@ -912,9 +1036,19 @@ function DATABASE:_RegisterAirbases()
-- Add and register airbase. -- Add and register airbase.
local airbase=self:AddAirbase( DCSAirbaseName ) local airbase=self:AddAirbase( DCSAirbaseName )
-- Unique ID.
local airbaseUID=airbase:GetID(true)
-- Debug output. -- Debug output.
self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType) do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])
end
end
text=text.."]"
self:I(text)
end end
return self return self
@@ -930,36 +1064,74 @@ function DATABASE:_EventOnBirth( Event )
self:F( { Event } ) self:F( { Event } )
if Event.IniDCSUnit then if Event.IniDCSUnit then
if Event.IniObjectCategory == 3 then if Event.IniObjectCategory == 3 then
self:AddStatic( Event.IniDCSUnitName ) self:AddStatic( Event.IniDCSUnitName )
else else
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
self:AddUnit( Event.IniDCSUnitName ) self:AddUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName ) self:AddGroup( Event.IniDCSGroupName )
-- Add airbase if it was spawned later in the mission. -- Add airbase if it was spawned later in the mission.
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then if DCSAirbase then
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName) self:AddAirbase(Event.IniDCSUnitName)
end end
end end
end end
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
-- Client
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
if client then
-- TODO: create event ClientAlive
end
-- Get player name.
local PlayerName = Event.IniUnit:GetPlayerName() local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName then if PlayerName then
self:I( { "Player Joined:", PlayerName } )
self:AddClient( Event.IniDCSUnitName ) -- Debug info.
self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
-- Add client in case it does not exist already.
if not client then
client=self:AddClient(Event.IniDCSUnitName)
end
-- Add player.
client:AddPlayer(PlayerName)
-- Add player.
if not self.PLAYERS[PlayerName] then if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniUnitName, PlayerName ) self:AddPlayer( Event.IniUnitName, PlayerName )
end end
-- Player settings.
local Settings = SETTINGS:Set( PlayerName ) local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit ) Settings:SetPlayerMenu(Event.IniUnit)
--MENU_INDEX:Refresh( Event.IniGroup )
-- Create an event.
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
end end
end end
end end
end end
@@ -967,22 +1139,69 @@ end
-- @param #DATABASE self -- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event -- @param Core.Event#EVENTDATA Event
function DATABASE:_EventOnDeadOrCrash( Event ) function DATABASE:_EventOnDeadOrCrash( Event )
self:F2( { Event } )
if Event.IniDCSUnit then if Event.IniDCSUnit then
local name=Event.IniDCSUnitName
if Event.IniObjectCategory == 3 then if Event.IniObjectCategory == 3 then
---
-- STATICS
---
if self.STATICS[Event.IniDCSUnitName] then if self.STATICS[Event.IniDCSUnitName] then
self:DeleteStatic( Event.IniDCSUnitName ) self:DeleteStatic( Event.IniDCSUnitName )
end end
else
if Event.IniObjectCategory == 1 then ---
if self.UNITS[Event.IniDCSUnitName] then -- Maybe a UNIT?
self:DeleteUnit( Event.IniDCSUnitName ) ---
-- Delete unit.
if self.UNITS[Event.IniDCSUnitName] then
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
local DCSUnit = _DATABASE:FindUnit( Event.IniDCSUnitName )
self:T({DCSUnit})
if DCSUnit then
--self:I("Creating DEAD Event for UNIT "..tostring(Event.IniDCSUnitName))
--DCSUnit:Destroy(true)
return
end end
end end
else
if Event.IniObjectCategory == 1 then
---
-- UNITS
---
-- Delete unit.
if self.UNITS[Event.IniDCSUnitName] then
self:DeleteUnit(Event.IniDCSUnitName)
end
-- Remove client players.
local client=self.CLIENTS[name] --Wrapper.Client#CLIENT
if client then
client:RemovePlayers()
end
end
end end
-- Add airbase if it was spawned later in the mission.
local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE
if airbase and (airbase:IsHelipad() or airbase:IsShip()) then
self:DeleteAirbase(Event.IniDCSUnitName)
end
end end
-- Account destroys.
self:AccountDestroys( Event ) self:AccountDestroys( Event )
end end
@@ -995,15 +1214,31 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
if Event.IniDCSUnit then if Event.IniDCSUnit then
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
-- Add unit.
self:AddUnit( Event.IniDCSUnitName ) self:AddUnit( Event.IniDCSUnitName )
-- Ini unit.
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
-- Add group.
self:AddGroup( Event.IniDCSGroupName ) self:AddGroup( Event.IniDCSGroupName )
-- Get player unit.
local PlayerName = Event.IniDCSUnit:getPlayerName() local PlayerName = Event.IniDCSUnit:getPlayerName()
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniDCSUnitName, PlayerName ) if PlayerName then
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
end
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit )
else
self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit")
end end
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit )
end end
end end
end end
@@ -1016,13 +1251,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
self:F2( { Event } ) self:F2( { Event } )
if Event.IniUnit then if Event.IniUnit then
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
-- Try to get the player name. This can be buggy for multicrew aircraft!
local PlayerName = Event.IniUnit:GetPlayerName() local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName and self.PLAYERS[PlayerName] then
self:I( { "Player Left:", PlayerName } ) if PlayerName then --and self.PLAYERS[PlayerName] then
-- Debug info.
self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName)))
-- Remove player menu.
local Settings = SETTINGS:Set( PlayerName ) local Settings = SETTINGS:Set( PlayerName )
Settings:RemovePlayerMenu( Event.IniUnit ) Settings:RemovePlayerMenu(Event.IniUnit)
self:DeletePlayer( Event.IniUnit, PlayerName )
-- Delete player.
self:DeletePlayer(Event.IniUnit, PlayerName)
-- Client stuff.
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
if client then
client:RemovePlayer(PlayerName)
end
end end
end end
end end
@@ -1249,19 +1501,19 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings )
self.PLAYERSETTINGS[PlayerName] = Settings self.PLAYERSETTINGS[PlayerName] = Settings
end end
--- Add a flight group to the data base. --- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base.
-- @param #DATABASE self -- @param #DATABASE self
-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB.
function DATABASE:AddFlightGroup(flightgroup) function DATABASE:AddOpsGroup(opsgroup)
self:I({NewFlightGroup=flightgroup.groupname}) --env.info("Adding OPSGROUP "..tostring(opsgroup.groupname))
self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
end end
--- Get a flight group from the data base. --- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object. -- @param #string groupname Group name of the group. Can also be passed as GROUP object.
-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object. -- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:GetFlightGroup(groupname) function DATABASE:GetOpsGroup(groupname)
-- Get group and group name. -- Get group and group name.
if type(groupname)=="string" then if type(groupname)=="string" then
@@ -1269,9 +1521,53 @@ function DATABASE:GetFlightGroup(groupname)
groupname=groupname:GetName() groupname=groupname:GetName()
end end
--env.info("Getting OPSGROUP "..tostring(groupname))
return self.FLIGHTGROUPS[groupname] return self.FLIGHTGROUPS[groupname]
end end
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base.
-- @param #DATABASE self
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:FindOpsGroup(groupname)
-- Get group and group name.
if type(groupname)=="string" then
else
groupname=groupname:GetName()
end
--env.info("Getting OPSGROUP "..tostring(groupname))
return self.FLIGHTGROUPS[groupname]
end
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base for a given unit.
-- @param #DATABASE self
-- @param #string unitname Unit name. Can also be passed as UNIT object.
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:FindOpsGroupFromUnit(unitname)
local unit=nil --Wrapper.Unit#UNIT
local groupname
-- Get group and group name.
if type(unitname)=="string" then
unit=UNIT:FindByName(unitname)
else
unit=unitname
end
if unit then
groupname=unit:GetGroup():GetName()
end
if groupname then
return self.FLIGHTGROUPS[groupname]
else
return nil
end
end
--- Add a flight control to the data base. --- Add a flight control to the data base.
-- @param #DATABASE self -- @param #DATABASE self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol
@@ -1355,19 +1651,13 @@ function DATABASE:_RegisterTemplates()
for group_num, Template in pairs(obj_type_data.group) do 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 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, self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
)
else else
self:_RegisterStaticTemplate(
Template, self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
)
end --if GroupTemplate and GroupTemplate.units then end --if GroupTemplate and GroupTemplate.units then
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +1,87 @@
--- **Core** - Models the process to achieve goal(s). --- **Core** - Models the process to achieve goal(s).
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Define the goal. -- * Define the goal.
-- * Monitor the goal achievement. -- * Monitor the goal achievement.
-- * Manage goal contribution by players. -- * Manage goal contribution by players.
-- --
-- === -- ===
-- --
-- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized. -- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized.
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky** -- ### Contributions: **funkyfranky**
-- --
-- === -- ===
-- --
-- @module Core.Goal -- @module Core.Goal
-- @image Core_Goal.JPG -- @image Core_Goal.JPG
do -- Goal do -- Goal
--- @type GOAL --- @type GOAL
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. --- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
-- --
-- # 1. GOAL constructor -- # 1. GOAL constructor
-- --
-- * @{#GOAL.New}(): Creates a new GOAL object. -- * @{#GOAL.New}(): Creates a new GOAL object.
-- --
-- # 2. GOAL is a finite state machine (FSM). -- # 2. GOAL is a finite state machine (FSM).
-- --
-- ## 2.1. GOAL States -- ## 2.1. GOAL States
-- --
-- * **Pending**: The goal object is in progress. -- * **Pending**: The goal object is in progress.
-- * **Achieved**: The goal objective is Achieved. -- * **Achieved**: The goal objective is Achieved.
-- --
-- ## 2.2. GOAL Events -- ## 2.2. GOAL Events
-- --
-- * **Achieved**: Set the goal objective to Achieved. -- * **Achieved**: Set the goal objective to Achieved.
-- --
-- # 3. Player contributions. -- # 3. Player contributions.
-- --
-- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement. -- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement.
-- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal. -- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal.
-- The player contributions are based on a points system, an internal counter per player. -- The player contributions are based on a points system, an internal counter per player.
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
-- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}(). -- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}().
-- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}(). -- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}().
-- --
-- # 4. Goal achievement. -- # 4. Goal achievement.
-- --
-- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event. -- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event.
-- The underlying 2 examples will achieve the goals for the `Goal` object: -- The underlying 2 examples will achieve the goals for the `Goal` object:
-- --
-- Goal:Achieved() -- Achieve the goal immediately. -- Goal:Achieved() -- Achieve the goal immediately.
-- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds. -- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds.
-- --
-- # 5. Check goal achievement. -- # 5. Check goal achievement.
-- --
-- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed). -- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed).
-- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler. -- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler.
-- --
-- @field #GOAL -- @field #GOAL
GOAL = { GOAL = {
ClassName = "GOAL", ClassName = "GOAL",
} }
--- @field #table GOAL.Players --- @field #table GOAL.Players
GOAL.Players = {} GOAL.Players = {}
--- @field #number GOAL.TotalContributions --- @field #number GOAL.TotalContributions
GOAL.TotalContributions = 0 GOAL.TotalContributions = 0
--- GOAL Constructor. --- GOAL Constructor.
-- @param #GOAL self -- @param #GOAL self
-- @return #GOAL -- @return #GOAL
function GOAL:New() function GOAL:New()
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
self:F( {} ) self:F( {} )
@@ -104,11 +102,10 @@ do -- Goal
-- @param #string From -- @param #string From
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
self:SetStartState( "Pending" ) self:SetStartState( "Pending" )
self:AddTransition( "*", "Achieved", "Achieved" ) self:AddTransition( "*", "Achieved", "Achieved" )
--- Achieved Handler OnBefore for GOAL --- Achieved Handler OnBefore for GOAL
-- @function [parent=#GOAL] OnBeforeAchieved -- @function [parent=#GOAL] OnBeforeAchieved
-- @param #GOAL self -- @param #GOAL self
@@ -116,47 +113,44 @@ do -- Goal
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @return #boolean -- @return #boolean
--- Achieved Handler OnAfter for GOAL --- Achieved Handler OnAfter for GOAL
-- @function [parent=#GOAL] OnAfterAchieved -- @function [parent=#GOAL] OnAfterAchieved
-- @param #GOAL self -- @param #GOAL self
-- @param #string From -- @param #string From
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
--- Achieved Trigger for GOAL --- Achieved Trigger for GOAL
-- @function [parent=#GOAL] Achieved -- @function [parent=#GOAL] Achieved
-- @param #GOAL self -- @param #GOAL self
--- Achieved Asynchronous Trigger for GOAL --- Achieved Asynchronous Trigger for GOAL
-- @function [parent=#GOAL] __Achieved -- @function [parent=#GOAL] __Achieved
-- @param #GOAL self -- @param #GOAL self
-- @param #number Delay -- @param #number Delay
self:SetEventPriority( 5 ) self:SetEventPriority( 5 )
return self return self
end end
--- Add a new contribution by a player. --- Add a new contribution by a player.
-- @param #GOAL self -- @param #GOAL self
-- @param #string PlayerName The name of the player. -- @param #string PlayerName The name of the player.
function GOAL:AddPlayerContribution( PlayerName ) function GOAL:AddPlayerContribution( PlayerName )
self:F({PlayerName}) self:F( { PlayerName } )
self.Players[PlayerName] = self.Players[PlayerName] or 0 self.Players[PlayerName] = self.Players[PlayerName] or 0
self.Players[PlayerName] = self.Players[PlayerName] + 1 self.Players[PlayerName] = self.Players[PlayerName] + 1
self.TotalContributions = self.TotalContributions + 1 self.TotalContributions = self.TotalContributions + 1
end end
--- @param #GOAL self --- @param #GOAL self
-- @param #number Player contribution. -- @param #number Player contribution.
function GOAL:GetPlayerContribution( PlayerName ) function GOAL:GetPlayerContribution( PlayerName )
return self.Players[PlayerName] or 0 return self.Players[PlayerName] or 0
end end
--- Get the players who contributed to achieve the goal. --- Get the players who contributed to achieve the goal.
-- The result is a list of players, sorted by the name of the players. -- The result is a list of players, sorted by the name of the players.
-- @param #GOAL self -- @param #GOAL self
@@ -165,7 +159,6 @@ do -- Goal
return self.Players or {} return self.Players or {}
end end
--- Gets the total contributions that happened to achieve the goal. --- Gets the total contributions that happened to achieve the goal.
-- The result is a number. -- The result is a number.
-- @param #GOAL self -- @param #GOAL self
@@ -173,9 +166,7 @@ do -- Goal
function GOAL:GetTotalContributions() function GOAL:GetTotalContributions()
return self.TotalContributions or 0 return self.TotalContributions or 0
end end
--- Validates if the goal is achieved. --- Validates if the goal is achieved.
-- @param #GOAL self -- @param #GOAL self
-- @return #boolean true if the goal is achieved. -- @return #boolean true if the goal is achieved.
@@ -183,4 +174,4 @@ do -- Goal
return self:Is( "Achieved" ) return self:Is( "Achieved" )
end end
end end

View File

@@ -0,0 +1,267 @@
--- **Core** - MarkerOps_Base.
--
-- **Main Features:**
--
-- * Create an easy way to tap into markers added to the F10 map by users.
-- * Recognize own tag and list of keywords.
-- * Matched keywords are handed down to functions.
--
-- ===
--
-- ### Author: **Applevangelist**
--
-- Date: 5 May 2021
-- Last Update: Sep 2022
--
-- ===
---
-- @module Core.MarkerOps_Base
-- @image MOOSE_Core.JPG
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition.
--------------------------------------------------------------------------
--- MARKEROPS_BASE class.
-- @type MARKEROPS_BASE
-- @field #string ClassName Name of the class.
-- @field #string Tag Tag to identify commands.
-- @field #table Keywords Table of keywords to recognize.
-- @field #string version Version of #MARKEROPS_BASE.
-- @field #boolean Casesensitive Enforce case when identifying the Tag, i.e. tag ~= Tag
-- @extends Core.Fsm#FSM
--- *Fiat lux.* -- Latin proverb.
--
-- ===
--
-- # The MARKEROPS_BASE Concept
--
-- This class enable scripting text-based actions from markers.
--
-- @field #MARKEROPS_BASE
MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.1.0",
debug = false,
Casesensitive = true,
}
--- Function to instantiate a new #MARKEROPS_BASE object.
-- @param #MARKEROPS_BASE self
-- @param #string Tagname Name to identify us from the event text.
-- @param #table Keywords Table of keywords recognized from the event text.
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
-- @return #MARKEROPS_BASE self
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- Inherit FSM
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
-- Set some string id for output to DCS.log file.
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
self.Tag = Tagname or "mytag"-- #string
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
self.debug = false
self.Casesensitive = true
if Casesensitive and Casesensitive == false then
self.Casesensitive = false
end
-----------------------
--- FSM Transitions ---
-----------------------
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
-- start
self:I(self.lid..string.format("started for %s",self.Tag))
self:__Start(1)
return self
-------------------
-- PSEUDO Functions
-------------------
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
--- "Stop" trigger. Used to stop the function an unhandle events
-- @function [parent=#MARKEROPS_BASE] Stop
end
--- (internal) Handle events.
-- @param #MARKEROPS_BASE self
-- @param Core.Event#EVENTDATA Event
function MARKEROPS_BASE:OnEventMark(Event)
self:T({Event})
if Event == nil or Event.idx == nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
--position
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
-- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
-- Handle event
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
-- Handle event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
-- Hande event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
self:MarkDeleted()
end
end
end
end
--- (internal) Match tag.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #boolean
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches = false
if not self.Casesensitive then
local type = string.lower(self.Tag) -- #string
if string.find(string.lower(Eventtext),type) then
matches = true --event text contains tag
end
else
local type = self.Tag -- #string
if string.find(Eventtext,type) then
matches = true --event text contains tag
end
end
return matches
end
--- (internal) Match keywords table.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #table
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
local matchtable = {}
local keytable = self.Keywords
for _index,_word in pairs (keytable) do
if string.find(string.lower(Eventtext),string.lower(_word))then
table.insert(matchtable,_word)
end
end
return matchtable
end
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
self:T({self.lid,From,Event,To})
end
--- On enter "Stopped" event. Unsubscribe events.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:T({self.lid,From,Event,To})
-- unsubscribe from events
self:UnHandleEvent(EVENTS.MarkAdded)
self:UnHandleEvent(EVENTS.MarkChange)
self:UnHandleEvent(EVENTS.MarkRemoved)
end
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition End.
--------------------------------------------------------------------------

View File

@@ -14,7 +14,7 @@
-- * Only create or delete menus when required, and keep existing menus persistent. -- * Only create or delete menus when required, and keep existing menus persistent.
-- * Update menu structures. -- * Update menu structures.
-- * Refresh menu structures intelligently, based on a time stamp of updates. -- * Refresh menu structures intelligently, based on a time stamp of updates.
-- - Delete obscolete menus. -- - Delete obsolete menus.
-- - Create new one where required. -- - Create new one where required.
-- - Don't touch the existing ones. -- - Don't touch the existing ones.
-- * Provide a variable amount of parameters to menus. -- * Provide a variable amount of parameters to menus.
@@ -23,7 +23,7 @@
-- * Provide a great tool to manage menus in your code. -- * Provide a great tool to manage menus in your code.
-- --
-- DCS Menus can be managed using the MENU classes. -- DCS Menus can be managed using the MENU classes.
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to -- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing -- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
-- menus is not a easy feat if you have complex menu hierarchies defined. -- menus is not a easy feat if you have complex menu hierarchies defined.
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. -- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
@@ -53,7 +53,6 @@
-- @module Core.Menu -- @module Core.Menu
-- @image Core_Menu.JPG -- @image Core_Menu.JPG
MENU_INDEX = {} MENU_INDEX = {}
MENU_INDEX.MenuMission = {} MENU_INDEX.MenuMission = {}
MENU_INDEX.MenuMission.Menus = {} MENU_INDEX.MenuMission.Menus = {}
@@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
MENU_INDEX.Coalition[coalition.side.RED].Menus = {} MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
MENU_INDEX.Group = {} MENU_INDEX.Group = {}
function MENU_INDEX:ParentPath( ParentMenu, MenuText ) function MENU_INDEX:ParentPath( ParentMenu, MenuText )
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or "" local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
if ParentMenu then if ParentMenu then
if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then
@@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
Path = Path .. "@" .. MenuText Path = Path .. "@" .. MenuText
return Path return Path
end end
function MENU_INDEX:PrepareMission() function MENU_INDEX:PrepareMission()
self.MenuMission.Menus = self.MenuMission.Menus or {} self.MenuMission.Menus = self.MenuMission.Menus or {}
end end
function MENU_INDEX:PrepareCoalition( CoalitionSide ) function MENU_INDEX:PrepareCoalition( CoalitionSide )
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
end end
--- ---
-- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#GROUP Group
function MENU_INDEX:PrepareGroup( Group ) function MENU_INDEX:PrepareGroup( Group )
@@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group )
end end
end end
function MENU_INDEX:HasMissionMenu( Path ) function MENU_INDEX:HasMissionMenu( Path )
return self.MenuMission.Menus[Path] return self.MenuMission.Menus[Path]
end end
function MENU_INDEX:SetMissionMenu( Path, Menu ) function MENU_INDEX:SetMissionMenu( Path, Menu )
self.MenuMission.Menus[Path] = Menu self.MenuMission.Menus[Path] = Menu
end end
function MENU_INDEX:ClearMissionMenu( Path ) function MENU_INDEX:ClearMissionMenu( Path )
self.MenuMission.Menus[Path] = nil self.MenuMission.Menus[Path] = nil
end end
function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
return self.Coalition[Coalition].Menus[Path] return self.Coalition[Coalition].Menus[Path]
end end
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
self.Coalition[Coalition].Menus[Path] = Menu self.Coalition[Coalition].Menus[Path] = Menu
end end
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
self.Coalition[Coalition].Menus[Path] = nil self.Coalition[Coalition].Menus[Path] = nil
end end
function MENU_INDEX:HasGroupMenu( Group, Path ) function MENU_INDEX:HasGroupMenu( Group, Path )
if Group and Group:IsAlive() then if Group and Group:IsAlive() then
local MenuGroupName = Group:GetName() local MenuGroupName = Group:GetName()
@@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path )
end end
return nil return nil
end end
function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
local MenuGroupName = Group:GetName() local MenuGroupName = Group:GetName()
Group:F({MenuGroupName=MenuGroupName,Path=Path}) Group:F({MenuGroupName=MenuGroupName,Path=Path})
self.Group[MenuGroupName].Menus[Path] = Menu self.Group[MenuGroupName].Menus[Path] = Menu
end end
function MENU_INDEX:ClearGroupMenu( Group, Path ) function MENU_INDEX:ClearGroupMenu( Group, Path )
local MenuGroupName = Group:GetName() local MenuGroupName = Group:GetName()
self.Group[MenuGroupName].Menus[Path] = nil self.Group[MenuGroupName].Menus[Path] = nil
end end
function MENU_INDEX:Refresh( Group ) function MENU_INDEX:Refresh( Group )
for MenuID, Menu in pairs( self.MenuMission.Menus ) do for MenuID, Menu in pairs( self.MenuMission.Menus ) do
Menu:Refresh() Menu:Refresh()
end end
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
Menu:Refresh() Menu:Refresh()
end end
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
Menu:Refresh() Menu:Refresh()
end end
local GroupName = Group:GetName() local GroupName = Group:GetName()
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
Menu:Refresh() Menu:Refresh()
end end
return self
end end
do -- MENU_BASE do -- MENU_BASE
--- @type MENU_BASE --- @type MENU_BASE
-- @extends Base#BASE -- @extends Core.Base#BASE
--- Defines the main MENU class where other MENU classes are derived from. --- Defines the main MENU class where other MENU classes are derived from.
-- This is an abstract class, so don't use it. -- This is an abstract class, so don't use it.
-- @field #MENU_BASE -- @field #MENU_BASE
@@ -216,10 +175,10 @@ do -- MENU_BASE
ClassName = "MENU_BASE", ClassName = "MENU_BASE",
MenuPath = nil, MenuPath = nil,
MenuText = "", MenuText = "",
MenuParentPath = nil MenuParentPath = nil,
} }
--- Consructor --- Constructor
-- @param #MENU_BASE -- @param #MENU_BASE
-- @return #MENU_BASE -- @return #MENU_BASE
function MENU_BASE:New( MenuText, ParentMenu ) function MENU_BASE:New( MenuText, ParentMenu )
@@ -228,27 +187,25 @@ do -- MENU_BASE
if ParentMenu ~= nil then if ParentMenu ~= nil then
MenuParentPath = ParentMenu.MenuPath MenuParentPath = ParentMenu.MenuPath
end end
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, BASE:New() )
self.MenuPath = nil self.MenuPath = nil
self.MenuText = MenuText self.MenuText = MenuText
self.ParentMenu = ParentMenu self.ParentMenu = ParentMenu
self.MenuParentPath = MenuParentPath self.MenuParentPath = MenuParentPath
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
self.Menus = {} self.Menus = {}
self.MenuCount = 0 self.MenuCount = 0
self.MenuStamp = timer.getTime() self.MenuStamp = timer.getTime()
self.MenuRemoveParent = false self.MenuRemoveParent = false
if self.ParentMenu then if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus = self.ParentMenu.Menus or {}
self.ParentMenu.Menus[MenuText] = self self.ParentMenu.Menus[MenuText] = self
end end
return self return self
end end
function MENU_BASE:SetParentMenu( MenuText, Menu ) function MENU_BASE:SetParentMenu( MenuText, Menu )
if self.ParentMenu then if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus = self.ParentMenu.Menus or {}
@@ -256,7 +213,6 @@ do -- MENU_BASE
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
end end
end end
function MENU_BASE:ClearParentMenu( MenuText ) function MENU_BASE:ClearParentMenu( MenuText )
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
self.ParentMenu.Menus[MenuText] = nil self.ParentMenu.Menus[MenuText] = nil
@@ -266,7 +222,6 @@ do -- MENU_BASE
end end
end end
end end
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
-- @param #MENU_BASE self -- @param #MENU_BASE self
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
@@ -276,7 +231,6 @@ do -- MENU_BASE
self.MenuRemoveParent = RemoveParent self.MenuRemoveParent = RemoveParent
return self return self
end end
--- Gets a @{Menu} from a parent @{Menu} --- Gets a @{Menu} from a parent @{Menu}
-- @param #MENU_BASE self -- @param #MENU_BASE self
@@ -285,7 +239,7 @@ do -- MENU_BASE
function MENU_BASE:GetMenu( MenuText ) function MENU_BASE:GetMenu( MenuText )
return self.Menus[MenuText] return self.Menus[MenuText]
end end
--- Sets a menu stamp for later prevention of menu removal. --- Sets a menu stamp for later prevention of menu removal.
-- @param #MENU_BASE self -- @param #MENU_BASE self
-- @param MenuStamp -- @param MenuStamp
@@ -323,9 +277,7 @@ do -- MENU_BASE
end end
end end
do -- MENU_COMMAND_BASE do -- MENU_COMMAND_BASE
--- @type MENU_COMMAND_BASE --- @type MENU_COMMAND_BASE
-- @field #function MenuCallHandler -- @field #function MenuCallHandler
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -346,8 +298,7 @@ do -- MENU_COMMAND_BASE
-- @return #MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
-- When a menu function goes into error, DCS displays an obscure menu message. -- When a menu function goes into error, DCS displays an obscure menu message.
-- This error handler catches the menu error and displays the full call stack. -- This error handler catches the menu error and displays the full call stack.
local ErrorHandler = function( errmsg ) local ErrorHandler = function( errmsg )
@@ -367,7 +318,7 @@ do -- MENU_COMMAND_BASE
local Status, Result = xpcall( MenuFunction, ErrorHandler ) local Status, Result = xpcall( MenuFunction, ErrorHandler )
end end
return self return self
end end
--- This sets the new command function of a menu, --- This sets the new command function of a menu,
@@ -380,7 +331,6 @@ do -- MENU_COMMAND_BASE
self.CommandMenuFunction = CommandMenuFunction self.CommandMenuFunction = CommandMenuFunction
return self return self
end end
--- This sets the new command arguments of a menu, --- This sets the new command arguments of a menu,
-- so that if a menu is regenerated, or if command arguments change, -- so that if a menu is regenerated, or if command arguments change,
-- that the arguments set for the menu are loosely coupled with the menu itself!!! -- that the arguments set for the menu are loosely coupled with the menu itself!!!
@@ -391,35 +341,30 @@ do -- MENU_COMMAND_BASE
self.CommandMenuArguments = CommandMenuArguments self.CommandMenuArguments = CommandMenuArguments
return self return self
end end
end end
do -- MENU_MISSION do -- MENU_MISSION
--- @type MENU_MISSION --- @type MENU_MISSION
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
--- Manages the main menus for a complete mission. --- Manages the main menus for a complete mission.
-- --
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
-- @field #MENU_MISSION -- @field #MENU_MISSION
MENU_MISSION = { MENU_MISSION = {
ClassName = "MENU_MISSION" ClassName = "MENU_MISSION",
} }
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
-- @param #MENU_MISSION self -- @param #MENU_MISSION self
-- @param #string MenuText The text for the menu. -- @param #string MenuText The text for the menu.
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
-- @return #MENU_MISSION -- @return #MENU_MISSION
function MENU_MISSION:New( MenuText, ParentMenu ) function MENU_MISSION:New( MenuText, ParentMenu )
MENU_INDEX:PrepareMission() MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu then if MissionMenu then
return MissionMenu return MissionMenu
else else
@@ -432,17 +377,16 @@ do -- MENU_MISSION
end end
end end
--- Refreshes a radio item for a mission --- Refreshes a radio item for a mission
-- @param #MENU_MISSION self -- @param #MENU_MISSION self
-- @return #MENU_MISSION -- @return #MENU_MISSION
function MENU_MISSION:Refresh() function MENU_MISSION:Refresh()
do do
missionCommands.removeItem( self.MenuPath ) missionCommands.removeItem( self.MenuPath )
self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath ) self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath )
end end
return self
end end
--- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept!
@@ -466,7 +410,6 @@ do -- MENU_MISSION
MENU_INDEX:PrepareMission() MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu == self then if MissionMenu == self then
self:RemoveSubMenus() self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -487,10 +430,7 @@ do -- MENU_MISSION
return self return self
end end
end end
do -- MENU_MISSION_COMMAND do -- MENU_MISSION_COMMAND
--- @type MENU_MISSION_COMMAND --- @type MENU_MISSION_COMMAND
@@ -503,7 +443,7 @@ do -- MENU_MISSION_COMMAND
-- --
-- @field #MENU_MISSION_COMMAND -- @field #MENU_MISSION_COMMAND
MENU_MISSION_COMMAND = { MENU_MISSION_COMMAND = {
ClassName = "MENU_MISSION_COMMAND" ClassName = "MENU_MISSION_COMMAND",
} }
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
@@ -518,7 +458,6 @@ do -- MENU_MISSION_COMMAND
MENU_INDEX:PrepareMission() MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu then if MissionMenu then
MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) MissionMenu:SetCommandMenuFunction( CommandMenuFunction )
MissionMenu:SetCommandMenuArguments( arg ) MissionMenu:SetCommandMenuArguments( arg )
@@ -532,17 +471,15 @@ do -- MENU_MISSION_COMMAND
return self return self
end end
end end
--- Refreshes a radio item for a mission --- Refreshes a radio item for a mission
-- @param #MENU_MISSION_COMMAND self -- @param #MENU_MISSION_COMMAND self
-- @return #MENU_MISSION_COMMAND -- @return #MENU_MISSION_COMMAND
function MENU_MISSION_COMMAND:Refresh() function MENU_MISSION_COMMAND:Refresh()
do do
missionCommands.removeItem( self.MenuPath ) missionCommands.removeItem( self.MenuPath )
missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler ) missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end end
return self
end end
--- Removes a radio command item for a coalition --- Removes a radio command item for a coalition
@@ -553,7 +490,6 @@ do -- MENU_MISSION_COMMAND
MENU_INDEX:PrepareMission() MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu == self then if MissionMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -572,13 +508,8 @@ do -- MENU_MISSION_COMMAND
return self return self
end end
end end
do -- MENU_COALITION do -- MENU_COALITION
--- @type MENU_COALITION --- @type MENU_COALITION
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -634,18 +565,15 @@ do -- MENU_COALITION
-- @param #MENU_COALITION self -- @param #MENU_COALITION self
-- @param DCS#coalition.side Coalition The coalition owning the menu. -- @param DCS#coalition.side Coalition The coalition owning the menu.
-- @param #string MenuText The text for the menu. -- @param #string MenuText The text for the menu.
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
-- @return #MENU_COALITION self -- @return #MENU_COALITION self
function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
MENU_INDEX:PrepareCoalition( Coalition ) MENU_INDEX:PrepareCoalition( Coalition )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
if CoalitionMenu then if CoalitionMenu then
return CoalitionMenu return CoalitionMenu
else else
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self )
@@ -656,17 +584,15 @@ do -- MENU_COALITION
return self return self
end end
end end
--- Refreshes a radio item for a coalition --- Refreshes a radio item for a coalition
-- @param #MENU_COALITION self -- @param #MENU_COALITION self
-- @return #MENU_COALITION -- @return #MENU_COALITION
function MENU_COALITION:Refresh() function MENU_COALITION:Refresh()
do do
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath ) missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath )
end end
return self
end end
--- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept!
@@ -689,7 +615,6 @@ do -- MENU_COALITION
MENU_INDEX:PrepareCoalition( self.Coalition ) MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
if CoalitionMenu == self then if CoalitionMenu == self then
self:RemoveSubMenus() self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -709,11 +634,7 @@ do -- MENU_COALITION
return self return self
end end
end end
do -- MENU_COALITION_COMMAND do -- MENU_COALITION_COMMAND
--- @type MENU_COALITION_COMMAND --- @type MENU_COALITION_COMMAND
@@ -742,7 +663,6 @@ do -- MENU_COALITION_COMMAND
MENU_INDEX:PrepareCoalition( Coalition ) MENU_INDEX:PrepareCoalition( Coalition )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
if CoalitionMenu then if CoalitionMenu then
CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction ) CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction )
CoalitionMenu:SetCommandMenuArguments( arg ) CoalitionMenu:SetCommandMenuArguments( arg )
@@ -757,20 +677,17 @@ do -- MENU_COALITION_COMMAND
self:SetParentMenu( self.MenuText, self ) self:SetParentMenu( self.MenuText, self )
return self return self
end end
end end
--- Refreshes a radio item for a coalition --- Refreshes a radio item for a coalition
-- @param #MENU_COALITION_COMMAND self -- @param #MENU_COALITION_COMMAND self
-- @return #MENU_COALITION_COMMAND -- @return #MENU_COALITION_COMMAND
function MENU_COALITION_COMMAND:Refresh() function MENU_COALITION_COMMAND:Refresh()
do do
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end end
return self
end end
--- Removes a radio command item for a coalition --- Removes a radio command item for a coalition
@@ -781,7 +698,6 @@ do -- MENU_COALITION_COMMAND
MENU_INDEX:PrepareCoalition( self.Coalition ) MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
if CoalitionMenu == self then if CoalitionMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -800,20 +716,16 @@ do -- MENU_COALITION_COMMAND
return self return self
end end
end end
--- MENU_GROUP --- MENU_GROUP
do do
-- This local variable is used to cache the menus registered under groups. -- This local variable is used to cache the menus registered under groups.
-- Menus don't dissapear when groups for players are destroyed and restarted. -- Menus don't disappear when groups for players are destroyed and restarted.
-- So every menu for a client created must be tracked so that program logic accidentally does not create. -- So every menu for a client created must be tracked so that program logic accidentally does not create.
-- the same menus twice during initialization logic. -- the same menus twice during initialization logic.
-- These menu classes are handling this logic with this variable. -- These menu classes are handling this logic with this variable.
local _MENUGROUPS = {} local _MENUGROUPS = {}
--- @type MENU_GROUP --- @type MENU_GROUP
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -889,16 +801,13 @@ do
MENU_INDEX:PrepareGroup( Group ) MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then if GroupMenu then
return GroupMenu return GroupMenu
else else
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetGroupMenu( Group, Path, self ) MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group self.Group = Group
self.GroupID = Group:GetID() self.GroupID = Group:GetID()
self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath ) self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath )
self:SetParentMenu( self.MenuText, self ) self:SetParentMenu( self.MenuText, self )
@@ -906,12 +815,11 @@ do
end end
end end
--- Refreshes a new radio item for a group and submenus --- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP self -- @param #MENU_GROUP self
-- @return #MENU_GROUP -- @return #MENU_GROUP
function MENU_GROUP:Refresh() function MENU_GROUP:Refresh()
do do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -920,7 +828,31 @@ do
Menu:Refresh() Menu:Refresh()
end end
end end
return self
end
--- Refreshes a new radio item for a group and submenus, ordering by (numerical) MenuTag
-- @param #MENU_GROUP self
-- @return #MENU_GROUP
function MENU_GROUP:RefreshAndOrderByTag()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
local MenuTable = {}
for MenuText, Menu in pairs( self.Menus or {} ) do
local tag = Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu}
end
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end )
for _, Menu in pairs( MenuTable ) do
Menu.Entry:Refresh()
end
end
return self
end end
--- Removes the sub menus recursively of this MENU_GROUP. --- Removes the sub menus recursively of this MENU_GROUP.
@@ -929,7 +861,6 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP self -- @return #MENU_GROUP self
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag ) function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuStamp, MenuTag ) Menu:Remove( MenuStamp, MenuTag )
end end
@@ -938,18 +869,15 @@ do
end end
--- Removes the main menu and sub menus recursively of this MENU_GROUP. --- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self -- @param #MENU_GROUP self
-- @param MenuStamp -- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil -- @return #nil
function MENU_GROUP:Remove( MenuStamp, MenuTag ) function MENU_GROUP:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group ) MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then if GroupMenu == self then
self:RemoveSubMenus( MenuStamp, MenuTag ) self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -993,18 +921,15 @@ do
-- @param CommandMenuArgument An argument for the function. -- @param CommandMenuArgument An argument for the function.
-- @return #MENU_GROUP_COMMAND -- @return #MENU_GROUP_COMMAND
function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
MENU_INDEX:PrepareGroup( Group ) MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then if GroupMenu then
GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
GroupMenu:SetCommandMenuArguments( arg ) GroupMenu:SetCommandMenuArguments( arg )
return GroupMenu return GroupMenu
else else
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
MENU_INDEX:SetGroupMenu( Group, Path, self ) MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group self.Group = Group
@@ -1015,19 +940,17 @@ do
self:SetParentMenu( self.MenuText, self ) self:SetParentMenu( self.MenuText, self )
return self return self
end end
end end
--- Refreshes a radio item for a group --- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND self -- @param #MENU_GROUP_COMMAND self
-- @return #MENU_GROUP_COMMAND -- @return #MENU_GROUP_COMMAND
function MENU_GROUP_COMMAND:Refresh() function MENU_GROUP_COMMAND:Refresh()
do do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end end
return self
end end
--- Removes a menu structure for a group. --- Removes a menu structure for a group.
@@ -1036,11 +959,9 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil -- @return #nil
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag ) function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group ) MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then if GroupMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -1059,13 +980,9 @@ do
return self return self
end end
end end
--- MENU_GROUP_DELAYED --- MENU_GROUP_DELAYED
do do
--- @type MENU_GROUP_DELAYED --- @type MENU_GROUP_DELAYED
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -1093,16 +1010,13 @@ do
MENU_INDEX:PrepareGroup( Group ) MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then if GroupMenu then
return GroupMenu return GroupMenu
else else
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetGroupMenu( Group, Path, self ) MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group self.Group = Group
self.GroupID = Group:GetID() self.GroupID = Group:GetID()
if self.MenuParentPath then if self.MenuParentPath then
self.MenuPath = UTILS.DeepCopy( self.MenuParentPath ) self.MenuPath = UTILS.DeepCopy( self.MenuParentPath )
else else
@@ -1116,12 +1030,10 @@ do
end end
--- Refreshes a new radio item for a group and submenus --- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP_DELAYED self -- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED -- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Set() function MENU_GROUP_DELAYED:Set()
do do
if not self.MenuSet then if not self.MenuSet then
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -1132,15 +1044,12 @@ do
Menu:Set() Menu:Set()
end end
end end
end end
--- Refreshes a new radio item for a group and submenus --- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP_DELAYED self -- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED -- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Refresh() function MENU_GROUP_DELAYED:Refresh()
do do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -1149,7 +1058,8 @@ do
Menu:Refresh() Menu:Refresh()
end end
end end
return self
end end
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED. --- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
@@ -1158,7 +1068,6 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED self
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag ) function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuStamp, MenuTag ) Menu:Remove( MenuStamp, MenuTag )
end end
@@ -1167,18 +1076,15 @@ do
end end
--- Removes the main menu and sub menus recursively of this MENU_GROUP. --- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP_DELAYED self -- @param #MENU_GROUP_DELAYED self
-- @param MenuStamp -- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil -- @return #nil
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag ) function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group ) MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then if GroupMenu == self then
self:RemoveSubMenus( MenuStamp, MenuTag ) self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -1223,18 +1129,15 @@ do
-- @param CommandMenuArgument An argument for the function. -- @param CommandMenuArgument An argument for the function.
-- @return #MENU_GROUP_COMMAND_DELAYED -- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
MENU_INDEX:PrepareGroup( Group ) MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then if GroupMenu then
GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
GroupMenu:SetCommandMenuArguments( arg ) GroupMenu:SetCommandMenuArguments( arg )
return GroupMenu return GroupMenu
else else
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
MENU_INDEX:SetGroupMenu( Group, Path, self ) MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group self.Group = Group
@@ -1250,33 +1153,29 @@ do
self:SetParentMenu( self.MenuText, self ) self:SetParentMenu( self.MenuText, self )
return self return self
end end
end end
--- Refreshes a radio item for a group --- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND_DELAYED self -- @param #MENU_GROUP_COMMAND_DELAYED self
-- @return #MENU_GROUP_COMMAND_DELAYED -- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:Set() function MENU_GROUP_COMMAND_DELAYED:Set()
do do
if not self.MenuSet then if not self.MenuSet then
self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
self.MenuSet = true self.MenuSet = true
end end
end end
end end
--- Refreshes a radio item for a group --- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND_DELAYED self -- @param #MENU_GROUP_COMMAND_DELAYED self
-- @return #MENU_GROUP_COMMAND_DELAYED -- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:Refresh() function MENU_GROUP_COMMAND_DELAYED:Refresh()
do do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end end
return self
end end
--- Removes a menu structure for a group. --- Removes a menu structure for a group.
@@ -1285,11 +1184,9 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil -- @return #nil
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag ) function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group ) MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then if GroupMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -1308,6 +1205,4 @@ do
return self return self
end end
end end

View File

@@ -1,18 +1,19 @@
--- **Core** - Informs the players using messages during a simulation. --- **Core** - Informs the players using messages during a simulation.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * A more advanced messaging system using the DCS message system. -- * A more advanced messaging system using the DCS message system.
-- * Time messages. -- * Time messages.
-- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS. -- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS.
-- * Send message to all players. -- * Send message to all players.
-- * Send messages to a coalition. -- * Send messages to a coalition.
-- * Send messages to a specific group. -- * Send messages to a specific group.
-- * Send messages to a specific unit or client.
-- --
-- === -- ===
-- --
-- @module Core.Message -- @module Core.Message
-- @image Core_Message.JPG -- @image Core_Message.JPG
@@ -23,42 +24,43 @@
--- Message System to display Messages to Clients, Coalitions or All. --- Message System to display Messages to Clients, Coalitions or All.
-- Messages are shown on the display panel for an amount of seconds, and will then disappear. -- Messages are shown on the display panel for an amount of seconds, and will then disappear.
-- Messages can contain a category which is indicating the category of the message. -- Messages can contain a category which is indicating the category of the message.
-- --
-- ## MESSAGE construction -- ## MESSAGE construction
-- --
-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. -- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
-- To send messages, you need to use the To functions. -- To send messages, you need to use the To functions.
-- --
-- ## Send messages to an audience -- ## Send messages to an audience
-- --
-- Messages are sent: -- Messages are sent:
-- --
-- * To a @{Client} using @{#MESSAGE.ToClient}(). -- * To a @{Client} using @{#MESSAGE.ToClient}().
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}() -- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}()
-- * To a coalition using @{#MESSAGE.ToCoalition}(). -- * To a coalition using @{#MESSAGE.ToCoalition}().
-- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the red coalition using @{#MESSAGE.ToRed}().
-- * To the blue coalition using @{#MESSAGE.ToBlue}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}().
-- * To all Players using @{#MESSAGE.ToAll}(). -- * To all Players using @{#MESSAGE.ToAll}().
-- --
-- ## Send conditionally to an audience -- ## Send conditionally to an audience
-- --
-- Messages can be sent conditionally to an audience (when a condition is true): -- Messages can be sent conditionally to an audience (when a condition is true):
-- --
-- * To all players using @{#MESSAGE.ToAllIf}(). -- * To all players using @{#MESSAGE.ToAllIf}().
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}(). -- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: -- ### Contributions:
-- --
-- === -- ===
-- --
-- @field #MESSAGE -- @field #MESSAGE
MESSAGE = { MESSAGE = {
ClassName = "MESSAGE", ClassName = "MESSAGE",
MessageCategory = 0, MessageCategory = 0,
MessageID = 0, MessageID = 0,
} }
--- Message Types --- Message Types
@@ -68,10 +70,9 @@ MESSAGE.Type = {
Information = "Information", Information = "Information",
Briefing = "Briefing Report", Briefing = "Briefing Report",
Overview = "Overview Report", Overview = "Overview Report",
Detailed = "Detailed Report" Detailed = "Detailed Report",
} }
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- @param self -- @param self
-- @param #string MessageText is the text of the Message. -- @param #string MessageText is the text of the Message.
@@ -80,52 +81,52 @@ MESSAGE.Type = {
-- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @param #boolean ClearScreen (optional) Clear all previous messages if true.
-- @return #MESSAGE -- @return #MESSAGE
-- @usage -- @usage
-- -- Create a series of new Messages. --
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". -- -- Create a series of new Messages.
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". -- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". -- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". -- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) -- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) -- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
--
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } ) self:F( { MessageText, MessageDuration, MessageCategory } )
self.MessageType = nil self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title... -- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub(-1) ~= "\n" then if MessageCategory:sub( -1 ) ~= "\n" then
self.MessageCategory = MessageCategory .. ": " self.MessageCategory = MessageCategory .. ": "
else else
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
end end
else else
self.MessageCategory = "" self.MessageCategory = ""
end end
self.ClearScreen=false self.ClearScreen = false
if ClearScreen~=nil then if ClearScreen ~= nil then
self.ClearScreen=ClearScreen self.ClearScreen = ClearScreen
end end
self.MessageDuration = MessageDuration or 5 self.MessageDuration = MessageDuration or 5
self.MessageTime = timer.getTime() self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
self.MessageSent = false
self.MessageGroup = false
self.MessageCoalition = false
return self self.MessageSent = false
self.MessageGroup = false
self.MessageCoalition = false
return self
end end
--- Creates a new MESSAGE object of a certain type.
--- Creates a new MESSAGE object of a certain type. -- Note that these MESSAGE objects are not yet displayed on the display panel.
-- Note that these MESSAGE objects are not yet displayed on the display panel.
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. -- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu. -- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
-- @param self -- @param self
@@ -134,83 +135,86 @@ end
-- @param #boolean ClearScreen (optional) Clear all previous messages. -- @param #boolean ClearScreen (optional) Clear all previous messages.
-- @return #MESSAGE -- @return #MESSAGE
-- @usage -- @usage
--
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
--
function MESSAGE:NewType( MessageText, MessageType, ClearScreen ) function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText } ) self:F( { MessageText } )
self.MessageType = MessageType self.MessageType = MessageType
self.ClearScreen=false self.ClearScreen = false
if ClearScreen~=nil then if ClearScreen ~= nil then
self.ClearScreen=ClearScreen self.ClearScreen = ClearScreen
end end
self.MessageTime = timer.getTime() self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
return self return self
end end
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @return #MESSAGE -- @return #MESSAGE
function MESSAGE:Clear() function MESSAGE:Clear()
self:F() self:F()
self.ClearScreen=true self.ClearScreen = true
return self return self
end end
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param Wrapper.Client#CLIENT Client is the Group of the Client. -- @param Wrapper.Client#CLIENT Client is the Group of the Client.
-- @param Core.Settings#SETTINGS Settings Settings used to display the message. -- @param Core.Settings#SETTINGS Settings Settings used to display the message.
-- @return #MESSAGE -- @return #MESSAGE
-- @usage -- @usage
-- -- Send the 2 messages created with the @{New} method to the Client Group.
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
-- ClientGroup = Group.getByName( "ClientGroup" )
-- --
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) -- -- Send the 2 messages created with the @{New} method to the Client Group.
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) -- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
-- or -- ClientGroup = Group.getByName( "ClientGroup" )
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- or -- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) -- or
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) -- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- MessageClient1:ToClient( ClientGroup ) -- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup ) -- or
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
-- MessageClient1:ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup )
--
function MESSAGE:ToClient( Client, Settings ) function MESSAGE:ToClient( Client, Settings )
self:F( Client ) self:F( Client )
if Client and Client:GetClientGroupID() then if Client and Client:GetClientGroupID() then
if self.MessageType then if self.MessageType then
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": " self.MessageCategory = "" -- self.MessageType .. ": "
end end
local Unit = Client:GetClient()
if self.MessageDuration ~= 0 then if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID() local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) --trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
end end
end end
return self return self
end end
--- Sends a MESSAGE to a Group. --- Sends a MESSAGE to a Group.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group to which the message is displayed. -- @param Wrapper.Group#GROUP Group to which the message is displayed.
-- @return #MESSAGE Message object. -- @return #MESSAGE Message object.
@@ -218,74 +222,129 @@ function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName ) self:F( Group.GroupName )
if Group then if Group then
if self.MessageType then
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
end
end
return self
end
--- Sends a MESSAGE to a Unit.
-- @param #MESSAGE self
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
-- @return #MESSAGE Message object.
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
if Unit then
if self.MessageType then if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": " self.MessageCategory = "" -- self.MessageType .. ": "
end end
if self.MessageDuration ~= 0 then if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end end
end end
return self return self
end end
--- Sends a MESSAGE to the Blue coalition.
-- @param #MESSAGE self
-- @return #MESSAGE
-- @usage
-- -- Send a message created with the @{New} method to the BLUE coalition.
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
-- or
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
-- or
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageBLUE:ToBlue()
function MESSAGE:ToBlue()
self:F()
self:ToCoalition( coalition.side.BLUE ) --- Sends a MESSAGE to a Unit.
-- @param #MESSAGE self
return self -- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
-- @return #MESSAGE Message object.
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
if Unit then
if self.MessageType then
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
end
return self
end end
--- Sends a MESSAGE to the Red Coalition. --- Sends a MESSAGE to the Blue coalition.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @return #MESSAGE -- @return #MESSAGE
-- @usage -- @usage
-- -- Send a message created with the @{New} method to the RED coalition. --
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() -- -- Send a message created with the @{New} method to the BLUE coalition.
-- or -- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() -- or
-- or -- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) -- or
-- MessageRED:ToRed() -- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
function MESSAGE:ToRed( ) -- MessageBLUE:ToBlue()
self:F() --
function MESSAGE:ToBlue()
self:F()
self:ToCoalition( coalition.side.RED ) self:ToCoalition( coalition.side.BLUE )
return self return self
end end
--- Sends a MESSAGE to a Coalition. --- Sends a MESSAGE to the Red Coalition.
-- @param #MESSAGE self
-- @return #MESSAGE
-- @usage
--
-- -- Send a message created with the @{New} method to the RED coalition.
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
-- or
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
-- or
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToRed()
--
function MESSAGE:ToRed()
self:F()
self:ToCoalition( coalition.side.RED )
return self
end
--- Sends a MESSAGE to a Coalition.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed. -- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display. -- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object. -- @return #MESSAGE Message object.
-- @usage -- @usage
-- -- Send a message created with the @{New} method to the RED coalition. --
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) -- -- Send a message created with the @{New} method to the RED coalition.
-- or -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) -- or
-- or -- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) -- or
-- MessageRED:ToCoalition( coalition.side.RED ) -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToCoalition( coalition.side.RED )
--
function MESSAGE:ToCoalition( CoalitionSide, Settings ) function MESSAGE:ToCoalition( CoalitionSide, Settings )
self:F( CoalitionSide ) self:F( CoalitionSide )
if self.MessageType then if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
@@ -293,20 +352,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
self.MessageCategory = "" -- self.MessageType .. ": " self.MessageCategory = "" -- self.MessageType .. ": "
end end
if CoalitionSide then if CoalitionSide then
if self.MessageDuration ~= 0 then if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
end end
end end
return self return self
end end
--- Sends a MESSAGE to a Coalition if the given Condition is true. --- Sends a MESSAGE to a Coalition if the given Condition is true.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. -- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}.
-- @param #boolean Condition Sends the message only if the condition is true. -- @param #boolean Condition Sends the message only if the condition is true.
-- @return #MESSAGE self -- @return #MESSAGE self
function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
self:F( CoalitionSide ) self:F( CoalitionSide )
@@ -314,7 +373,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
if Condition and Condition == true then if Condition and Condition == true then
self:ToCoalition( CoalitionSide ) self:ToCoalition( CoalitionSide )
end end
return self return self
end end
@@ -323,14 +382,16 @@ end
-- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE -- @return #MESSAGE
-- @usage -- @usage
-- -- Send a message created to all players. --
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() -- -- Send a message created to all players.
-- or -- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() -- or
-- or -- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) -- or
-- MessageAll:ToAll() -- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
function MESSAGE:ToAll(Settings) -- MessageAll:ToAll()
--
function MESSAGE:ToAll( Settings )
self:F() self:F()
if self.MessageType then if self.MessageType then
@@ -340,14 +401,13 @@ function MESSAGE:ToAll(Settings)
end end
if self.MessageDuration ~= 0 then if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
end end
return self return self
end end
--- Sends a MESSAGE to all players if the given Condition is true. --- Sends a MESSAGE to all players if the given Condition is true.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @return #MESSAGE -- @return #MESSAGE
@@ -357,5 +417,5 @@ function MESSAGE:ToAllIf( Condition )
self:ToAll() self:ToAll()
end end
return self return self
end end

File diff suppressed because it is too large Load Diff

View File

@@ -1,832 +0,0 @@
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
--
-- ===
--
-- ## Features:
--
-- * Provide radio functionality to broadcast radio transmissions.
-- * Provide beacon functionality to assist pilots.
--
-- The Radio contains 2 classes : RADIO and BEACON
--
-- 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?
--
-- * 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 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 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 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.
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Radio
-- @image Core_Radio.JPG
--- Models the radio capability.
--
-- ## RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
-- * @{#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 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 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?
--
-- * 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 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 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 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",
FileName = "",
Frequency = 0,
Modulation = radio.modulation.AM,
Subtitle = "",
SubtitleDuration = 0,
Power = 100,
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.
-- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @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:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil
end
--- 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
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?", FileName})
return self
end
--- 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 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 <= 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
local commandSetFrequency={
id = "SetFrequency",
params = {
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.", Frequency})
return self
end
--- Set AM or FM modulation of the radio transmitter.
-- @param #RADIO self
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
-- @return #RADIO self
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
if type(Modulation) == "number" then
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self.Modulation = Modulation
return self
end
end
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
return self
end
--- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self
-- @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
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
return self
end
--- Set message looping on or off.
-- @param #RADIO self
-- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop) == "boolean" then
self.Loop = Loop
return self
end
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
return self
end
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
-- @param #RADIO self
-- @param #string Subtitle
-- @param #number SubtitleDuration in s
-- @return #RADIO self
-- @usage
-- -- create the broadcaster and attaches it a RADIO
-- local MyUnit = UNIT:FindByName("MyUnit")
-- local MyUnitRadio = MyUnit:GetRadio()
--
-- -- add a subtitle for the next transmission, which will be up for 10s
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:F2({Subtitle, SubtitleDuration})
if type(Subtitle) == "string" then
self.Subtitle = Subtitle
else
self.Subtitle = ""
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end
if type(SubtitleDuration) == "number" then
self.SubtitleDuration = SubtitleDuration
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
-- 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 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})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Power then self:SetPower(Power) end
if Loop then self:SetLoop(Loop) end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @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)
-- 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
--- 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
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
-- * 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(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") 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: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
-- @return #RADIO self
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
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID))
end
return self
end
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## AA TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- Use @#BEACON:StopAATACAN}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- 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,
}
--- 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 object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
-- 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
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
return nil
end
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @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
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- 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
-- 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
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
-- @param #boolean Bearing Can the BEACON be homed on ?
-- @param #number BeaconDuration 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:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration})
local IsValid = true
if not self.Positionable:IsAir() then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
IsValid = false
end
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
-- or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = 5
else
System = 14
end
if IsValid then -- Starts the BEACON
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = 4,
system = System,
callsign = Message,
frequency = Frequency,
}
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id = 'DeactivateBeacon',
params = {
}
})
end
end
--- Activates a general pupose Radio Beacon
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
-- They can home in on these specific frequencies :
-- * **Mi8**
-- * R-828 -> 20-60MHz
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
-- * ARK9 -> 150-1300KHz
-- * **Huey**
-- * AN/ARC-131 -> 30-76 Mhz FM
-- @param #BEACON self
-- @param #string FileName The name of the audio file
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a beacon for a unit in distress.
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
-- -- The beacon they use is battery-powered, and only lasts for 5 min
-- local UnitInDistress = UNIT:FindByName("Unit1")
-- local UnitBeacon = UnitInDistress:GetBeacon()
--
-- -- Set the beacon and start it
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
-- Check the 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
IsValid = true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
end
-- Check the Frequency
if type(Frequency) ~= "number" and IsValid then
self:E({"Frequency invalid. ", Frequency})
IsValid = false
end
Frequency = Frequency * 1000000 -- Conversion to Hz
-- Check the modulation
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
IsValid = false
end
-- Check the Power
if type(Power) ~= "number" and IsValid then
self:E({"Power is invalid. ", Power})
IsValid = false
end
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
function()
self:StopRadioBeacon()
end, {}, BeaconDuration)
end
end
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
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

@@ -1,13 +1,13 @@
--- **Core** - Provides a handy means to create messages and reports. --- **Core** - Provides a handy means to create messages and reports.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Create text blocks that are formatted. -- * Create text blocks that are formatted.
-- * Create automatic indents. -- * Create automatic indents.
-- * Variate the delimiters between reporting lines. -- * Variate the delimiters between reporting lines.
-- --
-- === -- ===
-- --
-- ### Authors: FlightControl : Design & Programming -- ### Authors: FlightControl : Design & Programming
@@ -15,7 +15,6 @@
-- @module Core.Report -- @module Core.Report
-- @image Core_Report.JPG -- @image Core_Report.JPG
--- @type REPORT --- @type REPORT
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -36,7 +35,7 @@ function REPORT:New( Title )
self.Report = {} self.Report = {}
self:SetTitle( Title or "" ) self:SetTitle( Title or "" )
self:SetIndent( 3 ) self:SetIndent( 3 )
return self return self
@@ -45,28 +44,26 @@ end
--- Has the REPORT Text? --- Has the REPORT Text?
-- @param #REPORT self -- @param #REPORT self
-- @return #boolean -- @return #boolean
function REPORT:HasText() --R2.1 function REPORT:HasText() -- R2.1
return #self.Report > 0 return #self.Report > 0
end end
--- Set indent of a REPORT. --- Set indent of a REPORT.
-- @param #REPORT self -- @param #REPORT self
-- @param #number Indent -- @param #number Indent
-- @return #REPORT -- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1 function REPORT:SetIndent( Indent ) -- R2.1
self.Indent = Indent self.Indent = Indent
return self return self
end end
--- Add a new line to a REPORT. --- Add a new line to a REPORT.
-- @param #REPORT self -- @param #REPORT self
-- @param #string Text -- @param #string Text
-- @return #REPORT -- @return #REPORT
function REPORT:Add( Text ) function REPORT:Add( Text )
self.Report[#self.Report+1] = Text self.Report[#self.Report + 1] = Text
return self return self
end end
@@ -76,17 +73,17 @@ end
-- @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. -- @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 -- @return #REPORT
function REPORT:AddIndent( Text, Separator ) 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 ) ) 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 return self
end end
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default. --- Produces the text of the report, taking into account an optional delimiter, which is \n by default.
-- @param #REPORT self -- @param #REPORT self
-- @param #string Delimiter (optional) A delimiter text. -- @param #string Delimiter (optional) A delimiter text.
-- @return #string The report text. -- @return #string The report text.
function REPORT:Text( Delimiter ) function REPORT:Text( Delimiter )
Delimiter = Delimiter or "\n" Delimiter = Delimiter or "\n"
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or "" local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or ""
return ReportText return ReportText
end end
@@ -95,7 +92,7 @@ end
-- @param #string Title The title of the report. -- @param #string Title The title of the report.
-- @return #REPORT -- @return #REPORT
function REPORT:SetTitle( Title ) function REPORT:SetTitle( Title )
self.Title = Title self.Title = Title
return self return self
end end

View File

@@ -1,36 +1,36 @@
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules. --- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
-- --
-- === -- ===
-- --
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. -- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
-- --
-- This class is tricky and needs some thorough explanation. -- This class is tricky and needs some thorough explanation.
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. -- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
-- The SCHEDULEDISPATCHER class ensures that: -- The SCHEDULEDISPATCHER class ensures that:
-- --
-- - Scheduled functions are planned according the SCHEDULER object parameters. -- - Scheduled functions are planned according the SCHEDULER object parameters.
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. -- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. -- - 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: -- 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! -- - 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. -- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, -- 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. -- these will not be executed anymore when the SCHEDULER object has been destroyed.
-- --
-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. -- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object.
-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. -- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER.
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. -- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
-- The Schedule() method returns the CallID that is the reference ID for each planned schedule. -- The Schedule() method returns the CallID that is the reference ID for each planned schedule.
-- --
-- === -- ===
-- --
-- ### Contributions: - -- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming -- ### Authors: FlightControl : Design & Programming
-- --
-- @module Core.ScheduleDispatcher -- @module Core.ScheduleDispatcher
-- @image Core_Schedule_Dispatcher.JPG -- @image Core_Schedule_Dispatcher.JPG
@@ -38,7 +38,7 @@
-- @type SCHEDULEDISPATCHER -- @type SCHEDULEDISPATCHER
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #number CallID Call ID counter. -- @field #number CallID Call ID counter.
-- @field #table PersistentSchedulers Persistant schedulers. -- @field #table PersistentSchedulers Persistent schedulers.
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists. -- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ). -- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -46,11 +46,11 @@
--- The SCHEDULEDISPATCHER structure --- The SCHEDULEDISPATCHER structure
-- @type SCHEDULEDISPATCHER -- @type SCHEDULEDISPATCHER
SCHEDULEDISPATCHER = { SCHEDULEDISPATCHER = {
ClassName = "SCHEDULEDISPATCHER", ClassName = "SCHEDULEDISPATCHER",
CallID = 0, CallID = 0,
PersistentSchedulers = {}, PersistentSchedulers = {},
ObjectSchedulers = {}, ObjectSchedulers = {},
Schedule = nil, Schedule = nil,
} }
--- Player data table holding all important parameters of each player. --- Player data table holding all important parameters of each player.
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
-- @field #function Function The schedule function to be called. -- @field #function Function The schedule function to be called.
-- @field #table Arguments Schedule function arguments. -- @field #table Arguments Schedule function arguments.
-- @field #number Start Start time in seconds. -- @field #number Start Start time in seconds.
-- @field #number Repeat Repeat time intervall in seconds. -- @field #number Repeat Repeat time interval in seconds.
-- @field #number Randomize Randomization factor [0,1]. -- @field #number Randomize Randomization factor [0,1].
-- @field #number Stop Stop time in seconds. -- @field #number Stop Stop time in seconds.
-- @field #number StartTime Time in seconds when the scheduler is created. -- @field #number StartTime Time in seconds when the scheduler is created.
@@ -77,7 +77,7 @@ end
--- Add a Schedule to the ScheduleDispatcher. --- Add a Schedule to the ScheduleDispatcher.
-- The development of this method was really tidy. -- The development of this method was really tidy.
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. -- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
-- Nothing of this code should be modified without testing it thoroughly. -- Nothing of this code should be modified without testing it thoroughly.
-- @param #SCHEDULEDISPATCHER self -- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
@@ -85,7 +85,7 @@ end
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction. -- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
-- @param #number Start Start time in seconds. -- @param #number Start Start time in seconds.
-- @param #number Repeat Repeat interval in seconds. -- @param #number Repeat Repeat interval in seconds.
-- @param #number Randomize Radomization factor [0,1]. -- @param #number Randomize Randomization factor [0,1].
-- @param #number Stop Stop time in seconds. -- @param #number Stop Stop time in seconds.
-- @param #number TraceLevel Trace level [0,3]. -- @param #number TraceLevel Trace level [0,3].
-- @param Core.Fsm#FSM Fsm Finite state model. -- @param Core.Fsm#FSM Fsm Finite state model.
@@ -95,39 +95,40 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
-- Increase counter. -- Increase counter.
self.CallID = self.CallID + 1 self.CallID = self.CallID + 1
-- Create ID. -- Create ID.
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or "" 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)) self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
-- Initialize PersistentSchedulers -- Initialize PersistentSchedulers
self.PersistentSchedulers = self.PersistentSchedulers or {} self.PersistentSchedulers = self.PersistentSchedulers or {}
-- Initialize the ObjectSchedulers array, which is a weakly coupled table. -- 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. -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then if Scheduler.MasterObject then
--env.info("FF Object Scheduler")
self.ObjectSchedulers[CallID] = Scheduler self.ObjectSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } ) self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
else else
--env.info("FF Persistent Scheduler")
self.PersistentSchedulers[CallID] = Scheduler self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } ) self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
end end
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {} self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
self.Schedule[Scheduler][CallID].Function = ScheduleFunction self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 ) self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + 0.1 self.Schedule[Scheduler][CallID].Start = Start + 0.001
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop self.Schedule[Scheduler][CallID].Stop = Stop
-- This section handles the tracing of the scheduled calls. -- 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. -- 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 Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
@@ -149,10 +150,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
-- 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. -- 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. -- 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 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. -- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
local Info = {} local Info = {}
if debug then if debug then
TraceLevel = TraceLevel or 2 TraceLevel = TraceLevel or 2
Info = debug.getinfo( TraceLevel, "nlS" ) Info = debug.getinfo( TraceLevel, "nlS" )
@@ -166,7 +167,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
--- Function passed to the DCS timer.scheduleFunction() --- Function passed to the DCS timer.scheduleFunction()
self.Schedule[Scheduler][CallID].CallHandler = function( Params ) self.Schedule[Scheduler][CallID].CallHandler = function( Params )
local CallID = Params.CallID local CallID = Params.CallID
local Info = Params.Info or {} local Info = Params.Info or {}
local Source = Info.source or "?" local Source = Info.source or "?"
@@ -180,27 +181,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
end end
return errmsg return errmsg
end end
-- Get object or persistant scheduler object. -- Get object or persistent scheduler object.
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
if not Scheduler then if not Scheduler then
Scheduler = self.PersistentSchedulers[CallID] Scheduler = self.PersistentSchedulers[CallID]
end end
--self:T3( { Scheduler = Scheduler } ) -- self:T3( { Scheduler = Scheduler } )
if Scheduler then if Scheduler then
local MasterObject = tostring(Scheduler.MasterObject) local MasterObject = tostring( Scheduler.MasterObject )
-- Schedule object.
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
--self:T3( { Schedule = Schedule } )
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object? -- Schedule object.
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
-- self:T3( { Schedule = Schedule } )
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
local ShowTrace = Scheduler.ShowTrace local ShowTrace = Scheduler.ShowTrace
local ScheduleFunction = Schedule.Function local ScheduleFunction = Schedule.Function
local ScheduleArguments = Schedule.Arguments or {} local ScheduleArguments = Schedule.Arguments or {}
local Start = Schedule.Start local Start = Schedule.Start
@@ -208,17 +209,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local Randomize = Schedule.Randomize or 0 local Randomize = Schedule.Randomize or 0
local Stop = Schedule.Stop or 0 local Stop = Schedule.Stop or 0
local ScheduleID = Schedule.ScheduleID local ScheduleID = Schedule.ScheduleID
local Prefix = (Repeat == 0) and "--->" or "+++>"
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
local Status, Result local Status, Result
--self:E( { SchedulerObject = SchedulerObject } ) -- self:E( { SchedulerObject = SchedulerObject } )
if SchedulerObject then if SchedulerObject then
local function Timer() local function Timer()
if ShowTrace then if ShowTrace then
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end end
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end end
Status, Result = xpcall( Timer, ErrorHandler ) Status, Result = xpcall( Timer, ErrorHandler )
@@ -227,40 +228,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if ShowTrace then if ShowTrace then
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end end
return ScheduleFunction( unpack( ScheduleArguments ) ) return ScheduleFunction( unpack( ScheduleArguments ) )
end end
Status, Result = xpcall( Timer, ErrorHandler ) Status, Result = xpcall( Timer, ErrorHandler )
end end
local CurrentTime = timer.getTime() local CurrentTime = timer.getTime()
local StartTime = Schedule.StartTime local StartTime = Schedule.StartTime
-- Debug info. -- Debug info.
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } ) 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 Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) 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.0001 -- Accuracy
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy -- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
return ScheduleTime -- returns the next time the function needs to be called. return ScheduleTime -- returns the next time the function needs to be called.
else else
self:Stop( Scheduler, CallID ) self:Stop( Scheduler, CallID )
end end
else else
self:Stop( Scheduler, CallID ) self:Stop( Scheduler, CallID )
end end
else else
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" ) self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end end
return nil return nil
end end
self:Start( Scheduler, CallID, Info ) self:Start( Scheduler, CallID, Info )
return CallID return CallID
end end
@@ -284,67 +284,67 @@ end
-- @param #string Info (Optional) Debug info. -- @param #string Info (Optional) Debug info.
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info ) function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
self:F2( { Start = CallID, Scheduler = Scheduler } ) self:F2( { Start = CallID, Scheduler = Scheduler } )
if CallID then if CallID then
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
-- Only start when there is no ScheduleID defined! -- Only start when there is no ScheduleID defined!
-- This prevents to "Start" the scheduler twice with the same CallID... -- This prevents to "Start" the scheduler twice with the same CallID...
if not Schedule.ScheduleID then if not Schedule.ScheduleID then
-- Current time in seconds. -- Current time in seconds.
local Tnow=timer.getTime() local Tnow = timer.getTime()
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. 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 -- 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) 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))) self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
end end
else else
-- Recursive. -- Recursive.
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Start( Scheduler, CallID, Info ) -- Recursive self:Start( Scheduler, CallID, Info ) -- Recursive
end end
end end
end end
--- Stop dispatcher. --- Stop dispatcher.
-- @param #SCHEDULEDISPATCHER self -- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID Call ID. -- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
self:F2( { Stop = CallID, Scheduler = Scheduler } ) self:F2( { Stop = CallID, Scheduler = Scheduler } )
if CallID then if CallID then
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData 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. -- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
if Schedule.ScheduleID then if Schedule.ScheduleID then
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) 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 -- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
timer.removeFunction(Schedule.ScheduleID) timer.removeFunction( Schedule.ScheduleID )
Schedule.ScheduleID = nil Schedule.ScheduleID = nil
else else
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID))) self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
end end
else else
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Stop( Scheduler, CallID ) -- Recursive self:Stop( Scheduler, CallID ) -- Recursive
end end
end end
end end
@@ -359,7 +359,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
end end
end end
--- Shopw tracing info. --- Show tracing info.
-- @param #SCHEDULEDISPATCHER self -- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
function SCHEDULEDISPATCHER:ShowTrace( Scheduler ) function SCHEDULEDISPATCHER:ShowTrace( Scheduler )

View File

@@ -3,39 +3,39 @@
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Schedule functions over time, -- * Schedule functions over time,
-- * optionally in an optional specified time interval, -- * optionally in an optional specified time interval,
-- * optionally **repeating** with a specified time repeat interval, -- * optionally **repeating** with a specified time repeat interval,
-- * optionally **randomizing** with a specified time interval randomization factor, -- * optionally **randomizing** with a specified time interval randomization factor,
-- * optionally **stop** the repeating after a specified time interval. -- * optionally **stop** the repeating after a specified time interval.
-- --
-- === -- ===
-- --
-- # Demo Missions -- # Demo Missions
-- --
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler) -- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
-- --
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
-- --
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) -- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
-- --
-- ===
--
-- # YouTube Channel
--
-- ### [SCHEDULER YouTube Channel (none)]()
--
-- === -- ===
-- --
-- ### Contributions: -- # YouTube Channel
-- --
-- ### [SCHEDULER YouTube Channel (none)]()
--
-- ===
--
-- ### Contributions:
--
-- * FlightControl : Concept & Testing -- * FlightControl : Concept & Testing
-- --
-- ### Authors: -- ### Authors:
-- --
-- * FlightControl : Design & Programming -- * FlightControl : Design & Programming
-- --
-- === -- ===
-- --
-- @module Core.Scheduler -- @module Core.Scheduler
@@ -48,62 +48,61 @@
-- @field #boolean ShowTrace Trace info if true. -- @field #boolean ShowTrace Trace info if true.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization. --- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
-- --
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**. -- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called. -- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(), -- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object. -- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
-- --
-- ## SCHEDULER constructor -- ## SCHEDULER constructor
-- --
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: -- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
-- --
-- The @{#SCHEDULER.New}() method returns 2 variables: -- The @{#SCHEDULER.New}() method returns 2 variables:
-- --
-- 1. The SCHEDULER object reference. -- 1. The SCHEDULER object reference.
-- 2. The first schedule planned in the SCHEDULER object. -- 2. The first schedule planned in the SCHEDULER object.
-- --
-- To clarify the different appliances, lets have a look at the following examples: -- To clarify the different appliances, lets have a look at the following examples:
-- --
-- ### Construct a SCHEDULER object without a persistent schedule. -- ### Construct a SCHEDULER object without a persistent schedule.
-- --
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. -- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
-- --
-- MasterObject = SCHEDULER:New() -- MasterObject = SCHEDULER:New()
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} ) -- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
-- --
-- The above example creates a new MasterObject, but does not schedule anything. -- 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 -- 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... -- ### 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. -- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
-- --
-- ZoneObject = ZONE:New( "ZoneName" ) -- ZoneObject = ZONE:New( "ZoneName" )
-- MasterObject = SCHEDULER:New( ZoneObject ) -- MasterObject = SCHEDULER:New( ZoneObject )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ... -- ...
-- ZoneObject = nil -- ZoneObject = nil
-- garbagecollect() -- garbagecollect()
-- --
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE. -- 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 -- 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. -- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the MasterObject will cancel any planned schedule. -- As a result, the MasterObject will cancel any planned schedule.
-- --
-- ### Construct a SCHEDULER object with a persistent 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. -- * @{#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.
-- --
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} ) -- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
-- --
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. -- 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... -- Note that 2 variables are returned here: MasterObject, ScheduleID...
-- --
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence... -- ### 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. -- * @{#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" ) -- ZoneObject = ZONE:New( "ZoneName" )
@@ -112,13 +111,13 @@
-- ... -- ...
-- ZoneObject = nil -- ZoneObject = nil
-- garbagecollect() -- garbagecollect()
-- --
-- The above example creates a new MasterObject, 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). -- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
-- Both a MasterObject 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. -- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the MasterObject will cancel the planned schedule. -- As a result, the MasterObject will cancel the planned schedule.
-- --
-- ## SCHEDULER timer stopping and (re-)starting. -- ## SCHEDULER timer stopping and (re-)starting.
-- --
-- The SCHEDULER can be stopped and restarted with the following methods: -- The SCHEDULER can be stopped and restarted with the following methods:
@@ -133,70 +132,70 @@
-- MasterObject:Stop( SchedulerID ) -- MasterObject:Stop( SchedulerID )
-- ... -- ...
-- MasterObject:Start( SchedulerID ) -- MasterObject:Start( SchedulerID )
-- --
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. -- 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... -- Note that 2 variables are returned here: MasterObject, ScheduleID...
-- Later in the logic, the repeating schedule with SchedulerID is stopped. -- Later in the logic, the repeating schedule with SchedulerID is stopped.
-- A bit later, the repeating schedule with SchedulerId is (re)-started. -- A bit later, the repeating schedule with SchedulerId is (re)-started.
-- --
-- ## Create a new schedule -- ## Create a new schedule
-- --
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. -- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
-- This method is used by the :New() constructor when a new schedule is planned. -- This method is used by the :New() constructor when a new schedule is planned.
-- --
-- Consider the following code fragment of the SCHEDULER object creation. -- Consider the following code fragment of the SCHEDULER object creation.
-- --
-- ZoneObject = ZONE:New( "ZoneName" ) -- ZoneObject = ZONE:New( "ZoneName" )
-- MasterObject = SCHEDULER:New( ZoneObject ) -- MasterObject = SCHEDULER:New( ZoneObject )
-- --
-- Several parameters can be specified that influence the behaviour of a Schedule. -- Several parameters can be specified that influence the behavior of a Schedule.
-- --
-- ### A single schedule, immediately executed -- ### A single schedule, immediately executed
-- --
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- --
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ... -- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
-- --
-- ### A single schedule, planned over time -- ### A single schedule, planned over time
-- --
-- SchedulerID = MasterObject: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 ... -- 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 -- ### A schedule with a repeating time interval, planned over time
-- --
-- SchedulerID = MasterObject: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, -- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- and repeating 60 every seconds ... -- and repeating 60 every seconds ...
-- --
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization -- ### A schedule with a repeating time interval, planned over time, with time interval randomization
-- --
-- SchedulerID = MasterObject: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, -- 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 ... -- and repeating 60 seconds, with a 50% time interval randomization ...
-- So the repeating time interval will be randomized using the **0.5**, -- So the repeating time interval will be randomized using the **0.5**,
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
-- which is in this example between **30** and **90** seconds. -- which is in this example between **30** and **90** seconds.
-- --
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval -- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
-- --
-- SchedulerID = MasterObject: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 above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- The schedule will repeat every 60 seconds. -- The schedule will repeat every 60 seconds.
-- So the repeating time interval will be randomized using the **0.5**, -- So the repeating time interval will be randomized using the **0.5**,
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
-- which is in this example between **30** and **90** seconds. -- which is in this example between **30** and **90** seconds.
-- The schedule will stop after **300** seconds. -- The schedule will stop after **300** seconds.
-- --
-- @field #SCHEDULER -- @field #SCHEDULER
SCHEDULER = { SCHEDULER = {
ClassName = "SCHEDULER", ClassName = "SCHEDULER",
Schedules = {}, Schedules = {},
MasterObject = nil, MasterObject = nil,
ShowTrace = nil, ShowTrace = nil,
} }
--- SCHEDULER constructor. --- SCHEDULER constructor.
@@ -211,15 +210,15 @@ SCHEDULER = {
-- @return #SCHEDULER self. -- @return #SCHEDULER self.
-- @return #table The ScheduleID of the planned schedule. -- @return #table The ScheduleID of the planned schedule.
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
self:F2( { Start, Repeat, RandomizeFactor, Stop } ) self:F2( { Start, Repeat, RandomizeFactor, Stop } )
local ScheduleID = nil local ScheduleID = nil
self.MasterObject = MasterObject self.MasterObject = MasterObject
self.ShowTrace = false self.ShowTrace = false
if SchedulerFunction then if SchedulerFunction then
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 ) ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
end end
@@ -235,48 +234,47 @@ end
-- @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 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 time 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 RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe. -- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
-- @param #number TraceLevel Trace level [0,3]. Default 3. -- @param #number TraceLevel Trace level [0,3]. Default 3.
-- @param Core.Fsm#FSM Fsm Finite state model. -- @param Core.Fsm#FSM Fsm Finite state model.
-- @return #table The ScheduleID of the planned schedule. -- @return #string The Schedule ID of the planned schedule.
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm ) function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
self:F2( { Start, Repeat, RandomizeFactor, Stop } ) self:F2( { Start, Repeat, RandomizeFactor, Stop } )
self:T3( { SchedulerArguments } ) self:T3( { SchedulerArguments } )
-- Debug info. -- Debug info.
local ObjectName = "-" local ObjectName = "-"
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
ObjectName = MasterObject.ClassName .. MasterObject.ClassID ObjectName = MasterObject.ClassName .. MasterObject.ClassID
end end
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
-- Set master object. -- Set master object.
self.MasterObject = MasterObject self.MasterObject = MasterObject
-- Add schedule. -- Add schedule.
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
self, SchedulerFunction,
SchedulerFunction, SchedulerArguments,
SchedulerArguments, Start,
Start, Repeat,
Repeat, RandomizeFactor,
RandomizeFactor, Stop,
Stop, TraceLevel or 3,
TraceLevel or 3, Fsm
Fsm )
)
self.Schedules[#self.Schedules + 1] = ScheduleID
self.Schedules[#self.Schedules+1] = ScheduleID
return ScheduleID return ScheduleID
end end
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. --- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self -- @param #SCHEDULER self
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. -- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
function SCHEDULER:Start( ScheduleID ) function SCHEDULER:Start( ScheduleID )
self:F3( { ScheduleID } ) self:F3( { ScheduleID } )
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID))) self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
_SCHEDULEDISPATCHER:Start( self, ScheduleID ) _SCHEDULEDISPATCHER:Start( self, ScheduleID )
end end
@@ -285,7 +283,7 @@ end
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Stop( ScheduleID ) function SCHEDULER:Stop( ScheduleID )
self:F3( { ScheduleID } ) self:F3( { ScheduleID } )
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID))) self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
_SCHEDULEDISPATCHER:Stop( self, ScheduleID ) _SCHEDULEDISPATCHER:Stop( self, ScheduleID )
end end
@@ -294,15 +292,15 @@ end
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Remove( ScheduleID ) function SCHEDULER:Remove( ScheduleID )
self:F3( { ScheduleID } ) self:F3( { ScheduleID } )
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID))) self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID ) _SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
end end
--- Clears all pending schedules. --- Clears all pending schedules.
-- @param #SCHEDULER self -- @param #SCHEDULER self
function SCHEDULER:Clear() function SCHEDULER:Clear()
self:F3( ) self:F3()
self:T(string.format("Clearing scheduler")) self:T( string.format( "Clearing scheduler" ) )
_SCHEDULEDISPATCHER:Clear( self ) _SCHEDULEDISPATCHER:Clear( self )
end end

File diff suppressed because it is too large Load Diff

View File

@@ -29,15 +29,14 @@
-- @module Core.Settings -- @module Core.Settings
-- @image Core_Settings.JPG -- @image Core_Settings.JPG
--- @type SETTINGS --- @type SETTINGS
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. --- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
-- --
-- === -- ===
-- --
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. -- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
-- SETTINGS can work on 2 levels: -- SETTINGS can work on 2 levels:
-- --
-- - **Default settings**: A running mission has **Default settings**. -- - **Default settings**: A running mission has **Default settings**.
@@ -59,7 +58,7 @@
-- --
-- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- A menu is created automatically per Command Center that allows to modify the **Default** settings.
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
-- Note that the **Default settings** will only be used when a player has not choosen its own settings. -- Note that the **Default settings** will only be used when a player has not chosen its own settings.
-- --
-- ## 2.2) Player settings menu -- ## 2.2) Player settings menu
-- --
@@ -69,7 +68,7 @@
-- --
-- ## 2.3) Show or Hide the Player Setting menus -- ## 2.3) Show or Hide the Player Setting menus
-- --
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed. -- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
-- The option will only have effect when a player enters a new slot or changes a slot. -- The option will only have effect when a player enters a new slot or changes a slot.
@@ -94,8 +93,8 @@
-- --
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. -- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. -- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
-- --
-- ### 3.1.2) A2G coordinates setting **menu** -- ### 3.1.2) A2G coordinates setting **menu**
-- --
@@ -183,7 +182,7 @@
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
-- --
-- Each Message Type has specific timings that will be applied when the message is displayed. -- Each Message Type has specific timings that will be applied when the message is displayed.
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen. -- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
-- Note that **Update** messages can be chosen not to be displayed at all! -- Note that **Update** messages can be chosen not to be displayed at all!
-- --
@@ -196,7 +195,7 @@
-- --
-- ## 3.5) **Era** of the battle -- ## 3.5) **Era** of the battle
-- --
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare. -- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
-- Therefore, there are 4 era that are defined within the settings: -- Therefore, there are 4 era that are defined within the settings:
-- --
-- - **WWII** era: Use for warfare with equipment during the world war II time. -- - **WWII** era: Use for warfare with equipment during the world war II time.
@@ -213,8 +212,8 @@
SETTINGS = { SETTINGS = {
ClassName = "SETTINGS", ClassName = "SETTINGS",
ShowPlayerMenu = true, ShowPlayerMenu = true,
MenuShort = false, MenuShort = false,
MenuStatic = false, MenuStatic = false,
} }
SETTINGS.__Enum = {} SETTINGS.__Enum = {}
@@ -231,11 +230,11 @@ SETTINGS.__Enum.Era = {
Modern = 4, Modern = 4,
} }
do -- SETTINGS do -- SETTINGS
--- SETTINGS constructor. --- SETTINGS constructor.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @param #string PlayerName (Optional) Set settings for this player.
-- @return #SETTINGS -- @return #SETTINGS
function SETTINGS:Set( PlayerName ) function SETTINGS:Set( PlayerName )
@@ -252,6 +251,7 @@ do -- SETTINGS
self:SetMessageTime( MESSAGE.Type.Overview, 60 ) self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 ) self:SetMessageTime( MESSAGE.Type.Update, 15 )
self:SetEraModern() self:SetEraModern()
self:SetLocale("en")
return self return self
else else
local Settings = _DATABASE:GetPlayerSettings( PlayerName ) local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@@ -267,14 +267,14 @@ do -- SETTINGS
-- Short text are better suited for, e.g., VR. -- Short text are better suited for, e.g., VR.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default). -- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
function SETTINGS:SetMenutextShort(onoff) function SETTINGS:SetMenutextShort( onoff )
_SETTINGS.MenuShort = onoff _SETTINGS.MenuShort = onoff
end end
--- Set menu to be static. --- Set menu to be static.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
function SETTINGS:SetMenuStatic(onoff) function SETTINGS:SetMenuStatic( onoff )
_SETTINGS.MenuStatic = onoff _SETTINGS.MenuStatic = onoff
end end
@@ -283,12 +283,26 @@ do -- SETTINGS
function SETTINGS:SetMetric() function SETTINGS:SetMetric()
self.Metric = true self.Metric = true
end end
--- Sets the SETTINGS default text locale.
-- @param #SETTINGS self
-- @param #string Locale
function SETTINGS:SetLocale(Locale)
self.Locale = Locale or "en"
end
--- Gets the SETTINGS text locale.
-- @param #SETTINGS self
-- @return #string
function SETTINGS:GetLocale()
return self.Locale or _SETTINGS:GetLocale()
end
--- Gets if the SETTINGS is metric. --- Gets if the SETTINGS is metric.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if metric. -- @return #boolean true if metric.
function SETTINGS:IsMetric() function SETTINGS:IsMetric()
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
end end
--- Sets the SETTINGS imperial. --- Sets the SETTINGS imperial.
@@ -301,7 +315,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if imperial. -- @return #boolean true if imperial.
function SETTINGS:IsImperial() function SETTINGS:IsImperial()
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsImperial())
end end
--- Sets the SETTINGS LL accuracy. --- Sets the SETTINGS LL accuracy.
@@ -343,13 +357,12 @@ do -- SETTINGS
self.MessageTypeTimings[MessageType] = MessageTime self.MessageTypeTimings[MessageType] = MessageTime
end end
--- Gets the SETTINGS Message Display Timing of a MessageType --- Gets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self -- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message. -- @param Core.Message#MESSAGE MessageType The type of the message.
-- @return #number -- @return #number
function SETTINGS:GetMessageTime( MessageType ) function SETTINGS:GetMessageTime( MessageType )
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType ) return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
end end
--- Sets A2G LL DMS --- Sets A2G LL DMS
@@ -370,14 +383,14 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if LL DMS -- @return #boolean true if LL DMS
function SETTINGS:IsA2G_LL_DMS() function SETTINGS:IsA2G_LL_DMS()
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() ) return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
end end
--- Is LL DDM --- Is LL DDM
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if LL DDM -- @return #boolean true if LL DDM
function SETTINGS:IsA2G_LL_DDM() function SETTINGS:IsA2G_LL_DDM()
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() ) return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
end end
--- Sets A2G MGRS --- Sets A2G MGRS
@@ -391,7 +404,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if MGRS -- @return #boolean true if MGRS
function SETTINGS:IsA2G_MGRS() function SETTINGS:IsA2G_MGRS()
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() ) return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
end end
--- Sets A2G BRA --- Sets A2G BRA
@@ -405,7 +418,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if BRA -- @return #boolean true if BRA
function SETTINGS:IsA2G_BR() function SETTINGS:IsA2G_BR()
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() ) return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
end end
--- Sets A2A BRA --- Sets A2A BRA
@@ -419,7 +432,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if BRA -- @return #boolean true if BRA
function SETTINGS:IsA2A_BRAA() function SETTINGS:IsA2A_BRAA()
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
end end
--- Sets A2A BULLS --- Sets A2A BULLS
@@ -433,7 +446,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if BULLS -- @return #boolean true if BULLS
function SETTINGS:IsA2A_BULLS() function SETTINGS:IsA2A_BULLS()
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() ) return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
end end
--- Sets A2A LL DMS --- Sets A2A LL DMS
@@ -454,14 +467,14 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if LL DMS -- @return #boolean true if LL DMS
function SETTINGS:IsA2A_LL_DMS() function SETTINGS:IsA2A_LL_DMS()
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() ) return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
end end
--- Is LL DDM --- Is LL DDM
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if LL DDM -- @return #boolean true if LL DDM
function SETTINGS:IsA2A_LL_DDM() function SETTINGS:IsA2A_LL_DDM()
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() ) return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
end end
--- Sets A2A MGRS --- Sets A2A MGRS
@@ -475,7 +488,7 @@ do -- SETTINGS
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #boolean true if MGRS -- @return #boolean true if MGRS
function SETTINGS:IsA2A_MGRS() function SETTINGS:IsA2A_MGRS()
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() ) return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
end end
--- @param #SETTINGS self --- @param #SETTINGS self
@@ -494,37 +507,37 @@ do -- SETTINGS
-- A2G Coordinate System -- A2G Coordinate System
------- -------
local text="A2G Coordinate System" local text = "A2G Coordinate System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G Coordinates" text = "A2G Coordinates"
end end
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
-- Set LL DMS -- Set LL DMS
if not self:IsA2G_LL_DMS() then if not self:IsA2G_LL_DMS() then
local text="Lat/Lon Degree Min Sec (LL DMS)" local text = "Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="LL DMS" text = "LL DMS"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end end
-- Set LL DDM -- Set LL DDM
if not self:IsA2G_LL_DDM() then if not self:IsA2G_LL_DDM() then
local text="Lat/Lon Degree Dec Min (LL DDM)" local text = "Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="LL DDM" text = "LL DDM"
end end
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end end
-- Set LL DMS accuracy. -- Set LL DMS accuracy.
if self:IsA2G_LL_DDM() then if self:IsA2G_LL_DDM() then
local text1="LL DDM Accuracy 1" local text1 = "LL DDM Accuracy 1"
local text2="LL DDM Accuracy 2" local text2 = "LL DDM Accuracy 2"
local text3="LL DDM Accuracy 3" local text3 = "LL DDM Accuracy 3"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text1="LL DDM" text1 = "LL DDM"
end end
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
@@ -533,18 +546,18 @@ do -- SETTINGS
-- Set BR. -- Set BR.
if not self:IsA2G_BR() then if not self:IsA2G_BR() then
local text="Bearing, Range (BR)" local text = "Bearing, Range (BR)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="BR" text = "BR"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
end end
-- Set MGRS. -- Set MGRS.
if not self:IsA2G_MGRS() then if not self:IsA2G_MGRS() then
local text="Military Grid (MGRS)" local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="MGRS" text = "MGRS"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end end
@@ -562,24 +575,24 @@ do -- SETTINGS
-- A2A Coordinate System -- A2A Coordinate System
------- -------
local text="A2A Coordinate System" local text = "A2A Coordinate System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A Coordinates" text = "A2A Coordinates"
end end
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
if not self:IsA2A_LL_DMS() then if not self:IsA2A_LL_DMS() then
local text="Lat/Lon Degree Min Sec (LL DMS)" local text = "Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="LL DMS" text = "LL DMS"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end end
if not self:IsA2A_LL_DDM() then if not self:IsA2A_LL_DDM() then
local text="Lat/Lon Degree Dec Min (LL DDM)" local text = "Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="LL DDM" text = "LL DDM"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end end
@@ -592,25 +605,25 @@ do -- SETTINGS
end end
if not self:IsA2A_BULLS() then if not self:IsA2A_BULLS() then
local text="Bullseye (BULLS)" local text = "Bullseye (BULLS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Bulls" text = "Bulls"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
end end
if not self:IsA2A_BRAA() then if not self:IsA2A_BRAA() then
local text="Bearing Range Altitude Aspect (BRAA)" local text = "Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="BRAA" text = "BRAA"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
end end
if not self:IsA2A_MGRS() then if not self:IsA2A_MGRS() then
local text="Military Grid (MGRS)" local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="MGRS" text = "MGRS"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end end
@@ -623,31 +636,31 @@ do -- SETTINGS
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end end
local text="Measures and Weights System" local text = "Measures and Weights System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Unit System" text = "Unit System"
end end
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
if self:IsMetric() then if self:IsMetric() then
local text="Imperial (Miles,Feet)" local text = "Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Imperial" text = "Imperial"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
end end
if self:IsImperial() then if self:IsImperial() then
local text="Metric (Kilometers,Meters)" local text = "Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Metric" text = "Metric"
end end
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
end end
local text="Messages and Reports" local text = "Messages and Reports"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Messages & Reports" text = "Messages & Reports"
end end
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
@@ -688,7 +701,6 @@ do -- SETTINGS
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
SettingsMenu:Remove( MenuTime ) SettingsMenu:Remove( MenuTime )
return self return self
@@ -732,11 +744,11 @@ do -- SETTINGS
self.PlayerMenu = PlayerMenu self.PlayerMenu = PlayerMenu
self:I(string.format("Setting menu for player %s", tostring(PlayerName))) self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu ) local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
@@ -753,40 +765,40 @@ do -- SETTINGS
-- A2G Coordinate System -- A2G Coordinate System
------ ------
local text="A2G Coordinate System" local text = "A2G Coordinate System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G Coordinates" text = "A2G Coordinates"
end end
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)" local text = "Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G LL DMS" text = "A2G LL DMS"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end end
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)" local text = "Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G LL DDM" text = "A2G LL DDM"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end end
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
local text="Bearing, Range (BR)" local text = "Bearing, Range (BR)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G BR" text = "A2G BR"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
end end
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)" local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2G MGRS" text = "A2G MGRS"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end end
@@ -795,49 +807,48 @@ do -- SETTINGS
-- A2A Coordinates Menu -- A2A Coordinates Menu
------ ------
local text="A2A Coordinate System" local text = "A2A Coordinate System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A Coordinates" text = "A2A Coordinates"
end end
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)" local text = "Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A LL DMS" text = "A2A LL DMS"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end end
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)" local text = "Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A LL DDM" text = "A2A LL DDM"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end end
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
local text="Bullseye (BULLS)" local text = "Bullseye (BULLS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A BULLS" text = "A2A BULLS"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
end end
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
local text="Bearing Range Altitude Aspect (BRAA)" local text = "Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A BRAA" text = "A2A BRAA"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
end end
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)" local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="A2A MGRS" text = "A2A MGRS"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end end
@@ -846,24 +857,24 @@ do -- SETTINGS
-- Unit system -- Unit system
--- ---
local text="Measures and Weights System" local text = "Measures and Weights System"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Unit System" text = "Unit System"
end end
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
if self:IsMetric() or _SETTINGS.MenuStatic then if self:IsMetric() or _SETTINGS.MenuStatic then
local text="Imperial (Miles,Feet)" local text = "Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Imperial" text = "Imperial"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false ) MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end end
if self:IsImperial() or _SETTINGS.MenuStatic then if self:IsImperial() or _SETTINGS.MenuStatic then
local text="Metric (Kilometers,Meters)" local text = "Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Metric" text = "Metric"
end end
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true ) MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end end
@@ -872,9 +883,9 @@ do -- SETTINGS
-- Messages and Reports -- Messages and Reports
--- ---
local text="Messages and Reports" local text = "Messages and Reports"
if _SETTINGS.MenuShort then if _SETTINGS.MenuShort then
text="Messages & Reports" text = "Messages & Reports"
end end
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
@@ -934,39 +945,38 @@ do -- SETTINGS
return self return self
end end
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem ) function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
self.A2GSystem = A2GSystem self.A2GSystem = A2GSystem
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem ) function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
self.A2ASystem = A2ASystem self.A2ASystem = A2ASystem
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy ) function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy ) function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW ) function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
self.Metric = MW self.Metric = MW
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll() MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu ) self:SetSystemMenu( MenuGroup, RootMenu )
end end
@@ -979,12 +989,12 @@ do -- SETTINGS
do do
--- @param #SETTINGS self --- @param #SETTINGS self
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} ) BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
self.A2GSystem = A2GSystem self.A2GSystem = A2GSystem
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu(PlayerUnit) self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu(PlayerUnit) self:SetPlayerMenu( PlayerUnit )
end end
end end
@@ -992,9 +1002,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem self.A2ASystem = A2ASystem
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu(PlayerUnit) self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu(PlayerUnit) self:SetPlayerMenu( PlayerUnit )
end end
end end
@@ -1002,9 +1012,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu(PlayerUnit) self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu(PlayerUnit) self:SetPlayerMenu( PlayerUnit )
end end
end end
@@ -1012,9 +1022,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu(PlayerUnit) self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu(PlayerUnit) self:SetPlayerMenu( PlayerUnit )
end end
end end
@@ -1022,9 +1032,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW ) function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
self.Metric = MW self.Metric = MW
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup ) MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu(PlayerUnit) self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu(PlayerUnit) self:SetPlayerMenu( PlayerUnit )
end end
end end
@@ -1054,7 +1064,6 @@ do -- SETTINGS
end end
--- Configures the era of the mission to be Cold war. --- Configures the era of the mission to be Cold war.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #SETTINGS self -- @return #SETTINGS self
@@ -1064,7 +1073,6 @@ do -- SETTINGS
end end
--- Configures the era of the mission to be Modern war. --- Configures the era of the mission to be Modern war.
-- @param #SETTINGS self -- @param #SETTINGS self
-- @return #SETTINGS self -- @return #SETTINGS self
@@ -1074,7 +1082,4 @@ do -- SETTINGS
end end
end end

File diff suppressed because it is too large Load Diff

View File

@@ -46,14 +46,14 @@
-- @field #number InitOffsetAngle Link offset angle in degrees. -- @field #number InitOffsetAngle Link offset angle in degrees.
-- @field #number InitStaticHeading Heading of the static. -- @field #number InitStaticHeading Heading of the static.
-- @field #string InitStaticLivery Livery for aircraft. -- @field #string InitStaticLivery Livery for aircraft.
-- @field #string InitStaticShape Shape of teh static. -- @field #string InitStaticShape Shape of the static.
-- @field #string InitStaticType Type of the static. -- @field #string InitStaticType Type of the static.
-- @field #string InitStaticCategory Categrory of the static. -- @field #string InitStaticCategory Categrory of the static.
-- @field #string InitStaticName Name of the static. -- @field #string InitStaticName Name of the static.
-- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn 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 InitStaticDead Set static to be dead if true.
-- @field #boolean InitCargo If true, static can act as cargo. -- @field #boolean InitStaticCargo If true, static can act as cargo.
-- @field #number InitCargoMass Mass of cargo in kg. -- @field #number InitStaticCargoMass Mass of cargo in kg.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -138,24 +138,24 @@ SPAWNSTATIC = {
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplateName self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID = SpawnCountryID or CountryID self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID self.CategoryID = CategoryID
self.CoalitionID = CoalitionID self.CoalitionID = CoalitionID
self.SpawnIndex = 0 self.SpawnIndex = 0
else else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end end
self:SetEventPriority( 5 ) self:SetEventPriority( 5 )
return self return self
end end
--- Creates the main object to spawn a @{Static} given a template table. --- Creates the main object to spawn a @{Static} given a template table.
@@ -241,12 +241,26 @@ function SPAWNSTATIC:InitShape(StaticShape)
return self return self
end end
--- Initialize parameters for spawning FARPs.
-- @param #SPAWNSTATIC self
-- @param #number CallsignID Callsign ID. Default 1 (="London").
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
-- @param #number Modulation Modulation 0=AM, 1=FM.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0
return self
end
--- Initialize cargo mass. --- Initialize cargo mass.
-- @param #SPAWNSTATIC self -- @param #SPAWNSTATIC self
-- @param #number Mass Mass of the cargo in kg. -- @param #number Mass Mass of the cargo in kg.
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargoMass(Mass) function SPAWNSTATIC:InitCargoMass(Mass)
self.InitCargoMass=Mass self.InitStaticCargoMass=Mass
return self return self
end end
@@ -255,7 +269,16 @@ end
-- @param #boolean IsCargo If true, this static can act as cargo. -- @param #boolean IsCargo If true, this static can act as cargo.
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargo(IsCargo) function SPAWNSTATIC:InitCargo(IsCargo)
self.InitCargo=IsCargo self.InitStaticCargo=IsCargo
return self
end
--- Initialize as dead.
-- @param #SPAWNSTATIC self
-- @param #boolean IsCargo If true, this static is dead.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitDead(IsDead)
self.InitStaticDead=IsDead
return self return self
end end
@@ -403,12 +426,16 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.livery_id=self.InitStaticLivery Template.livery_id=self.InitStaticLivery
end end
if self.InitDead~=nil then if self.InitStaticDead~=nil then
Template.dead=self.InitDead Template.dead=self.InitStaticDead
end end
if self.InitCargo~=nil then if self.InitStaticCargo~=nil then
Template.isCargo=self.InitCargo Template.canCargo=self.InitStaticCargo
end
if self.InitStaticCargoMass~=nil then
Template.mass=self.InitStaticCargoMass
end end
if self.InitLinkUnit then if self.InitLinkUnit then
@@ -420,22 +447,51 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
end end
if self.InitFarp then
Template.heliport_callsign_id = self.InitFarpCallsignID
Template.heliport_frequency = self.InitFarpFreq
Template.heliport_modulation = self.InitFarpModu
Template.unitId=nil
end
-- Increase spawn index counter. -- Increase spawn index counter.
self.SpawnIndex = self.SpawnIndex + 1 self.SpawnIndex = self.SpawnIndex + 1
-- Name of the spawned static. -- Name of the spawned static.
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
-- Register the new static. -- Add and register the new static.
--_DATABASE:_RegisterStaticTemplate(Template, self.CoalitionID, self.CategoryID, CountryID) local mystatic=_DATABASE:AddStatic(Template.name)
_DATABASE:AddStatic(Template.name)
-- Debug output. -- Debug output.
self:T(Template) self:T(Template)
-- Add static to the game. -- Add static to the game.
local Static=coalition.addStaticObject(CountryID, Template) local Static=nil
if self.InitFarp then
return _DATABASE:FindStatic(Static:getName()) local TemplateGroup={}
TemplateGroup.units={}
TemplateGroup.units[1]=Template
TemplateGroup.visible=true
TemplateGroup.hidden=false
TemplateGroup.x=Template.x
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
self:T("Spawning FARP")
self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup})
-- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
else
self:T("Spawning Static")
self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID, Template)
end
return mystatic
end end

View File

@@ -341,7 +341,7 @@ do
-- @return #SPOT -- @return #SPOT
function SPOT:onafterLaseOff( From, Event, To ) function SPOT:onafterLaseOff( From, Event, To )
self:F( {"Stopped lasing for ", self.Target:GetName() , SpotIR = self.SportIR, SpotLaser = self.SpotLaser } ) self:F( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self.Lasing = false self.Lasing = false

View File

@@ -0,0 +1,202 @@
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
--
-- ===
--
-- ## Main Features:
--
-- * A GetText for Moose
-- * Build a set of localized text entries, alongside their sounds and subtitles
-- * Aimed at class developers to offer localizable language support
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
--
-- ===
--
-- ### Author: **applevangelist**
-- ## Date: April 2022
--
-- ===
--
-- @module Core.TextAndSound
-- @image MOOSE.JPG
--- Text and Sound class.
-- @type TEXTANDSOUND
-- @field #string ClassName Name of this class.
-- @field #string version Versioning.
-- @field #string lid LID for log entries.
-- @field #string locale Default locale of this object.
-- @field #table entries Table of entries.
-- @field #string textclass Name of the class the texts belong to.
-- @extends Core.Base#BASE
---
--
-- @field #TEXTANDSOUND
TEXTANDSOUND = {
ClassName = "TEXTANDSOUND",
version = "0.0.1",
lid = "",
locale = "en",
entries = {},
textclass = "",
}
--- Text and Sound entry.
-- @type TEXTANDSOUND.Entry
-- @field #string Classname Name of the class this entry is for.
-- @field #string Locale Locale of this entry, defaults to "en".
-- @field #table Data The list of entries.
--- Text and Sound data
-- @type TEXTANDSOUND.Data
-- @field #string ID ID of this entry for retrieval.
-- @field #string Text Text of this entry.
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
-- @field #number Soundlength (optional) Length of the sound file in seconds.
-- @field #string Subtitle (optional) Subtitle for the sound file.
--- Instantiate a new object
-- @param #TEXTANDSOUND self
-- @param #string ClassName Name of the class this instance is providing texts for.
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:New(ClassName,Defaultlocale)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
self.textclass = ClassName or "none"
self.entries = {}
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = ClassName
initentry.Data = {}
initentry.Locale = self.locale
self.entries[self.locale] = initentry
self:I(self.lid .. "Instantiated.")
self:T({self.entries[self.locale]})
return self
end
--- Add an entry
-- @param #TEXTANDSOUND self
-- @param #string Locale Locale to set for this entry, e.g. "de".
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
-- @param #string Text Text for this entry.
-- @param #string Soundfile (Optional) Sound file name for this entry.
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
self:T(self.lid .. "AddEntry")
local locale = Locale or self.locale
local dataentry = {} -- #TEXTANDSOUND.Data
dataentry.ID = ID or "1"
dataentry.Text = Text or "none"
dataentry.Soundfile = Soundfile
dataentry.Soundlength = Soundlength or 0
dataentry.Subtitle = Subtitle
if not self.entries[locale] then
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = self.textclass -- class name entry
initentry.Data = {} -- data array
initentry.Locale = locale -- default locale
self.entries[locale] = initentry
end
self.entries[locale].Data[ID] = dataentry
self:T({self.entries[locale].Data})
return self
end
--- Get an entry
-- @param #TEXTANDSOUND self
-- @param #string ID The unique ID of the data to be retrieved.
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
-- @return #string Text Text or nil if not found and no fallback.
-- @return #string Soundfile Filename or nil if not found and no fallback.
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
function TEXTANDSOUND:GetEntry(ID,Locale)
self:T(self.lid .. "GetEntry")
local locale = Locale or self.locale
if not self.entries[locale] then
-- fall back to default "en"
locale = self.locale
end
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
if self.entries[locale] then
if self.entries[locale].Data then
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
if data then
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
elseif self.entries[self.locale].Data[ID] then
-- no matching entry, try default
local data = self.entries[self.locale].Data[ID]
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
end
end
else
return nil, nil, 0, nil
end
return Text,Soundfile,Soundlength,Subtitle
end
--- Get the default locale of this object
-- @param #TEXTANDSOUND self
-- @return #string locale
function TEXTANDSOUND:GetDefaultLocale()
self:T(self.lid .. "GetDefaultLocale")
return self.locale
end
--- Set default locale of this object
-- @param #TEXTANDSOUND self
-- @param #string locale
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:SetDefaultLocale(locale)
self:T(self.lid .. "SetDefaultLocale")
self.locale = locale or "en"
return self
end
--- Check if a locale exists
-- @param #TEXTANDSOUND self
-- @return #boolean outcome
function TEXTANDSOUND:HasLocale(Locale)
self:T(self.lid .. "HasLocale")
return self.entries[Locale] and true or false
end
--- Flush all entries to the log
-- @param #TEXTANDSOUND self
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:FlushToLog()
self:I(self.lid .. "Flushing entries:")
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
for _,_entry in pairs(self.entries) do
local entry = _entry -- #TEXTANDSOUND.Entry
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
self:I(text)
for _ID,_data in pairs(entry.Data) do
local data = _data -- #TEXTANDSOUND.Data
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
self:I(text)
end
end
return self
end
----------------------------------------------------------------
-- End TextAndSound
----------------------------------------------------------------

View File

@@ -11,7 +11,7 @@
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
-- @module Core.Timer -- @module Core.Timer
-- @image CORE_Timer.png -- @image Core_Scheduler.JPG
--- TIMER class. --- TIMER class.
@@ -19,6 +19,7 @@
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file. -- @field #string lid Class id string for output to DCS log file.
-- @field #number tid Timer ID returned by the DCS API function. -- @field #number tid Timer ID returned by the DCS API function.
-- @field #number uid Unique ID of the timer.
-- @field #function func Timer function. -- @field #function func Timer function.
-- @field #table para Parameters passed to the timer function. -- @field #table para Parameters passed to the timer function.
-- @field #number Tstart Relative start time in seconds. -- @field #number Tstart Relative start time in seconds.
@@ -26,17 +27,16 @@
-- @field #number dT Time interval between function calls in seconds. -- @field #number dT Time interval between function calls in seconds.
-- @field #number ncalls Counter of function calls. -- @field #number ncalls Counter of function calls.
-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. -- @field #number ncallsMax Max number of function calls. If reached, timer is stopped.
-- @field #boolean isrunning If `true`, timer is running. Else it was not started yet or was stopped.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* – William Shakespeare --- *Better three hours too soon than a minute too late.* - William Shakespeare
-- --
-- === -- ===
-- --
-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg)
--
-- # The TIMER Concept -- # 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. -- The TIMER class is the little sister of the @{Core.Scheduler#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). -- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction).
-- --
@@ -62,20 +62,20 @@
-- --
-- Note that -- Note that
-- --
-- * if *Tstart* is not specified (*nil*), the first function call happens immediately. -- * if *Tstart* is not specified (*nil*), the first function call happens immediately, i.e. after one millisecond.
-- * if *dT* is not specified (*nil*), the function is called only once. -- * 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). -- * 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, -- For example,
-- --
-- mytimer:Start(3) -- Will call the function once after 3 seconds. -- 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, 0.5) -- Will call right now and then every 0.5 sec until all eternity.
-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec. -- 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. -- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway.
-- --
-- ## Stopping the Timer -- ## Stopping the Timer
-- --
-- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function -- The timer can be stopped manually by the @{#TIMER.Stop}(*Delay*) function
-- --
-- mytimer:Stop() -- mytimer:Stop()
-- --
@@ -105,19 +105,17 @@ TIMER = {
--- Timer ID. --- Timer ID.
_TIMERID=0 _TIMERID=0
--- Timer data base.
--_TIMERDB={}
--- TIMER class version. --- TIMER class version.
-- @field #string version -- @field #string version
TIMER.version="0.1.0" TIMER.version="0.1.2"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot. -- TODO: Randomization.
-- TODO: Write docs. -- TODO: Pause/unpause.
-- DONE: Write docs.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor -- Constructor
@@ -142,6 +140,9 @@ function TIMER:New(Function, ...)
-- Number of function calls. -- Number of function calls.
self.ncalls=0 self.ncalls=0
-- Not running yet.
self.isrunning=false
-- Increase counter -- Increase counter
_TIMERID=_TIMERID+1 _TIMERID=_TIMERID+1
@@ -151,9 +152,6 @@ function TIMER:New(Function, ...)
-- Log id. -- Log id.
self.lid=string.format("TIMER UID=%d | ", self.uid) self.lid=string.format("TIMER UID=%d | ", self.uid)
-- Add to DB.
--_TIMERDB[self.uid]=self
return self return self
end end
@@ -169,7 +167,7 @@ function TIMER:Start(Tstart, dT, Duration)
local Tnow=timer.getTime() local Tnow=timer.getTime()
-- Start time in sec. -- Start time in sec.
self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil self.Tstart=Tstart and Tnow+math.max(Tstart, 0.001) or Tnow+0.001 -- one millisecond delay if Tstart=nil
-- Set time interval. -- Set time interval.
self.dT=dT self.dT=dT
@@ -185,6 +183,9 @@ function TIMER:Start(Tstart, dT, Duration)
-- Set log id. -- Set log id.
self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid) self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid)
-- Is now running.
self.isrunning=true
-- Debug info. -- 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))) self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop)))
@@ -209,9 +210,9 @@ function TIMER:Stop(Delay)
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
timer.removeFunction(self.tid) timer.removeFunction(self.tid)
-- Remove DB entry. -- Not running any more.
--_TIMERDB[self.uid]=nil self.isrunning=false
end end
end end
@@ -228,6 +229,22 @@ function TIMER:SetMaxFunctionCalls(Nmax)
return self return self
end end
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
-- @param #TIMER self
-- @param #number dT Time interval in seconds.
-- @return #TIMER self
function TIMER:SetTimeInterval(dT)
self.dT=dT
return self
end
--- Check if the timer has been started and was not stopped.
-- @param #TIMER self
-- @return #boolean If `true`, the timer is running.
function TIMER:IsRunning()
return self.isrunning
end
--- Call timer function. --- Call timer function.
-- @param #TIMER self -- @param #TIMER self
-- @param #number time DCS model time in seconds. -- @param #number time DCS model time in seconds.

File diff suppressed because it is too large Load Diff

View File

@@ -295,6 +295,17 @@ do -- country
-- @field QATAR -- @field QATAR
-- @field OMAN -- @field OMAN
-- @field UNITED_ARAB_EMIRATES -- @field UNITED_ARAB_EMIRATES
-- @field SOUTH_AFRICA
-- @field CUBA
-- @field PORTUGAL
-- @field GDR
-- @field LEBANON
-- @field CJTF_BLUE
-- @field CJTF_RED
-- @field UN_PEACEKEEPERS
-- @field Argentinia
-- @field Cyprus
-- @field Slovenia
country = {} --#country country = {} --#country
@@ -460,6 +471,22 @@ do -- Types
--@field #boolean lateActivated --@field #boolean lateActivated
--@field #boolean uncontrolled --@field #boolean uncontrolled
--- DCS template data structure.
-- @type Template
-- @field #boolean uncontrolled Aircraft is uncontrolled.
-- @field #boolean lateActivation Group is late activated.
-- @field #number x 2D Position on x-axis in meters.
-- @field #number y 2D Position on y-axis in meters.
-- @field #table units Unit list.
--
--- Unit data structure.
--@type Template.Unit
--@field #string name Name of the unit.
--@field #number x
--@field #number y
--@field #number alt
end -- end --
@@ -476,8 +503,9 @@ do -- Object
-- @field UNIT -- @field UNIT
-- @field WEAPON -- @field WEAPON
-- @field STATIC -- @field STATIC
-- @field SCENERY
-- @field BASE -- @field BASE
-- @field SCENERY
-- @field CARGO
--- @type Object.Desc --- @type Object.Desc
-- @extends #Desc -- @extends #Desc
@@ -487,6 +515,10 @@ do -- Object
--- @function [parent=#Object] isExist --- @function [parent=#Object] isExist
-- @param #Object self -- @param #Object self
-- @return #boolean -- @return #boolean
--- @function [parent=#Object] isActive
-- @param #Object self
-- @return #boolean
--- @function [parent=#Object] destroy --- @function [parent=#Object] destroy
-- @param #Object self -- @param #Object self
@@ -736,7 +768,66 @@ do -- Airbase
end -- Airbase end -- Airbase
do -- Spot
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
-- Represents a spot from laser or IR-pointer.
-- @type Spot
-- @field #Spot.Category Category enum that stores spot categories.
--- Enum that stores spot categories.
-- @type Spot.Category
-- @field #string INFRA_RED
-- @field #string LASER
--- Creates a laser ray emanating from the given object to a point in 3d space.
-- @function [parent=#Spot] createLaser
-- @param DCS#Object Source The source object of the laser.
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
-- @param #number LaserCode Any 4 digit number between 1111 and 1788.
-- @return #Spot
--- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles.
-- @function [parent=#Spot] createInfraRed
-- @param DCS#Object Source Source position of the IR ray.
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
-- @return #Spot
--- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin.
-- @function [parent=#Spot] getPoint
-- @param #Spot self
-- @return DCS#Vec3 Point in 3D, where the beam is pointing at.
--- Sets the destination point from which the source of the spot is drawn toward.
-- @function [parent=#Spot] setPoint
-- @param #Spot self
-- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at.
--- Returns the number that is used to define the laser code for which laser designation can track.
-- @function [parent=#Spot] getCode
-- @param #Spot self
-- @return #number Code The laser code used.
--- Sets the number that is used to define the laser code for which laser designation can track.
-- @function [parent=#Spot] setCode
-- @param #Spot self
-- @param #number Code The laser code. Default value is 1688.
--- Destroys the spot.
-- @function [parent=#Spot] destroy
-- @param #Spot self
--- Gets the category of the spot (laser or IR).
-- @function [parent=#Spot] getCategory
-- @param #Spot self
-- @return #string Category.
Spot = {} --#Spot
end -- Spot
do -- Controller do -- Controller
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. --- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
@@ -1060,6 +1151,11 @@ do -- Unit
-- @function [parent=#Unit] getAmmo -- @function [parent=#Unit] getAmmo
-- @param #Unit self -- @param #Unit self
-- @return #Unit.Ammo -- @return #Unit.Ammo
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
-- @function [parent=#Unit] getDescentCapacity
-- @param #Unit self
-- @return #number Number of soldiers that embark.
--- Returns the unit sensors. --- Returns the unit sensors.
-- @function [parent=#Unit] getSensors -- @function [parent=#Unit] getSensors
@@ -1093,6 +1189,10 @@ do -- Unit
-- @param #Unit self -- @param #Unit self
-- @return #Unit.Desc -- @return #Unit.Desc
--- GROUND - Switch on/off radar emissions
-- @function [parent=#Unit] enableEmission
-- @param #Unit self
-- @param #boolean switch
Unit = {} --#Unit Unit = {} --#Unit
@@ -1163,7 +1263,7 @@ do -- Group
-- @param #Group self -- @param #Group self
-- @return #number -- @return #number
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function. --- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function.
-- @function [parent=#Group] getInitialSize -- @function [parent=#Group] getInitialSize
-- @param #Group self -- @param #Group self
-- @return #number -- @return #number
@@ -1178,6 +1278,11 @@ do -- Group
-- @param #Group self -- @param #Group self
-- @return #Controller -- @return #Controller
--- GROUND - Switch on/off radar emissions
-- @function [parent=#Group] enableEmission
-- @param #Group self
-- @param #boolean switch
Group = {} --#Group Group = {} --#Group
end -- Group end -- Group
@@ -1269,7 +1374,9 @@ do -- AI
--- @type AI.Option.Ground --- @type AI.Option.Ground
-- @field #AI.Option.Ground.id id -- @field #AI.Option.Ground.id id
-- @field #AI.Option.Ground.val val -- @field #AI.Option.Ground.val val
-- @field #AI.Option.Ground.mid mid
-- @field #AI.Option.Ground.mval mval
--
--- @type AI.Option.Naval --- @type AI.Option.Naval
-- @field #AI.Option.Naval.id id -- @field #AI.Option.Naval.id id
-- @field #AI.Option.Naval.val val -- @field #AI.Option.Naval.val val
@@ -1292,6 +1399,11 @@ do -- AI
-- @field PROHIBIT_AG -- @field PROHIBIT_AG
-- @field MISSILE_ATTACK -- @field MISSILE_ATTACK
-- @field PROHIBIT_WP_PASS_REPORT -- @field PROHIBIT_WP_PASS_REPORT
-- @field OPTION_RADIO_USAGE_CONTACT
-- @field OPTION_RADIO_USAGE_ENGAGE
-- @field OPTION_RADIO_USAGE_KILL
-- @field JETT_TANKS_IF_EMPTY
-- @field FORCED_ATTACK
--- @type AI.Option.Air.id.FORMATION --- @type AI.Option.Air.id.FORMATION
-- @field LINE_ABREAST -- @field LINE_ABREAST
@@ -1361,19 +1473,35 @@ do -- AI
--- @type AI.Option.Ground.id --- @type AI.Option.Ground.id
-- @field NO_OPTION -- @field NO_OPTION
-- @field ROE @{#AI.Option.Ground.val.ROE} -- @field ROE @{#AI.Option.Ground.val.ROE}
-- @field FORMATION
-- @field DISPERSE_ON_ATTACK true or false -- @field DISPERSE_ON_ATTACK true or false
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE} -- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
-- @field ENGAGE_AIR_WEAPONS -- @field ENGAGE_AIR_WEAPONS
-- @field AC_ENGAGEMENT_RANGE_RESTRICTION
--- @type AI.Option.Ground.mid -- Moose added
-- @field RESTRICT_AAA_MIN 27
-- @field RESTRICT_AAA_MAX 29
-- @field RESTRICT_TARGETS @{#AI.Option.Ground.mval.ENGAGE_TARGETS} 28
--- @type AI.Option.Ground.val --- @type AI.Option.Ground.val
-- @field #AI.Option.Ground.val.ROE ROE -- @field #AI.Option.Ground.val.ROE ROE
-- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE -- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE
-- @field #AI.Option.Ground.val.ENGAGE_TARGETS RESTRICT_TARGETS
--- @type AI.Option.Ground.val.ROE --- @type AI.Option.Ground.val.ROE
-- @field OPEN_FIRE -- @field OPEN_FIRE
-- @field RETURN_FIRE -- @field RETURN_FIRE
-- @field WEAPON_HOLD -- @field WEAPON_HOLD
--- @type AI.Option.Ground.mval -- Moose added
-- @field #AI.Option.Ground.mval.ENGAGE_TARGETS ENGAGE_TARGETS
--- @type AI.Option.Ground.mval.ENGAGE_TARGETS -- Moose added
-- @field ANY_TARGET -- 0
-- @field AIR_UNITS_ONLY -- 1
-- @field GROUND_UNITS_ONLY -- 2
--- @type AI.Option.Ground.val.ALARM_STATE --- @type AI.Option.Ground.val.ALARM_STATE
-- @field AUTO -- @field AUTO
-- @field GREEN -- @field GREEN
@@ -1393,4 +1521,4 @@ do -- AI
AI = {} --#AI AI = {} --#AI
end -- AI end -- AI

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@
-- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there. -- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there.
-- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false. -- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false.
-- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group. -- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group.
-- @field #boolean report Arty group sends messages about their current state or target to its coaliton. -- @field #boolean report Arty group sends messages about their current state or target to its coalition.
-- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells. -- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells.
-- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets. -- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets.
-- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles. -- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles.
@@ -693,7 +693,7 @@ ARTY.db={
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="1.1.7" ARTY.version="1.2.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1146,12 +1146,11 @@ end
-- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name. -- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name.
-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group. -- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group.
function ARTY:NewFromCargoGroup(cargogroup, alias) function ARTY:NewFromCargoGroup(cargogroup, alias)
BASE:F2({cargogroup=cargogroup, alias=alias})
if cargogroup then if cargogroup then
BASE:T(self.lid..string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName())) BASE:T(string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
else else
BASE:E(self.lid.."ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)") BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
return nil return nil
end end
@@ -2766,38 +2765,32 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
self:_EventFromTo("onafterStatus", Event, From, To) self:_EventFromTo("onafterStatus", Event, From, To)
-- Get ammo. -- Get ammo.
local ntot, nshells, nrockets, nmissiles=self:GetAmmo() local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
-- We have a cargo group ==> check if group was loaded into a carrier.
if self.iscargo and self.cargogroup then
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
-- Group is now InTransit state. Current target is canceled.
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
self:Loaded()
elseif self.cargogroup:IsUnLoaded() then
-- Group has been unloaded and is combat ready again.
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
self:UnLoaded()
end
end
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
self:I(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, ntot, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles)) self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
if self.Controllable and self.Controllable:IsAlive() then if self.Controllable and self.Controllable:IsAlive() then
-- We have a cargo group ==> check if group was loaded into a carrier.
if self.cargogroup then
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
-- Group is now InTransit state. Current target is canceled.
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
self:Loaded()
elseif self.cargogroup:IsUnLoaded() then
-- Group has been unloaded and is combat ready again.
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
self:UnLoaded()
end
end
-- Debug current status info. -- Debug current status info.
if self.Debug then if self.Debug then
self:_StatusReport() self:_StatusReport()
end end
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
if self:is("InTransit") then
self:__Status(-5)
return
end
-- Group on the move. -- Group on the move.
if self:is("Moving") then if self:is("Moving") then
self:T2(self.lid..string.format("%s: Moving", Controllable:GetName())) self:T2(self.lid..string.format("%s: Moving", Controllable:GetName()))
@@ -2884,7 +2877,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
end end
-- Get ammo. -- Get ammo.
local nammo, nshells, nrockets, nmissiles=self:GetAmmo() --local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
-- Check if we have a target in the queue for which weapons are still available. -- Check if we have a target in the queue for which weapons are still available.
local gotsome=false local gotsome=false
@@ -2914,9 +2907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
-- Call status again in ~10 sec. -- Call status again in ~10 sec.
self:__Status(self.StatusInterval) self:__Status(self.StatusInterval)
elseif self.iscargo then
-- We have a cargo group ==> check if group was loaded into a carrier.
if self.cargogroup and self.cargogroup:IsAlive() then
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
if self:is("InTransit") then
self:__Status(-5)
end
end
else else
self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname)) self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname))
end end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -4814,7 +4821,8 @@ function ARTY:_CheckShootingStarted()
end end
-- Check if we waited long enough and no shot was fired. -- Check if we waited long enough and no shot was fired.
if dt > self.WaitForShotTime and self.Nshots==0 then --if dt > self.WaitForShotTime and self.Nshots==0 then
if dt > self.WaitForShotTime and (self.Nshots==0 or self.currentTarget.nshells >= self.Nshots) then --https://github.com/FlightControl-Master/MOOSE/issues/1356
-- Debug info. -- Debug info.
self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name)) self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name))

View File

@@ -213,7 +213,7 @@ do -- DESIGNATE
-- In order to prevent an overflow of designations due to many Detected Targets, there is a -- In order to prevent an overflow of designations due to many Detected Targets, there is a
-- Maximum Designations scope that is set in the DesignationObject. -- Maximum Designations scope that is set in the DesignationObject.
-- --
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject. -- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations (target groups) put in scope of the DesignationObject.
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected. -- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
-- --
-- # 4. Laser codes -- # 4. Laser codes
@@ -562,7 +562,8 @@ do -- DESIGNATE
end end
--- Set the maximum amount of designations. --- Set the maximum amount of designations (target groups). This will put a limit on the amount of designations in scope.
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
-- @param #DESIGNATE self -- @param #DESIGNATE self
-- @param #number MaximumDesignations -- @param #number MaximumDesignations
-- @return #DESIGNATE -- @return #DESIGNATE
@@ -602,7 +603,7 @@ do -- DESIGNATE
end end
--- Set the maximum amount of markings FACs will do, per designated target group. --- Set the maximum amount of markings FACs will do, per designated target group. This will limit the number of parallelly marked units of a target group.
-- @param #DESIGNATE self -- @param #DESIGNATE self
-- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group. -- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group.
-- @return #DESIGNATE -- @return #DESIGNATE
@@ -911,8 +912,8 @@ do -- DESIGNATE
for DesignateIndex, Designating in pairs( self.Designating ) do for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = DetectedItems[DesignateIndex] local DetectedItem = DetectedItems[DesignateIndex]
if DetectedItem then if DetectedItem then
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " ) local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup, nil, true ):Text( ", " )
DetectedReport:Add( string.rep( "-", 140 ) ) DetectedReport:Add( string.rep( "-", 40 ) )
DetectedReport:Add( " - " .. Report ) DetectedReport:Add( " - " .. Report )
if string.find( Designating, "L" ) then if string.find( Designating, "L" ) then
DetectedReport:Add( " - " .. "Lasing Targets" ) DetectedReport:Add( " - " .. "Lasing Targets" )
@@ -1001,7 +1002,13 @@ do -- DESIGNATE
local ID = self.Detection:GetDetectedItemID( DetectedItem ) local ID = self.Detection:GetDetectedItemID( DetectedItem )
local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup ) local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup )
MenuText = string.format( "(%3s) %s", Designating, MenuText ) -- Use injected MenuName from TaskA2GDispatcher if using same Detection Object
if DetectedItem.DesignateMenuName then
MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName )
else
MenuText = string.format( "(%3s) %s", Designating, MenuText )
end
local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName ) local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
-- Build the Lasing menu. -- Build the Lasing menu.
@@ -1186,8 +1193,8 @@ do -- DESIGNATE
local MarkingCount = 0 local MarkingCount = 0
local MarkedTypes = {} local MarkedTypes = {}
local ReportTypes = REPORT:New() --local ReportTypes = REPORT:New()
local ReportLaserCodes = REPORT:New() --local ReportLaserCodes = REPORT:New()
TargetSetUnit:Flush( self ) TargetSetUnit:Flush( self )
@@ -1237,8 +1244,8 @@ do -- DESIGNATE
if not Recce then if not Recce then
self:F( "Lasing..." ) self:F( "Lasing..." )
self.RecceSet:Flush( self) --self.RecceSet:Flush( self)
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
@@ -1276,13 +1283,13 @@ do -- DESIGNATE
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function. -- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
MarkingCount = MarkingCount + 1 MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName() local TargetUnitType = TargetUnit:GetTypeName()
--RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
-- 5, self.AttackSet, DesignateName ) 10, self.AttackSet, DesignateName )
if not MarkedTypes[TargetUnitType] then if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType) --ReportTypes:Add(TargetUnitType)
end end
ReportLaserCodes:Add(RecceUnit.LaserCode) --ReportLaserCodes:Add(RecceUnit.LaserCode)
return return
end end
else else
@@ -1297,16 +1304,16 @@ do -- DESIGNATE
if Recce then if Recce then
Recce:LaseOff() Recce:LaseOff()
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName ) Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 10, self.AttackSet, self.DesignateName )
end end
else else
--MarkingCount = MarkingCount + 1 --MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName() local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType) --ReportTypes:Add(TargetUnitType)
end end
ReportLaserCodes:Add(RecceUnit.LaserCode) --ReportLaserCodes:Add(RecceUnit.LaserCode)
end end
end end
end end
@@ -1316,19 +1323,19 @@ do -- DESIGNATE
local TargetUnitType = TargetUnit:GetTypeName() local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType) --ReportTypes:Add(TargetUnitType)
end end
ReportLaserCodes:Add(Recce.LaserCode) --ReportLaserCodes:Add(Recce.LaserCode)
--Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet ) Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 10, self.AttackSet )
end end
end end
end end
end end
) )
local MarkedTypesText = ReportTypes:Text(', ') --local MarkedTypesText = ReportTypes:Text(', ')
local MarkedLaserCodesText = ReportLaserCodes:Text(', ') --local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName ) --self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested ) self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@
-- @module Functional.FOX -- @module Functional.FOX
-- @image Functional_FOX.png -- @image Functional_FOX.png
--- FOX class. --- FOX class.
-- @type FOX -- @type FOX
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
@@ -47,8 +46,7 @@
-- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec.
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec. -- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
-- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec. -- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec.
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec. -- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
-- @field #boolean
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Fox 3! --- Fox 3!
@@ -794,7 +792,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing) local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
-- Say notching headings. -- Say notching headings.
BASE:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon) self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
--TODO: ALERT or INFO depending on whether this is a direct target. --TODO: ALERT or INFO depending on whether this is a direct target.
--TODO: lauchalertall option. --TODO: lauchalertall option.
@@ -1116,6 +1114,13 @@ end
-- Event Functions -- Event Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- FOX event handler for event birth.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventPlayerEnterAircraft(EventData)
end
--- FOX event handler for event birth. --- FOX event handler for event birth.
-- @param #FOX self -- @param #FOX self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
@@ -1157,7 +1162,7 @@ function FOX:OnEventBirth(EventData)
-- Add F10 radio menu for player. -- Add F10 radio menu for player.
if not self.menudisabled then if not self.menudisabled then
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) self:ScheduleOnce(0.1, FOX._AddF10Commands, self, _unitname)
end end
-- Player data. -- Player data.
@@ -1707,8 +1712,8 @@ end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. --- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FOX self -- @param #FOX self
-- @param DCS#Weapon weapon The weapon. -- @param DCS#Weapon weapon The weapon.
-- @return #number Notching heading right, i.e. missile heading +90<EFBFBD> -- @return #number Notching heading right, i.e. missile heading +90°.
-- @return #number Notching heading left, i.e. missile heading -90<EFBFBD>. -- @return #number Notching heading left, i.e. missile heading -90°.
function FOX:_GetNotchingHeadings(weapon) function FOX:_GetNotchingHeadings(weapon)
if weapon then if weapon then
@@ -1813,4 +1818,4 @@ end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,28 @@
--- **Functional** -- Train missile defence and deflection. --- **Functional** -- Train missile defence and deflection.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes. -- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range <20> -- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
-- * Provide alerts when a missile would have killed your aircraft. -- * Provide alerts when a missile would have killed your aircraft.
-- * Provide alerts when the missile self destructs. -- * Provide alerts when the missile self destructs.
-- * Enable / Disable and Configure the Missile Trainer using the various menu options. -- * Enable / Disable and Configure the Missile Trainer using the various menu options.
-- --
-- === -- ===
-- --
-- ## Missions: -- ## Missions:
-- --
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer) -- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
-- --
-- === -- ===
-- --
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft. -- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
-- --
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players: -- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
-- --
-- * **Messages**: Menu to configure all messages. -- * **Messages**: Menu to configure all messages.
-- * **Messages On**: Show all messages. -- * **Messages On**: Show all messages.
-- * **Messages Off**: Disable all messages. -- * **Messages Off**: Disable all messages.
@@ -45,23 +45,23 @@
-- * **Range Off**: Disable range information when a missile is fired to a target. -- * **Range Off**: Disable range information when a missile is fired to a target.
-- * **Bearing On**: Shows bearing information when a missile is fired to a target. -- * **Bearing On**: Shows bearing information when a missile is fired to a target.
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target. -- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. -- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter. -- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter. -- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter. -- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter. -- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
-- --
-- === -- ===
-- --
-- ### Authors: **FlightControl** -- ### Authors: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class. -- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design. -- Danny has shared his ideas and together we made a design.
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! -- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
-- * **132nd Squadron**: Testing and optimizing the logic. -- * **132nd Squadron**: Testing and optimizing the logic.
-- --
-- === -- ===
-- --
-- @module Functional.MissileTrainer -- @module Functional.MissileTrainer
@@ -76,7 +76,7 @@
--- ---
-- --
-- # Constructor: -- # Constructor:
-- --
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method: -- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
-- --
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed. -- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
@@ -84,7 +84,7 @@
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those. -- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
-- --
-- # Initialization: -- # Initialization:
-- --
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods: -- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
-- --
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF. -- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
@@ -97,8 +97,8 @@
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF. -- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF. -- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu. -- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
-- --
-- @field #MISSILETRAINER -- @field #MISSILETRAINER
MISSILETRAINER = { MISSILETRAINER = {
ClassName = "MISSILETRAINER", ClassName = "MISSILETRAINER",
TrackingMissiles = {}, TrackingMissiles = {},
@@ -167,7 +167,7 @@ end
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed. -- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
-- @param #MISSILETRAINER self -- @param #MISSILETRAINER self
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player. -- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. -- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @return #MISSILETRAINER -- @return #MISSILETRAINER
function MISSILETRAINER:New( Distance, Briefing ) function MISSILETRAINER:New( Distance, Briefing )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self:F( "ForEach:" .. Client.UnitName ) -- self:F( "ForEach:" .. Client.UnitName )
-- Client:Alive( self._Alive, self ) -- Client:Alive( self._Alive, self )
-- end -- end
-- --
self.DBClients:ForEachClient( self.DBClients:ForEachClient(
function( Client ) function( Client )
self:F( "ForEach:" .. Client.UnitName ) self:F( "ForEach:" .. Client.UnitName )
Client:Alive( self._Alive, self ) Client:Alive( self._Alive, self )
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self.DB:ForEachClient( -- self.DB:ForEachClient(
-- --- @param Wrapper.Client#CLIENT Client -- --- @param Wrapper.Client#CLIENT Client
-- function( Client ) -- function( Client )
-- --
-- ... actions ... -- ... actions ...
-- --
-- end -- end
-- ) -- )
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
self.DetailsRangeOnOff = true self.DetailsRangeOnOff = true
self.DetailsBearingOnOff = true self.DetailsBearingOnOff = true
self.MenusOnOff = true self.MenusOnOff = true
self.TrackingMissiles = {} self.TrackingMissiles = {}
@@ -293,7 +293,7 @@ end
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update. -- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
-- @param #MISSILETRAINER self -- @param #MISSILETRAINER self
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. -- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @return #MISSILETRAINER self -- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency ) function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
self:F( TrackingFrequency ) self:F( TrackingFrequency )
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
if TrainerTargetDCSUnit then if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName ) self:T(TrainerTargetDCSUnitName )
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName ) local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
if Client then if Client then
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit ) local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit ) local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
local Message = MESSAGE:New( local Message = MESSAGE:New(
string.format( "%s launched a %s", string.format( "%s launched a %s",
TrainerSourceUnit:GetTypeName(), TrainerSourceUnit:GetTypeName(),
TrainerWeaponName TrainerWeaponName
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" ) ) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
if self.AlertsToAll then if self.AlertsToAll then
Message:ToAll() Message:ToAll()
else else
Message:ToClient( Client ) Message:ToClient( Client )
end end
end end
local ClientID = Client:GetID() local ClientID = Client:GetID()
self:T( ClientID ) self:T( ClientID )
local MissileData = {} local MissileData = {}
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
end end
-- ALERTS PART -- ALERTS PART
-- Loop for all Player Clients to check the alerts and deletion of missiles. -- Loop for all Player Clients to check the alerts and deletion of missiles.
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client local Client = ClientData.Client
if Client and Client:IsAlive() then if Client and Client:IsAlive() then
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID ) self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3() local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 + ( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2 ( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000 ) ^ 0.5 / 1000
if Distance <= self.Distance then if Distance <= self.Distance then
-- Hit alert -- Hit alert
TrainerWeapon:destroy() TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" ) self:T( "killed" )
local Message = MESSAGE:New( local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s", string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(), TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(), TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName() TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" ) ), 15, "Hit Alert" )
if self.AlertsToAll == true then if self.AlertsToAll == true then
Message:ToAll() Message:ToAll()
else else
Message:ToClient( Client ) Message:ToClient( Client )
end end
MissileData = nil MissileData = nil
table.remove( ClientData.MissileData, MissileDataID ) table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData) self:T(ClientData.MissileData)
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
TrainerWeaponTypeName, TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName() TrainerSourceUnit:GetTypeName()
), 5, "Tracking" ) ), 5, "Tracking" )
if self.AlertsToAll == true then if self.AlertsToAll == true then
Message:ToAll() Message:ToAll()
else else
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed. if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
-- TRACKING PART -- TRACKING PART
-- For the current client, the missile range and bearing details are displayed To the Player Client. -- For the current client, the missile range and bearing details are displayed To the Player Client.
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients. -- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- Main Player Client loop -- Main Player Client loop
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client local Client = ClientData.Client
--self:T2( { Client:GetName() } ) --self:T2( { Client:GetName() } )
ClientData.MessageToClient = "" ClientData.MessageToClient = ""
ClientData.MessageToAll = "" ClientData.MessageToAll = ""
-- Other Players Client loop -- Other Players Client loop
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
--self:T3( MissileDataID ) --self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
if ShowMessages == true then if ShowMessages == true then
local TrackingTo local TrackingTo
TrackingTo = string.format( " -> %s", TrackingTo = string.format( " -> %s",
TrainerWeaponTypeName TrainerWeaponTypeName
) )
if ClientDataID == TrackingDataID then if ClientDataID == TrackingDataID then
if ClientData.MessageToClient == "" then if ClientData.MessageToClient == "" then
ClientData.MessageToClient = "Missiles to You:\n" ClientData.MessageToClient = "Missiles to You:\n"
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
end end
end end
end end
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them. -- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client ) local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )

View File

@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
local T=position:GetTemperature() local T=position:GetTemperature()
-- Correct unit system. -- Correct unit system.
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T)) local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
if settings:IsMetric() then if settings:IsMetric() then
_T=string.format('%d°C', T) _T=string.format('%d°C', T)
end end
@@ -949,11 +949,14 @@ function PSEUDOATC:LocalAirports(GID, UID)
for _,airbase in pairs(airports) do for _,airbase in pairs(airports) do
local name=airbase:getName() local name=airbase:getName()
local q=AIRBASE:FindByName(name):GetCoordinate() local a=AIRBASE:FindByName(name)
local d=q:Get2DDistance(pos) if a then
local q=a:GetCoordinate()
local d=q:Get2DDistance(pos)
-- Add to table. -- Add to table.
table.insert(self.group[GID].player[UID].airports, {distance=d, name=name}) table.insert(self.group[GID].player[UID].airports, {distance=d, name=name})
end
end end
end end
@@ -1041,6 +1044,3 @@ function PSEUDOATC:_myname(unitname)
return string.format("%s (%s)", csign, pname) return string.format("%s (%s)", csign, pname)
end 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,201 +1,520 @@
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
-- -- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
--
-- === -- ===
-- --
-- ## Missions: -- ## Missions:
-- --
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
-- --
-- === -- ===
-- --
-- ### Authors: **FlightControl** -- ### Authors: **FlightControl**, **applevangelist**
-- --
-- Last Update: Feb 2022
--
-- === -- ===
-- --
-- @module Functional.Sead -- @module Functional.Sead
-- @image SEAD.JPG -- @image SEAD.JPG
--- @type SEAD ---
-- @type SEAD
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- Make SAM sites execute evasive and defensive behaviour when being fired upon.
-- --
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
-- period of time to stay defensive, before it takes evasive actions.
-- --
-- # Constructor: -- # Constructor:
-- --
-- Use the @{#SEAD.New}() constructor to create a new SEAD object. -- Use the @{#SEAD.New}() constructor to create a new SEAD object.
-- --
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
-- --
-- @field #SEAD -- @field #SEAD
SEAD = { SEAD = {
ClassName = "SEAD", ClassName = "SEAD",
TargetSkill = { TargetSkill = {
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } , Average = { Evade = 30, DelayOn = { 40, 60 } } ,
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } , Good = { Evade = 20, DelayOn = { 30, 50 } } ,
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , High = { Evade = 15, DelayOn = { 20, 40 } } ,
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } Excellent = { Evade = 10, DelayOn = { 10, 30 } }
}, },
SEADGroupPrefixes = {} SEADGroupPrefixes = {},
SuppressedGroups = {},
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
Padding = 10,
CallBack = nil,
UseCallBack = false,
debug = false,
} }
--- Missile enumerators
-- @field Harms
SEAD.Harms = {
["AGM_88"] = "AGM_88",
["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45",
["ALARM"] = "ALARM",
["LD-10"] = "LD-10",
["X_58"] = "X_58",
["X_28"] = "X_28",
["X_25"] = "X_25",
["X_31"] = "X_31",
["Kh25"] = "Kh25",
["BGM_109"] = "BGM_109",
["AGM_154"] = "AGM_154",
["HY-2"] = "HY-2",
}
--- Missile enumerators - from DCS ME and Wikipedia
-- @field HarmData
SEAD.HarmData = {
-- km and mach
["AGM_88"] = { 150, 3},
["AGM_45"] = { 12, 2},
["AGM_122"] = { 16.5, 2.3},
["AGM_84"] = { 280, 0.8},
["ALARM"] = { 45, 2},
["LD-10"] = { 60, 4},
["X_58"] = { 70, 4},
["X_28"] = { 80, 2.5},
["X_25"] = { 25, 0.76},
["X_31"] = {150, 3},
["Kh25"] = {25, 0.8},
["BGM_109"] = {460, 0.705}, --in-game ~465kn
["AGM_154"] = {130, 0.61},
["HY-2"] = {90,1},
}
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
-- Chances are big that the missile will miss. -- Chances are big that the missile will miss.
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken. -- @param #SEAD self
-- @return SEAD -- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
-- @return #SEAD self
-- @usage -- @usage
-- -- CCCP SEAD Defenses -- -- CCCP SEAD Defenses
-- -- Defends the Russian SA installations from SEAD attacks. -- -- 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' } ) -- 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 ) function SEAD:New( SEADGroupPrefixes, Padding )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, FSM:New() )
self:F( SEADGroupPrefixes ) self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
end end
else else
self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end end
self:HandleEvent( EVENTS.Shot ) local padding = Padding or 10
if padding < 10 then padding = 10 end
return self self.Padding = padding
self.UseEmissionsOnOff = true
self.debug = false
self.CallBack = nil
self.UseCallBack = false
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
-- Start State.
self:SetStartState("Running")
self:AddTransition("*", "ManageEvasion", "*")
self:AddTransition("*", "CalculateHitZone", "*")
self:I("*** SEAD - Started Version 0.4.3")
return self
end end
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- Update the active SEAD Set (while running)
-- @see SEAD -- @param #SEAD self
-- @param #SEAD -- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
-- @return #SEAD self
function SEAD:UpdateSet( SEADGroupPrefixes )
self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end
return self
end
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
-- @param #SEAD self
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
-- @return #SEAD self
function SEAD:SetEngagementRange(range)
self:T( { range } )
range = range or 75
if range < 0 or range > 100 then
range = 75
end
self.EngagementRange = range
self:T(string.format("*** SEAD - Engagement range set to %s",range))
return self
end
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
-- @param #SEAD self
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
-- @return #SEAD self
function SEAD:SetPadding(Padding)
self:T( { Padding } )
local padding = Padding or 10
if padding < 10 then padding = 10 end
self.Padding = padding
return self
end
--- Set SEAD to use emissions on/off in addition to alarm state.
-- @param #SEAD self
-- @param #boolean Switch True for on, false for off.
-- @return #SEAD self
function SEAD:SwitchEmissions(Switch)
self:T({Switch})
self.UseEmissionsOnOff = Switch
return self
end
--- Add an object to call back when going evasive.
-- @param #SEAD self
-- @param #table Object The object to call. Needs to have object functions as follows:
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
-- `:SeadSuppressionStart(Group, Name)`,
-- `:SeadSuppressionEnd(Group, Name)`,
-- @return #SEAD self
function SEAD:AddCallBack(Object)
self:T({Class=Object.ClassName})
self.CallBack = Object
self.UseCallBack = true
return self
end
--- (Internal) Check if a known HARM was fired
-- @param #SEAD self
-- @param #string WeaponName
-- @return #boolean Returns true for a match
-- @return #string name Name of hit in table
function SEAD:_CheckHarms(WeaponName)
self:T( { WeaponName } )
local hit = false
local name = ""
for _,_name in pairs (SEAD.Harms) do
if string.find(WeaponName,_name,1,true) then
hit = true
name = _name
break
end
end
return hit, name
end
--- (Internal) Return distance in meters between two coordinates or -1 on error.
-- @param #SEAD self
-- @param Core.Point#COORDINATE _point1 Coordinate one
-- @param Core.Point#COORDINATE _point2 Coordinate two
-- @return #number Distance in meters
function SEAD:_GetDistance(_point1, _point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2)
--self:T({dist1=distance1, dist2=distance2})
if distance1 and type(distance1) == "number" then
return distance1
elseif distance2 and type(distance2) == "number" then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
end
--- (Internal) Calculate hit zone of an AGM-88
-- @param #SEAD self
-- @param #table SEADWeapon DCS.Weapon object
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
-- @param #number height Height when the missile was fired
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
-- @param #string SEADWeaponName Weapon Name
-- @return #SEAD self
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None"))
if SEADWeapon and SEADWeapon:isExist() then
--local pos = SEADWeapon:getPoint()
-- postion and height
local position = SEADWeapon:getPosition()
local mheight = height
-- heading
local wph = math.atan2(position.x.z, position.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
-- velocity
local wpndata = SEAD.HarmData["AGM_88"]
if string.find(SEADWeaponName,"154",1) then
wpndata = SEAD.HarmData["AGM_154"]
end
local mveloc = math.floor(wpndata[2] * 340.29)
local c1 = (2*mheight*9.81)/(mveloc^2)
local c2 = (mveloc^2) / 9.81
local Ropt = c2 * math.sqrt(c1+1)
if height <= 5000 then
Ropt = Ropt * 0.72
elseif height <= 7500 then
Ropt = Ropt * 0.82
elseif height <= 10000 then
Ropt = Ropt * 0.87
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
-- look at a couple of zones across the trajectory
for n=1,3 do
local dist = Ropt - ((n-1)*20000)
local predpos= pos0:Translate(dist,wph)
if predpos then
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtcoord = targetzone:GetRandomPointVec2()
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtgrp = seadset:GetRandom()
local _targetgroup = nil
local _targetgroupname = "none"
local _targetskill = "Random"
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
end
--end
end
end
end
return self
end
--- (Internal) Handle Evasion
-- @param #SEAD self
-- @param #string _targetskill
-- @param Wrapper.Group#GROUP _targetgroup
-- @param Core.Point#COORDINATE SEADPlanePos
-- @param #string SEADWeaponName
-- @param Wrapper.Group#GROUP SEADGroup Attacker Group
-- @param #number timeoffset Offset for tti calc
-- @return #SEAD self
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
local timeoffset = timeoffset or 0
if _targetskill == "Random" then -- when skill is random, choose a skill
local Skills = { "Average", "Good", "High", "Excellent" }
_targetskill = Skills[ math.random(1,4) ]
end
--self:T( _targetskill )
if self.TargetSkill[_targetskill] then
local _evade = math.random (1,100) -- random number for chance of evading action
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T("*** SEAD - Evading")
-- calculate distance of attacker
local _targetpos = _targetgroup:GetCoordinate()
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
-- weapon speed
local hit, data = self:_CheckHarms(SEADWeaponName)
local wpnspeed = 666 -- ;)
local reach = 10
if hit then
local wpndata = SEAD.HarmData[data]
reach = wpndata[1] * 1,1
local mach = wpndata[2]
wpnspeed = math.floor(mach * 340.29)
end
-- time to impact
local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time
if _distance > 0 then
_distance = math.floor(_distance / 1000) -- km
else
_distance = 0
end
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
if reach >= _distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
local name = args[2] -- #string Group Name
local attacker = args[3] -- Wrapper.Group#GROUP
if self.UseEmissionsOnOff then
grp:EnableEmission(false)
end
grp:OptionAlarmStateGreen() -- needed else we cannot move around
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionStart(grp,name,attacker)
end
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
local name = args[2] -- #string Group Nam
if self.UseEmissionsOnOff then
grp:EnableEmission(true)
end
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[name] = false
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionEnd(grp,name)
end
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if delay > _tti then delay = delay / 2 end -- speed up
if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
local _targetgroupname = _targetgroup:GetName()
if not self.SuppressedGroups[_targetgroupname] then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname] = true
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup)
end
end
end
end
end
return self
end
--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
-- @param #SEAD self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SEAD:OnEventShot( EventData ) -- @return #SEAD self
self:F( { EventData } ) function SEAD:HandleEventShot( EventData )
self:T( { EventData.id } )
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
local SEADUnit = EventData.IniDCSUnit
local SEADUnitName = EventData.IniDCSUnitName
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
local SEADWeaponName = EventData.WeaponName -- return weapon type
local SEADUnit = EventData.IniDCSUnit self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
local SEADUnitName = EventData.IniDCSUnitName --self:T({ SEADWeapon })
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
local SEADWeaponName = EventData.WeaponName -- return weapon type if self:_CheckHarms(SEADWeaponName) then
-- Start of the 2nd loop self:T( '*** SEAD - Weapon Match' )
self:T( "Missile Launched = " .. SEADWeaponName ) local _targetskill = "Random"
local _targetgroupname = "none"
--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 local _target = EventData.Weapon:getTarget() -- Identify target
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired if not _target or self.debug then -- AGM-88 or 154 w/o target data
or self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
or self:I("**** Tracking AGM-88/154 with no target data.")
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired local pos0 = SEADPlane:GetCoordinate()
or local fheight = SEADPlane:GetHeight()
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
or end
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired return self
or end
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired local targetcat = _target:getCategory() -- Identify category
or local _targetUnit = nil -- Wrapper.Unit#UNIT
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired local _targetgroup = nil -- Wrapper.Group#GROUP
or self:T(string.format("*** Targetcat = %d",targetcat))
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired if targetcat == Object.Category.UNIT then -- UNIT
or self:T("*** Target Category UNIT")
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
or if _targetUnit and _targetUnit:IsAlive() then
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired _targetgroup = _targetUnit:GetGroup()
then _targetgroupname = _targetgroup:GetName() -- group name
local _targetUnitName = _targetUnit:GetName()
local _evade = math.random (1,100) -- random number for chance of evading action _targetUnit:GetSkill()
local _targetMim = EventData.Weapon:getTarget() -- Identify target _targetskill = _targetUnit:GetSkill()
local _targetMimname = Unit.getName(_targetMim) end
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) elseif targetcat == Object.Category.STATIC then
local _targetMimgroupName = _targetMimgroup:getName() self:T("*** Target Category STATIC")
local _targetMimcont= _targetMimgroup:getController() local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill local targetpoint = _target:getPoint() or {x=0,y=0,z=0}
self:T( self.SEADGroupPrefixes ) local tgtcoord = COORDINATE:NewFromVec3(targetpoint)
self:T( _targetMimgroupName ) local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local SEADGroupFound = false if tgtgrp and tgtgrp:IsAlive() then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do _targetgroup = tgtgrp
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then _targetgroupname = tgtgrp:GetName() -- group name
SEADGroupFound = true _targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T( 'Group Found' ) self:T("*** Found Target = ".. _targetgroupname)
break end
end end
end -- see if we are shot at
if SEADGroupFound == true then local SEADGroupFound = false
if _targetskill == "Random" then -- when skill is random, choose a skill for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
local Skills = { "Average", "Good", "High", "Excellent" } self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix )
_targetskill = Skills[ math.random(1,4) ] if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then
end SEADGroupFound = true
self:T( _targetskill ) self:T( '*** SEAD - Group Match Found' )
if self.TargetSkill[_targetskill] then break
if (_evade > self.TargetSkill[_targetskill].Evade) then end
end
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) if SEADGroupFound == true then -- yes we are being attacked
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
local _targetMim = Weapon.getTarget(SEADWeapon) end
local _targetMimname = Unit.getName(_targetMim) end
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) return self
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
end
end
end
end
end end

View File

@@ -0,0 +1,667 @@
--- **Functional** -- Short Range Air Defense System
--
-- ===
--
-- **SHORAD** - Short Range Air Defense System
-- Controls a network of short range air/missile defense groups.
--
-- ===
--
-- ## Missions:
--
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
--
-- ===
--
-- ### Author : **applevangelist **
--
-- @module Functional.Shorad
-- @image Functional.Shorad.jpg
--
-- Date: Nov 2021
-------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE
-- @type SHORAD
-- @field #string ClassName
-- @field #string name Name of this Shorad
-- @field #boolean debug Set the debug state
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
-- @field #number Radius Shorad defense radius in meters
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
-- @field #string Coalition The coalition of this Shorad
-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds
-- @field #table ActiveGroups Table for the timer function
-- @field #string lid The log ID for the dcs.log
-- @field #boolean DefendHarms Default true, intercept incoming HARMS
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
-- @field #number DefenseLowProb Default 70, minimum detection limit
-- @field #number DefenseHighProb Default 90, maximim detection limit
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
-- @extends Core.Base#BASE
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
--
-- Simple Class for a more intelligent Short Range Air Defense System
--
-- #SHORAD
-- Moose derived missile intercepting short range defense system.
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup.
--
-- ## Usage
--
-- Set up a #SET_GROUP for the SAM sites to be protected:
--
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
--
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
--
-- ### Start a new SHORAD system, parameters are:
--
-- * Name: Name of this SHORAD.
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
-- * Samset: The #SET_GROUP of SAM sites to defend.
-- * Radius: Defense radius in meters.
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
--
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
--
-- ## Customize options
--
-- * SHORAD:SwitchDebug(debug)
-- * SHORAD:SwitchHARMDefense(onoff)
-- * SHORAD:SwitchAGMDefense(onoff)
-- * SHORAD:SetDefenseLimits(low,high)
-- * SHORAD:SetActiveTimer(seconds)
-- * SHORAD:SetDefenseRadius(meters)
--
-- @field #SHORAD
SHORAD = {
ClassName = "SHORAD",
name = "MyShorad",
debug = false,
Prefixes = "",
Radius = 20000,
Groupset = nil,
Samset = nil,
Coalition = nil,
ActiveTimer = 600, --stay on 10 mins
ActiveGroups = {},
lid = "",
DefendHarms = true,
DefendMavs = true,
DefenseLowProb = 70,
DefenseHighProb = 90,
UseEmOnOff = false,
}
-----------------------------------------------------------------------
-- SHORAD System
-----------------------------------------------------------------------
do
-- TODO Complete list?
--- Missile enumerators
-- @field Harms
SHORAD.Harms = {
["AGM_88"] = "AGM_88",
["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45",
["ALARM"] = "ALARM",
["LD-10"] = "LD-10",
["X_58"] = "X_58",
["X_28"] = "X_28",
["X_25"] = "X_25",
["X_31"] = "X_31",
["Kh25"] = "Kh25",
}
--- TODO complete list?
-- @field Mavs
SHORAD.Mavs = {
["AGM"] = "AGM",
["C-701"] = "C-701",
["Kh25"] = "Kh25",
["Kh29"] = "Kh29",
["Kh31"] = "Kh31",
["Kh66"] = "Kh66",
--["BGM_109"] = "BGM_109",
}
--- Instantiates a new SHORAD object
-- @param #SHORAD self
-- @param #string Name Name of this SHORAD
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
-- @retunr #SHORAD self
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
local self = BASE:Inherit( self, FSM:New() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
self.name = Name or "MyShorad"
self.Prefixes = ShoradPrefix or "SAM SHORAD"
self.Radius = Radius or 20000
self.Coalition = Coalition or "blue"
self.Samset = Samset or GroupSet
self.ActiveTimer = ActiveTimer or 600
self.ActiveGroups = {}
self.Groupset = GroupSet
self.DefendHarms = true
self.DefendMavs = true
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
self:I("*** SHORAD - Started Version 0.3.1")
-- Set the string id for output to DCS.log file.
self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState()
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
-- Start State.
self:SetStartState("Running")
self:AddTransition("*", "WakeUpShorad", "*")
self:AddTransition("*", "CalculateHitZone", "*")
return self
end
--- Initially set all groups to alarm state GREEN
-- @param #SHORAD self
function SHORAD:_InitState()
self:T(self.lid .. " _InitState")
local table = {}
local set = self.Groupset
self:T({set = set})
local aliveset = set:GetAliveSet() --#table
for _,_group in pairs (aliveset) do
if self.UseEmOnOff then
--_group:SetAIOff()
_group:EnableEmission(false)
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
else
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
end
_group:OptionDisperseOnAttack(30)
end
-- gather entropy
for i=1,100 do
math.random()
end
return self
end
--- Switch debug state on
-- @param #SHORAD self
-- @param #boolean debug Switch debug on (true) or off (false)
function SHORAD:SwitchDebug(onoff)
self:T( { onoff } )
if onoff then
self:SwitchDebugOn()
else
self.SwitchDebugOff()
end
return self
end
--- Switch debug state on
-- @param #SHORAD self
function SHORAD:SwitchDebugOn()
self.debug = true
--tracing
BASE:TraceOn()
BASE:TraceClass("SHORAD")
return self
end
--- Switch debug state off
-- @param #SHORAD self
function SHORAD:SwitchDebugOff()
self.debug = false
BASE:TraceOff()
return self
end
--- Switch defense for HARMs
-- @param #SHORAD self
-- @param #boolean onoff
function SHORAD:SwitchHARMDefense(onoff)
self:T( { onoff } )
local onoff = onoff or true
self.DefendHarms = onoff
return self
end
--- Switch defense for AGMs
-- @param #SHORAD self
-- @param #boolean onoff
function SHORAD:SwitchAGMDefense(onoff)
self:T( { onoff } )
local onoff = onoff or true
self.DefendMavs = onoff
return self
end
--- Set defense probability limits
-- @param #SHORAD self
-- @param #number low Minimum detection limit, integer 1-100
-- @param #number high Maximum detection limit integer 1-100
function SHORAD:SetDefenseLimits(low,high)
self:T( { low, high } )
local low = low or 70
local high = high or 90
if (low < 0) or (low > 100) or (low > high) then
low = 70
end
if (high < 0) or (high > 100) or (high < low ) then
high = 90
end
self.DefenseLowProb = low
self.DefenseHighProb = high
return self
end
--- Set the number of seconds a SHORAD site will stay active
-- @param #SHORAD self
-- @param #number seconds Number of seconds systems stay active
function SHORAD:SetActiveTimer(seconds)
self:T(self.lid .. " SetActiveTimer")
local timer = seconds or 600
if timer < 0 then
timer = 600
end
self.ActiveTimer = timer
return self
end
--- Set the number of meters for the SHORAD defense zone
-- @param #SHORAD self
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
function SHORAD:SetDefenseRadius(meters)
self:T(self.lid .. " SetDefenseRadius")
local radius = meters or 20000
if radius < 0 then
radius = 20000
end
self.Radius = radius
return self
end
--- Set using Emission on/off instead of changing alarm state
-- @param #SHORAD self
-- @param #boolean switch Decide if we are changing alarm state or AI state
function SHORAD:SetUsingEmOnOff(switch)
self:T(self.lid .. " SetUsingEmOnOff")
self.UseEmOnOff = switch or false
return self
end
--- Check if a HARM was fired
-- @param #SHORAD self
-- @param #string WeaponName
-- @return #boolean Returns true for a match
function SHORAD:_CheckHarms(WeaponName)
self:T(self.lid .. " _CheckHarms")
self:T( { WeaponName } )
local hit = false
if self.DefendHarms then
for _,_name in pairs (SHORAD.Harms) do
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
end
--- Check if an AGM was fired
-- @param #SHORAD self
-- @param #string WeaponName
-- @return #boolean Returns true for a match
function SHORAD:_CheckMavs(WeaponName)
self:T(self.lid .. " _CheckMavs")
self:T( { WeaponName } )
local hit = false
if self.DefendMavs then
for _,_name in pairs (SHORAD.Mavs) do
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
end
--- Check the coalition of the attacker
-- @param #SHORAD self
-- @param #string Coalition name
-- @return #boolean Returns false for a match
function SHORAD:_CheckCoalition(Coalition)
self:T(self.lid .. " _CheckCoalition")
local owncoalition = self.Coalition
local othercoalition = ""
if Coalition == 0 then
othercoalition = "neutral"
elseif Coalition == 1 then
othercoalition = "red"
else
othercoalition = "blue"
end
self:T({owncoalition = owncoalition, othercoalition = othercoalition})
if owncoalition ~= othercoalition then
return true
else
return false
end
end
--- Check if the missile is aimed at a SHORAD
-- @param #SHORAD self
-- @param #string TargetGroupName Name of the target group
-- @return #boolean Returns true for a match, else false
function SHORAD:_CheckShotAtShorad(TargetGroupName)
self:T(self.lid .. " _CheckShotAtShorad")
local tgtgrp = TargetGroupName
local shorad = self.Groupset
local shoradset = shorad:GetAliveSet() --#table
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
end
end
return returnname
end
--- Check if the missile is aimed at a SAM site
-- @param #SHORAD self
-- @param #string TargetGroupName Name of the target group
-- @return #boolean Returns true for a match, else false
function SHORAD:_CheckShotAtSams(TargetGroupName)
self:T(self.lid .. " _CheckShotAtSams")
local tgtgrp = TargetGroupName
local shorad = self.Samset
--local shoradset = shorad:GetAliveSet() --#table
local shoradset = shorad:GetSet() --#table
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
end
end
return returnname
end
--- Calculate if the missile shot is detected
-- @param #SHORAD self
-- @return #boolean Returns true for a detection, else false
function SHORAD:_ShotIsDetected()
self:T(self.lid .. " _ShotIsDetected")
if self.debug then return true end
local IsDetected = false
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
local ActualDetection = math.random(1,100) -- value for this shot
if ActualDetection <= DetectionProb then
IsDetected = true
end
return IsDetected
end
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
-- @param #SHORAD self
-- @param #string TargetGroup Name of the target group used to build the #ZONE
-- @param #number Radius Radius of the #ZONE
-- @param #number ActiveTimer Number of seconds to stay active
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
-- @usage Use this function to integrate with other systems, example
--
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
-- myshorad:SwitchDebug(true)
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mymantis:AddShorad(myshorad,720)
-- mymantis:Start()
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
self:T(self.lid .. " WakeUpShorad")
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
local targetcat = TargetCat or Object.Category.UNIT
local targetgroup = TargetGroup
local targetvec2 = nil
if targetcat == Object.Category.UNIT then
targetvec2 = GROUP:FindByName(targetgroup):GetVec2()
elseif targetcat == Object.Category.STATIC then
targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2()
else
local samset = self.Samset
local sam = samset:GetRandom()
targetvec2 = sam:GetVec2()
end
local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check
local groupset = self.Groupset --Core.Set#SET_GROUP
local shoradset = groupset:GetAliveSet() --#table
-- local function to switch off shorad again
local function SleepShorad(group)
local groupname = group:GetName()
self.ActiveGroups[groupname] = nil
if self.UseEmOnOff then
group:EnableEmission(false)
--group:SetAIOff()
else
group:OptionAlarmStateGreen()
end
local text = string.format("Sleeping SHORAD %s", group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
end
-- go through set and find the one(s) to activate
for _,_group in pairs (shoradset) do
if _group:IsAnyInZone(targetzone) then
local text = string.format("Waking up SHORAD %s", _group:GetName())
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then
--_group:SetAIOn()
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
local groupname = _group:GetName()
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
timer.scheduleFunction(SleepShorad, _group, endtime)
end
end
end
return self
end
--- (Internal) Calculate hit zone of an AGM-88
-- @param #SHORAD self
-- @param #table SEADWeapon DCS.Weapon object
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
-- @param #number height Height when the missile was fired
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
-- @return #SHORAD self
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
self:T("**** Calculating hit zone")
if SEADWeapon and SEADWeapon:isExist() then
--local pos = SEADWeapon:getPoint()
-- postion and height
local position = SEADWeapon:getPosition()
local mheight = height
-- heading
local wph = math.atan2(position.x.z, position.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
-- velocity
local wpndata = SEAD.HarmData["AGM_88"]
local mveloc = math.floor(wpndata[2] * 340.29)
local c1 = (2*mheight*9.81)/(mveloc^2)
local c2 = (mveloc^2) / 9.81
local Ropt = c2 * math.sqrt(c1+1)
if height <= 5000 then
Ropt = Ropt * 0.72
elseif height <= 7500 then
Ropt = Ropt * 0.82
elseif height <= 10000 then
Ropt = Ropt * 0.87
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
-- look at a couple of zones across the trajectory
for n=1,3 do
local dist = Ropt - ((n-1)*20000)
local predpos= pos0:Translate(dist,wph)
if predpos then
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset = self.Groupset
local tgtcoord = targetzone:GetRandomPointVec2()
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local _targetgroup = nil
local _targetgroupname = "none"
local _targetskill = "Random"
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
end
end
end
end
return self
end
--- Main function - work on the EventData
-- @param #SHORAD self
-- @param Core.Event#EVENTDATA EventData The event details table data set
function SHORAD:HandleEventShot( EventData )
self:T( { EventData } )
self:T(self.lid .. " HandleEventShot")
--local ShootingUnit = EventData.IniDCSUnit
--local ShootingUnitName = EventData.IniDCSUnitName
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
local ShootingWeaponName = EventData.WeaponName -- return weapon type
-- get firing coalition
local weaponcoalition = EventData.IniGroup:GetCoalition()
-- get detection probability
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
local IsDetected = self:_ShotIsDetected()
-- convert to text
local DetectedText = "false"
if IsDetected then
DetectedText = "true"
end
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
self:T( text )
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
--
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
-- get target data
local targetdata = EventData.Weapon:getTarget() -- Identify target
-- Is there target data?
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true) then
self:I("**** Tracking AGM-88 with no target data.")
local pos0 = EventData.IniUnit:GetCoordinate()
local fheight = EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
end
return self
end
local targetcat = targetdata:getCategory() -- Identify category
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
self:T({targetdata})
local targetunit = nil
if targetcat == Object.Category.UNIT then -- UNIT
targetunit = UNIT:Find(targetdata)
elseif targetcat == Object.Category.STATIC then -- STATIC
--self:T("Static Target Data")
--self:T({targetdata:isExist()})
--self:T({targetdata:getPoint()})
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
--tgtcoord:MarkToAll("Missile Target",true)
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1 = tgtgrp1:GetCoordinate()
--tgtcoord1:MarkToAll("Close target SAM",true)
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2 = tgtgrp2:GetCoordinate()
--tgtcoord2:MarkToAll("Close target SHORAD",true)
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
else
targetunit = tgtgrp2
targetcat = Object.Category.UNIT
end
end
--local targetunitname = Unit.getName(targetdata) -- Unit name
if targetunit and targetunit:IsAlive() then
local targetunitname = targetunit:GetName()
--local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group
local targetgroup = nil
local targetgroupname = "none"
if targetcat == Object.Category.UNIT then
if targetunit.ClassName == "UNIT" then
targetgroup = targetunit:GetGroup()
elseif targetunit.ClassName == "GROUP" then
targetgroup = targetunit
end
targetgroupname = targetgroup:GetName() -- group name
elseif targetcat == Object.Category.STATIC then
targetgroup = targetunit
targetgroupname = targetunitname
end
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
self:T( text )
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
-- check if we or a SAM site are the target
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
-- if being shot at, find closest SHORADs to activate
if shotatsams or shotatus then
self:T({shotatsams=shotatsams,shotatus=shotatus})
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
end
end
end
end
return self
end
--
end
-----------------------------------------------------------------------
-- SHORAD end
-----------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
local UnitHit = EventData.TgtUnit local UnitHit = EventData.TgtUnit
if UnitHit.ClassName ~= "SCENERY" then
-- Check if unit is inside the capture zone and that it is of the defending coalition. -- 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 if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
-- Update last hit time. -- Update last hit time.
self.HitTimeLast=timer.getTime() self.HitTimeLast=timer.getTime()
-- Only trigger attacked event if not already in state "Attacked". -- Only trigger attacked event if not already in state "Attacked".
if self:GetState()~="Attacked" then if self:GetState()~="Attacked" then
self:F2("Hit ==> Attack") self:F2("Hit ==> Attack")
self:Attack() self:Attack()
end end
end
end end
end end
end end
@@ -803,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION
return IsEmpty return IsEmpty
end end
--- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone. --- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone.
-- @param #ZONE_CAPTURE_COALITION self -- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition ) -- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition )
function ZONE_CAPTURE_COALITION:IsGuarded() function ZONE_CAPTURE_COALITION:IsGuarded()
@@ -825,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION
return IsCaptured return IsCaptured
end end
--- Check if zone is "Attacked", i.e. another coaliton entered the zone. --- Check if zone is "Attacked", i.e. another coalition entered the zone.
-- @param #ZONE_CAPTURE_COALITION self -- @param #ZONE_CAPTURE_COALITION self
-- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition ) -- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_CAPTURE_COALITION:IsAttacked() function ZONE_CAPTURE_COALITION:IsAttacked()
@@ -890,30 +891,31 @@ do -- ZONE_CAPTURE_COALITION
end end
-- Status text. -- 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) if false then
local NewState = self:GetState() 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)
if NewState~=State then local NewState = self:GetState()
text=text..string.format(" --> %s", NewState) if NewState~=State then
text=text..string.format(" --> %s", NewState)
end
self:I(text)
end end
self:I(text)
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions -- Misc Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Update Mark on F10 map. --- Update Mark on F10 map.
-- @param #ZONE_CAPTURE_COALITION self -- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark() function ZONE_CAPTURE_COALITION:Mark()
if self.MarkOn then if self.MarkOn then
local Coord = self:GetCoordinate() local Coord = self:GetCoordinate()
local ZoneName = self:GetZoneName() local ZoneName = self:GetZoneName()
local State = self:GetState() local State = self:GetState()
-- Remove marks. -- Remove marks.
if self.MarkRed then if self.MarkRed then
Coord:RemoveMark(self.MarkRed) Coord:RemoveMark(self.MarkRed)
@@ -921,21 +923,21 @@ do -- ZONE_CAPTURE_COALITION
if self.MarkBlue then if self.MarkBlue then
Coord:RemoveMark(self.MarkBlue) Coord:RemoveMark(self.MarkBlue)
end end
-- Create new marks for each coaliton. -- Create new marks for each coalition.
if self.Coalition == coalition.side.BLUE then if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
elseif self.Coalition == coalition.side.RED then elseif self.Coalition == coalition.side.RED then
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
else else
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
end end
end end
end end
end end

View File

@@ -1,16 +1,16 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition. --- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
-- --
-- === -- ===
-- --
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. -- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized. -- Derived classes implement the ways how the achievements can be realized.
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- === -- ===
-- --
-- @module Functional.ZoneGoalCoalition -- @module Functional.ZoneGoalCoalition
-- @image MOOSE.JPG -- @image MOOSE.JPG
@@ -24,34 +24,33 @@ do -- ZoneGoal
-- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS. -- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS.
-- @extends Functional.ZoneGoal#ZONE_GOAL -- @extends Functional.ZoneGoal#ZONE_GOAL
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized. -- Derived classes implement the ways how the achievements can be realized.
-- --
-- ## 1. ZONE_GOAL_COALITION constructor -- ## 1. ZONE_GOAL_COALITION constructor
-- --
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object. -- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
-- --
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM). -- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
-- --
-- ### 2.1 ZONE_GOAL_COALITION States -- ### 2.1 ZONE_GOAL_COALITION States
-- --
-- ### 2.2 ZONE_GOAL_COALITION Events -- ### 2.2 ZONE_GOAL_COALITION Events
-- --
-- ### 2.3 ZONE_GOAL_COALITION State Machine -- ### 2.3 ZONE_GOAL_COALITION State Machine
-- --
-- @field #ZONE_GOAL_COALITION -- @field #ZONE_GOAL_COALITION
ZONE_GOAL_COALITION = { ZONE_GOAL_COALITION = {
ClassName = "ZONE_GOAL_COALITION", ClassName = "ZONE_GOAL_COALITION",
Coalition = nil, Coalition = nil,
PreviousCoaliton = nil, PreviousCoalition = nil,
UnitCategories = nil, UnitCategories = nil,
ObjectCategories = nil, ObjectCategories = nil,
} }
--- @field #table ZONE_GOAL_COALITION.States --- @field #table ZONE_GOAL_COALITION.States
ZONE_GOAL_COALITION.States = {} ZONE_GOAL_COALITION.States = {}
--- ZONE_GOAL_COALITION Constructor. --- ZONE_GOAL_COALITION Constructor.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved. -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
@@ -59,33 +58,32 @@ do -- ZoneGoal
-- @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 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 -- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories ) function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
if not Zone then if not Zone then
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!") BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" )
return nil return nil
end end
-- Inherit ZONE_GOAL. -- Inherit ZONE_GOAL.
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
self:F( { Zone = Zone, Coalition = Coalition } ) self:F( { Zone = Zone, Coalition = Coalition } )
-- Set initial owner. -- Set initial owner.
self:SetCoalition( Coalition or coalition.side.NEUTRAL) self:SetCoalition( Coalition or coalition.side.NEUTRAL )
-- Set default unit and object categories for the zone scan. -- Set default unit and object categories for the zone scan.
self:SetUnitCategories(UnitCategories) self:SetUnitCategories( UnitCategories )
self:SetObjectCategories() self:SetObjectCategories()
return self return self
end end
--- Set the owning coalition of the zone. --- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*. -- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*.
-- @return #ZONE_GOAL_COALITION -- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetCoalition( Coalition ) function ZONE_GOAL_COALITION:SetCoalition( Coalition )
self.PreviousCoalition=self.Coalition or Coalition self.PreviousCoalition = self.Coalition or Coalition
self.Coalition = Coalition self.Coalition = Coalition
return self return self
end end
@@ -95,31 +93,31 @@ do -- ZoneGoal
-- @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 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 -- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories ) function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories )
if UnitCategories and type(UnitCategories)~="table" then if UnitCategories and type( UnitCategories ) ~= "table" then
UnitCategories={UnitCategories} UnitCategories = { UnitCategories }
end end
self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT} self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT }
return self return self
end end
--- Set the owning coalition of the zone. --- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self -- @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. -- @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 -- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories ) function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories )
if ObjectCategories and type(ObjectCategories)~="table" then if ObjectCategories and type( ObjectCategories ) ~= "table" then
ObjectCategories={ObjectCategories} ObjectCategories = { ObjectCategories }
end end
self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC} self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC }
return self return self
end end
--- Get the owning coalition of the zone. --- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @return DCSCoalition.DCSCoalition#coalition Coalition. -- @return DCSCoalition.DCSCoalition#coalition Coalition.
@@ -127,39 +125,37 @@ do -- ZoneGoal
return self.Coalition return self.Coalition
end end
--- Get the previous coaliton, i.e. the one owning the zone before the current one. --- Get the previous coalition, i.e. the one owning the zone before the current one.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @return DCSCoalition.DCSCoalition#coalition Coalition. -- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_COALITION:GetPreviousCoalition() function ZONE_GOAL_COALITION:GetPreviousCoalition()
return self.PreviousCoalition return self.PreviousCoalition
end end
--- Get the owning coalition name of the zone. --- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @return #string Coalition name. -- @return #string Coalition name.
function ZONE_GOAL_COALITION:GetCoalitionName() function ZONE_GOAL_COALITION:GetCoalitionName()
return UTILS.GetCoalitionName(self.Coalition) return UTILS.GetCoalitionName( self.Coalition )
end end
--- Check status Coalition ownership. --- Check status Coalition ownership.
-- @param #ZONE_GOAL_COALITION self -- @param #ZONE_GOAL_COALITION self
-- @return #ZONE_GOAL_COALITION -- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:StatusZone() function ZONE_GOAL_COALITION:StatusZone()
-- Get current state. -- Get current state.
local State = self:GetState() local State = self:GetState()
-- Debug text. -- Debug text.
local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName()) local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() )
self:F(text) self:F( text )
-- Scan zone. -- Scan zone.
self:Scan( self.ObjectCategories, self.UnitCategories ) self:Scan( self.ObjectCategories, self.UnitCategories )
return self return self
end end
end end

View File

@@ -1,5 +1,4 @@
-- The order of the declarations is important here. Don't touch it. --- GLOBALS: The order of the declarations is important here. Don't touch it.
--- Declare the event dispatcher based on the EVENT class --- Declare the event dispatcher based on the EVENT class
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
@@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
--- Declare the main database object, which is used internally by the MOOSE classes. --- Declare the main database object, which is used internally by the MOOSE classes.
_DATABASE = DATABASE:New() -- Core.Database#DATABASE _DATABASE = DATABASE:New() -- Core.Database#DATABASE
--- Settings
_SETTINGS = SETTINGS:Set() _SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn() _SETTINGS:SetPlayerMenuOn()
--- Register cargos.
_DATABASE:_RegisterCargos() _DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()
--- Register zones.
_DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
--- Check if os etc is available.
BASE:I("Checking de-sanitization of os, io and lfs:")
local __na=false
if os then
BASE:I("- os available")
else
BASE:I("- os NOT available! Some functions may not work.")
__na=true
end
if io then
BASE:I("- io available")
else
BASE:I("- io NOT available! Some functions may not work.")
__na=true
end
if lfs then
BASE:I("- lfs available")
else
BASE:I("- lfs NOT available! Some functions may not work.")
__na=true
end
if __na then
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
end

View File

@@ -2,10 +2,16 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Socket.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.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/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' ) __Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
@@ -20,14 +26,13 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.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/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@@ -67,11 +72,15 @@ __Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' ) __Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
@@ -98,16 +107,25 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.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_Helicopter.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.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_APC.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.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/AI/AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Task.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

@@ -31,7 +31,7 @@
-- @field #string tankergroupname Name of the late activated tanker template group. -- @field #string tankergroupname Name of the late activated tanker template group.
-- @field Wrapper.Group#GROUP tanker Tanker group. -- @field Wrapper.Group#GROUP tanker Tanker group.
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier. -- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon. -- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
-- @field #number TACANchannel TACAN channel. Default 1. -- @field #number TACANchannel TACAN channel. Default 1.
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations! -- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR". -- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
@@ -784,10 +784,11 @@ end
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param #number channel TACAN channel. Default 1. -- @param #number channel TACAN channel. Default 1.
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR". -- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
-- @return #RECOVERYTANKER self -- @return #RECOVERYTANKER self
function RECOVERYTANKER:SetTACAN(channel, morse) function RECOVERYTANKER:SetTACAN(channel, morse, mode)
self.TACANchannel=channel or 1 self.TACANchannel=channel or 1
self.TACANmode="Y" self.TACANmode=mode or "Y"
self.TACANmorse=morse or "TKR" self.TACANmorse=morse or "TKR"
self.TACANon=true self.TACANon=true
return self return self
@@ -1625,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then if delay and delay>0 then
-- Schedule TACAN activation. -- Schedule TACAN activation.
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self) self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
else else

View File

@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
-- Debug. -- Debug.
local text=string.format("Unit %s crashed or ejected.", unitname) local text=string.format("Unit %s crashed or ejected.", unitname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text) self:T(self.lid..text)
-- Get coordinate of unit. -- Get coordinate of unit.
local coord=unit:GetCoordinate() local coord=unit:GetCoordinate()

View File

@@ -0,0 +1,394 @@
--- **Sound** - Radio transmissions.
--
-- ===
--
-- ## Features:
--
-- * Provide radio functionality to broadcast radio transmissions.
--
-- 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?
--
-- * 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 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 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 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.
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Sound.Radio
-- @image Core_Radio.JPG
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
--
-- # RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
-- * @{#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 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 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?
--
-- * 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 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 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 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",
FileName = "",
Frequency = 0,
Modulation = radio.modulation.AM,
Subtitle = "",
SubtitleDuration = 0,
Power = 100,
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.
-- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @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:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil
end
--- 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
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?", FileName})
return self
end
--- 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 Frequency in MHz.
-- @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 <= 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
local commandSetFrequency={
id = "SetFrequency",
params = {
frequency = self.Frequency,
modulation = self.Modulation,
}
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
--end
end
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
return self
end
--- Set AM or FM modulation of the radio transmitter.
-- @param #RADIO self
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
-- @return #RADIO self
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
if type(Modulation) == "number" then
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self.Modulation = Modulation
return self
end
end
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
return self
end
--- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self
-- @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
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
return self
end
--- Set message looping on or off.
-- @param #RADIO self
-- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop) == "boolean" then
self.Loop = Loop
return self
end
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
return self
end
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
-- @param #RADIO self
-- @param #string Subtitle
-- @param #number SubtitleDuration in s
-- @return #RADIO self
-- @usage
-- -- create the broadcaster and attaches it a RADIO
-- local MyUnit = UNIT:FindByName("MyUnit")
-- local MyUnitRadio = MyUnit:GetRadio()
--
-- -- add a subtitle for the next transmission, which will be up for 10s
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:F2({Subtitle, SubtitleDuration})
if type(Subtitle) == "string" then
self.Subtitle = Subtitle
else
self.Subtitle = ""
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end
if type(SubtitleDuration) == "number" then
self.SubtitleDuration = SubtitleDuration
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
-- 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 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})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Power then self:SetPower(Power) end
if Loop then self:SetLoop(Loop) end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @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)
-- 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
--- 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
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
-- * 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(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") 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: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
-- @return #RADIO self
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
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID))
end
return self
end

View File

@@ -1,20 +1,24 @@
--- **Core** - Queues Radio Transmissions. --- **Sound** - Queues Radio Transmissions.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Managed Radio Transmissions. -- * Manage Radio Transmissions
-- --
-- === -- ===
-- --
-- ### Authors: funkyfranky -- ### Authors: funkyfranky
-- --
-- @module Core.RadioQueue -- @module Sound.RadioQueue
-- @image Core_Radio.JPG -- @image Core_Radio.JPG
--- Manages radio transmissions. --- Manages radio transmissions.
-- --
-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence.
-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other.
-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file.
--
-- @type RADIOQUEUE -- @type RADIOQUEUE
-- @field #string ClassName Name of the class "RADIOQUEUE". -- @field #string ClassName Name of the class "RADIOQUEUE".
-- @field #boolean Debugmode Debug mode. More info. -- @field #boolean Debugmode Debug mode. More info.
@@ -35,6 +39,7 @@
-- @field #table numbers Table of number transmission parameters. -- @field #table numbers Table of number transmission parameters.
-- @field #boolean checking Scheduler is checking the radio queue. -- @field #boolean checking Scheduler is checking the radio queue.
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler. -- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
-- @field Sound.SRS#MSRS msrs Moose SRS class.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
RADIOQUEUE = { RADIOQUEUE = {
ClassName = "RADIOQUEUE", ClassName = "RADIOQUEUE",
@@ -69,12 +74,14 @@ RADIOQUEUE = {
-- @field #boolean isplaying If true, transmission is currently playing. -- @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 Tplay Mission time (abs) in seconds when the transmission should be played.
-- @field #number interval Interval in seconds before next transmission. -- @field #number interval Interval in seconds before next transmission.
-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS.
-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS.
--- Create a new RADIOQUEUE object for a given radio frequency/modulation. --- Create a new RADIOQUEUE object for a given radio frequency/modulation.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @param #number frequency The radio frequency in MHz. -- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. -- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0).
-- @param #string alias (Optional) Name of the radio queue. -- @param #string alias (Optional) Name of the radio queue.
-- @return #RADIOQUEUE self The RADIOQUEUE object. -- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation, alias) function RADIOQUEUE:New(frequency, modulation, alias)
@@ -125,9 +132,9 @@ function RADIOQUEUE:Start(delay, dt)
-- Start Scheduler. -- Start Scheduler.
if self.schedonce then if self.schedonce then
self:_CheckRadioQueueDelayed(delay) self:_CheckRadioQueueDelayed(self.delay)
else else
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt) self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt)
end end
return self return self
@@ -170,6 +177,17 @@ function RADIOQUEUE:SetRadioPower(power)
return self return self
end end
--- Set SRS.
-- @param #RADIOQUEUE self
-- @param #string PathToSRS Path to SRS.
-- @param #number Port SRS port. Default 5002.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSRS(PathToSRS, Port)
self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation)
self.msrs:SetPort(Port)
return self
end
--- Set parameters of a digit. --- Set parameters of a digit.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @param #number digit The digit 0-9. -- @param #number digit The digit 0-9.
@@ -202,7 +220,7 @@ end
--- Add a transmission to the radio queue. --- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission data table. -- @param #RADIOQUEUE.Transmission transmission The transmission data table.
-- @return #RADIOQUEUE self The RADIOQUEUE object. -- @return #RADIOQUEUE self
function RADIOQUEUE:AddTransmission(transmission) function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission}) self:F({transmission=transmission})
@@ -221,7 +239,7 @@ function RADIOQUEUE:AddTransmission(transmission)
return self return self
end end
--- Add a transmission to the radio queue. --- Create a new transmission and add it to the radio queue.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @param #string filename Name of the sound file. Usually an ogg or wav file type. -- @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 duration Duration in seconds the file lasts.
@@ -230,7 +248,7 @@ end
-- @param #number interval Interval in seconds after the last transmission finished. -- @param #number interval Interval in seconds after the last transmission finished.
-- @param #string subtitle Subtitle of the transmission. -- @param #string subtitle Subtitle of the transmission.
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. -- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object. -- @return #RADIOQUEUE.Transmission Radio transmission table.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
-- Sanity checks. -- Sanity checks.
@@ -269,9 +287,36 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval,
-- Add transmission to queue. -- Add transmission to queue.
self:AddTransmission(transmission) self:AddTransmission(transmission)
return transmission
end
--- Add a SOUNDFILE to the radio queue.
-- @param #RADIOQUEUE self
-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added.
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @return #RADIOQUEUE self
function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval)
--env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName()))
local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration)
transmission.soundfile=soundfile
return self return self
end end
--- Add a SOUNDTEXT to the radio queue.
-- @param #RADIOQUEUE self
-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text.
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @return #RADIOQUEUE self
function RADIOQUEUE:AddSoundText(soundtext, tstart, interval)
local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration)
transmission.soundtext=soundtext
return self
end
--- Convert a number (as string) into a radio transmission. --- Convert a number (as string) into a radio transmission.
-- E.g. for board number or headings. -- E.g. for board number or headings.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
@@ -280,19 +325,9 @@ end
-- @param #number interval Interval between the next call. -- @param #number interval Interval between the next call.
-- @return #number Duration of the call in seconds. -- @return #number Duration of the call in seconds.
function RADIOQUEUE:Number2Transmission(number, delay, interval) 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. -- Split string into characters.
local numbers=_split(number) local numbers=UTILS.GetCharacters(number)
local wait=0 local wait=0
for i=1,#numbers do for i=1,#numbers do
@@ -325,6 +360,11 @@ end
-- @param #RADIOQUEUE.Transmission transmission The transmission. -- @param #RADIOQUEUE.Transmission transmission The transmission.
function RADIOQUEUE:Broadcast(transmission) function RADIOQUEUE:Broadcast(transmission)
if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then
self:_BroadcastSRS(transmission)
return
end
-- Get unit sending the transmission. -- Get unit sending the transmission.
local sender=self:_GetRadioSender() local sender=self:_GetRadioSender()
@@ -416,6 +456,19 @@ function RADIOQUEUE:Broadcast(transmission)
end end
end end
--- Broadcast radio message.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission.
function RADIOQUEUE:_BroadcastSRS(transmission)
if transmission.soundfile and transmission.soundfile.useSRS then
self.msrs:PlaySoundFile(transmission.soundfile)
elseif transmission.soundtext then
self.msrs:PlaySoundText(transmission.soundtext)
end
end
--- Start checking the radio queue. --- Start checking the radio queue.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @param #number delay Delay in seconds before checking. -- @param #number delay Delay in seconds before checking.
@@ -525,7 +578,7 @@ end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. --- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive. -- @return Wrapper.Unit#UNIT Sending unit or nil if was not setup, is not an aircraft or ground unit or is not alive.
function RADIOQUEUE:_GetRadioSender() function RADIOQUEUE:_GetRadioSender()
-- Check if we have a sending aircraft. -- Check if we have a sending aircraft.
@@ -538,7 +591,7 @@ function RADIOQUEUE:_GetRadioSender()
sender=UNIT:FindByName(self.sendername) sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft. -- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() and sender:IsAir() then if sender and sender:IsAlive() and (sender:IsAir() or sender:IsGround()) then
return sender return sender
end end
@@ -547,7 +600,7 @@ function RADIOQUEUE:_GetRadioSender()
return nil return nil
end end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. --- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work.
-- @param #RADIOQUEUE self -- @param #RADIOQUEUE self
-- @return DCS#Vec3 Vector 3D. -- @return DCS#Vec3 Vector 3D.
function RADIOQUEUE:_GetRadioSenderCoord() function RADIOQUEUE:_GetRadioSenderCoord()

View File

@@ -1,9 +1,9 @@
--- **Core** - Makes the radio talk. --- **Core** - Makes the radio talk.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Send text strings using a vocabulary that is converted in spoken language. -- * Send text strings using a vocabulary that is converted in spoken language.
-- * Possiblity to implement multiple language. -- * Possiblity to implement multiple language.
-- --
@@ -11,14 +11,14 @@
-- --
-- ### Authors: FlightControl -- ### Authors: FlightControl
-- --
-- @module Core.RadioSpeech -- @module Sound.RadioSpeech
-- @image Core_Radio.JPG -- @image Core_Radio.JPG
--- Makes the radio speak. --- Makes the radio speak.
-- --
-- # RADIOSPEECH usage -- # RADIOSPEECH usage
-- --
-- --
-- @type RADIOSPEECH -- @type RADIOSPEECH
-- @extends Core.RadioQueue#RADIOQUEUE -- @extends Core.RadioQueue#RADIOQUEUE
RADIOSPEECH = { RADIOSPEECH = {
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
["70"] = { "70", 0.48 }, ["70"] = { "70", 0.48 },
["80"] = { "80", 0.26 }, ["80"] = { "80", 0.26 },
["90"] = { "90", 0.36 }, ["90"] = { "90", 0.36 },
["100"] = { "100", 0.55 }, ["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 }, ["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 }, ["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 }, ["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 }, ["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 }, ["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 }, ["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 }, ["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 }, ["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 }, ["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 }, ["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 }, ["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 }, ["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 }, ["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 }, ["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 }, ["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 }, ["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 }, ["9000"] = { "9000", 0.65 },
["chevy"] = { "chevy", 0.35 }, ["chevy"] = { "chevy", 0.35 },
["colt"] = { "colt", 0.35 }, ["colt"] = { "colt", 0.35 },
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
["meters"] = { "meters", 0.41 }, ["meters"] = { "meters", 0.41 },
["mi"] = { "miles", 0.45 }, ["mi"] = { "miles", 0.45 },
["feet"] = { "feet", 0.29 }, ["feet"] = { "feet", 0.29 },
["br"] = { "br", 1.1 }, ["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 }, ["bra"] = { "bra", 0.3 },
["returning to base"] = { "returning_to_base", 0.85 }, ["returning to base"] = { "returning_to_base", 0.85 },
["on route to ground target"] = { "on_route_to_ground_target", 1.05 }, ["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
@@ -143,52 +143,52 @@ RADIOSPEECH.Vocabulary.RU = {
["70"] = { "70", 0.68 }, ["70"] = { "70", 0.68 },
["80"] = { "80", 0.84 }, ["80"] = { "80", 0.84 },
["90"] = { "90", 0.71 }, ["90"] = { "90", 0.71 },
["100"] = { "100", 0.35 }, ["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 }, ["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 }, ["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 }, ["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 }, ["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 }, ["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 }, ["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 }, ["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 }, ["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 }, ["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 }, ["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 }, ["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 }, ["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 }, ["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 }, ["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 }, ["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 }, ["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 }, ["9000"] = { "9000", 0.87 },
["степени"] = { "degrees", 0.5 }, ["градусы"] = { "degrees", 0.5 },
["километров"] = { "kilometers", 0.65 }, ["километры"] = { "kilometers", 0.65 },
["km"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 },
["миль"] = { "miles", 0.45 }, ["мили"] = { "miles", 0.45 },
["mi"] = { "miles", 0.45 }, ["mi"] = { "miles", 0.45 },
["метры"] = { "meters", 0.41 }, ["метров"] = { "meters", 0.41 },
["m"] = { "meters", 0.41 }, ["m"] = { "meters", 0.41 },
["ноги"] = { "feet", 0.37 }, ["ноги"] = { "feet", 0.37 },
["br"] = { "br", 1.1 }, ["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 }, ["bra"] = { "bra", 0.3 },
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
["возвращение на базу"] = { "returning_to_base", 1.40 },
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
["перехват самолетов"] = { "intercepting_bogeys", 1.22 }, ["перехват боги"] = { "intercepting_bogeys", 1.22 },
["поражение наземной цели"] = { "engaging_ground_target", 1.53 }, ["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 }, ["привлечение болотных птиц"] = { "engaging_bogeys", 1.68 },
["колеса вверх"] = { "wheels_up", 0.92 }, ["колёса вверх..."] = { "wheels_up", 0.92 },
["посадка на базу"] = { "landing at base", 1.04 }, ["посадка на базу"] = { "landing at base", 1.04 },
["патрулирующий"] = { "patrolling", 0.96 }, ["патрулирование"] = { "patrolling", 0.96 },
["за"] = { "for", 0.27 }, ["для"] = { "for", 0.27 },
["и"] = { "and", 0.17 }, ["и"] = { "and", 0.17 },
["в"] = { "at", 0.19 }, ["на сайте"] = { "at", 0.19 },
["dot"] = { "dot", 0.51 }, ["точка"] = { "dot", 0.51 },
["defender"] = { "defender", 0.45 }, ["защитник"] = { "defender", 0.45 },
} }
--- Create a new RADIOSPEECH object for a given radio frequency/modulation. --- Create a new RADIOSPEECH object for a given radio frequency/modulation.
@@ -200,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation)
-- Inherit base -- Inherit base
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
self.Language = "EN" self.Language = "EN"
self:BuildTree() self:BuildTree()
return self return self
end end
@@ -262,7 +262,7 @@ end
function RADIOSPEECH:BuildTree() function RADIOSPEECH:BuildTree()
self.Speech = {} self.Speech = {}
for Language, Sentences in pairs( self.Vocabulary ) do for Language, Sentences in pairs( self.Vocabulary ) do
self:I( { Language = Language, Sentences = Sentences }) self:I( { Language = Language, Sentences = Sentences })
self.Speech[Language] = {} self.Speech[Language] = {}
@@ -271,7 +271,7 @@ function RADIOSPEECH:BuildTree()
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data ) self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
end end
end end
self:I( { Speech = self.Speech } ) self:I( { Speech = self.Speech } )
return self return self
@@ -290,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" ) local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } ) self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
if Word then if Word then
if Word ~= "" and tonumber(Word) == nil then if Word ~= "" and tonumber(Word) == nil then
@@ -302,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
if Speech[Word].Next == nil then if Speech[Word].Next == nil then
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" ) self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
else else
if RemainderSentence and RemainderSentence ~= "" then if RemainderSentence and RemainderSentence ~= "" then
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language ) return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
end end
@@ -310,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
end end
return RemainderSentence return RemainderSentence
end end
return OriginalSentence return OriginalSentence
else else
return "" return ""
end end
end end
--- Speak a sentence. --- Speak a sentence.
@@ -333,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
if Digits then if Digits then
if Digits ~= "" and tonumber( Digits ) ~= nil then if Digits ~= "" and tonumber( Digits ) ~= nil then
-- Construct numbers -- Construct numbers
local Number = tonumber( Digits ) local Number = tonumber( Digits )
local Multiple = nil local Multiple = nil
@@ -357,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
end end
return RemainderSentence return RemainderSentence
end end
return OriginalSentence return OriginalSentence
else else
return "" return ""
end end
@@ -374,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
self:I( { Sentence, Language } ) self:I( { Sentence, Language } )
local Language = Language or "EN" local Language = Language or "EN"
self:I( { Language = Language } ) self:I( { Language = Language } )
-- If there is no node for Speech, then we start at the first nodes of the language. -- If there is no node for Speech, then we start at the first nodes of the language.
local Speech = self.Speech[Language] local Speech = self.Speech[Language]
self:I( { Speech = Speech, Language = Language } ) self:I( { Speech = Speech, Language = Language } )
self:NewTransmission( "_In.wav", 0.52, Language .. "/" ) self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
repeat repeat
Sentence = self:SpeakWords( Sentence, Speech, Language ) Sentence = self:SpeakWords( Sentence, Speech, Language )
self:I( { Sentence = Sentence } ) self:I( { Sentence = Sentence } )
Sentence = self:SpeakDigits( Sentence, Speech, Language ) Sentence = self:SpeakDigits( Sentence, Speech, Language )
self:I( { Sentence = Sentence } ) self:I( { Sentence = Sentence } )
-- Sentence = self:SpeakSymbols( Sentence, Speech ) -- Sentence = self:SpeakSymbols( Sentence, Speech )
-- --
-- self:I( { Sentence = Sentence } ) -- self:I( { Sentence = Sentence } )

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,412 @@
--- **Sound** - Sound output classes.
--
-- ===
--
-- ## Features:
--
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions
-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS)
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
--
-- There are two classes, SOUNDFILE and SOUNDTEXT, defined in this section that deal with playing
-- sound files or arbitrary text (via SRS Simple-Text-To-Speech), respectively.
--
-- The SOUNDFILE and SOUNDTEXT objects can be defined and used in other MOOSE classes.
--
--
-- @module Sound.SoundOutput
-- @image Sound_SoundOutput.png
do -- Sound Base
--- @type SOUNDBASE
-- @field #string ClassName Name of the class.
-- @extends Core.Base#BASE
--- Basic sound output inherited by other classes suche as SOUNDFILE and SOUNDTEXT.
--
-- This class is **not** meant to be used by "ordinary" users.
--
-- @field #SOUNDBASE
SOUNDBASE={
ClassName = "SOUNDBASE",
}
--- Constructor to create a new SOUNDBASE object.
-- @param #SOUNDBASE self
-- @return #SOUNDBASE self
function SOUNDBASE:New()
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE
return self
end
--- Function returns estimated speech time in seconds.
-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so
--
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
--
-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
--
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
--
-- @param #string Text The text string to analyze.
-- @param #number Speed Speed factor. Default 1.
-- @param #boolean isGoogle If true, google text-to-speech is used.
function SOUNDBASE:GetSpeechTime(length,speed,isGoogle)
local maxRateRatio = 3
speed = speed or 1.0
isGoogle = isGoogle or false
local speedFactor = 1.0
if isGoogle then
speedFactor = speed
else
if speed ~= 0 then
speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1
end
if speed < 0 then
speedFactor = 1/speedFactor
end
end
-- Words per minute.
local wpm = math.ceil(100 * speedFactor)
-- Characters per second.
local cps = math.floor((wpm * 5)/60)
if type(length) == "string" then
length = string.len(length)
end
return math.ceil(length/cps)
end
end
do -- Sound File
--- @type SOUNDFILE
-- @field #string ClassName Name of the class
-- @field #string filename Name of the flag.
-- @field #string path Directory path, where the sound file is located. This includes the final slash "/".
-- @field #string duration Duration of the sound file in seconds.
-- @field #string subtitle Subtitle of the transmission.
-- @field #number subduration Duration in seconds how long the subtitle is displayed.
-- @field #boolean useSRS If true, sound file is played via SRS. Sound file needs to be on local disk not inside the miz file!
-- @extends Core.Base#BASE
--- Sound files used by other classes.
--
-- # The SOUNDFILE Concept
--
-- A SOUNDFILE object hold the important properties that are necessary to play the sound file, e.g. its file name, path, duration.
--
-- It can be created with the @{#SOUNDFILE.New}(*FileName*, *Path*, *Duration*) function:
--
-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "Sound File/", 3.5)
--
-- ## SRS
--
-- If sound files are supposed to be played via SRS, you need to use the @{#SOUNDFILE.SetPlayWithSRS}() function.
--
-- # Location/Path
--
-- ## DCS
--
-- DCS can only play sound files that are located inside the mission (.miz) file. In particular, DCS cannot make use of files that are stored on
-- your hard drive.
--
-- The default location where sound files are stored in DCS is the directory "l10n/DEFAULT/". This is where sound files are placed, if they are
-- added via the mission editor (TRIGGERS-->ACTIONS-->SOUND TO ALL). Note however, that sound files which are not added with a trigger command,
-- will be deleted each time the mission is saved! Therefore, this directory is not ideal to be used especially if many sound files are to
-- be included since for each file a trigger action needs to be created. Which is cumbersome, to say the least.
--
-- The recommended way is to create a new folder inside the mission (.miz) file (a miz file is essentially zip file and can be opened, e.g., with 7-Zip)
-- and to place the sound files in there. Sound files in these folders are not wiped out by DCS on the next save.
--
-- ## SRS
--
-- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path.
--
-- @field #SOUNDFILE
SOUNDFILE={
ClassName = "SOUNDFILE",
filename = nil,
path = "l10n/DEFAULT/",
duration = 3,
subtitle = nil,
subduration = 0,
useSRS = false,
}
--- Constructor to create a new SOUNDFILE object.
-- @param #SOUNDFILE self
-- @param #string FileName The name of the sound file, e.g. "Hello World.ogg".
-- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file.
-- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds.
-- @return #SOUNDFILE self
function SOUNDFILE:New(FileName, Path, Duration)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE
-- Set file name.
self:SetFileName(FileName)
-- Set path.
self:SetPath(Path)
-- Set duration.
self:SetDuration(Duration)
-- Debug info:
self:T(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path))
return self
end
--- Set path, where the sound file is located.
-- @param #SOUNDFILE self
-- @param #string Path Path to the directory, where the sound file is located. In case this is nil, it defaults to the DCS mission temp directory.
-- @return #SOUNDFILE self
function SOUNDFILE:SetPath(Path)
-- Init path.
self.path=Path or "l10n/DEFAULT/"
if not Path and self.useSRS then -- use path to mission temp dir
self.path = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT"
end
-- Remove (back)slashes.
local nmax=1000 ; local n=1
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
n=n+1
end
-- Append slash.
self.path=self.path.."/"
return self
end
--- Get path of the directory, where the sound file is located.
-- @param #SOUNDFILE self
-- @return #string Path.
function SOUNDFILE:GetPath()
local path=self.path or "l10n/DEFAULT/"
return path
end
--- Set sound file name. This must be a .ogg or .mp3 file!
-- @param #SOUNDFILE self
-- @param #string FileName Name of the file. Default is "Hello World.mp3".
-- @return #SOUNDFILE self
function SOUNDFILE:SetFileName(FileName)
--TODO: check that sound file is really .ogg or .mp3
self.filename=FileName or "Hello World.mp3"
return self
end
--- Get the sound file name.
-- @param #SOUNDFILE self
-- @return #string Name of the soud file. This does *not* include its path.
function SOUNDFILE:GetFileName()
return self.filename
end
--- Set duration how long it takes to play the sound file.
-- @param #SOUNDFILE self
-- @param #string Duration Duration in seconds. Default 3 seconds.
-- @return #SOUNDFILE self
function SOUNDFILE:SetDuration(Duration)
self.duration=Duration or 3
return self
end
--- Get duration how long the sound file takes to play.
-- @param #SOUNDFILE self
-- @return #number Duration in seconds.
function SOUNDFILE:GetDuration()
return self.duration or 3
end
--- Get the complete sound file name inlcuding its path.
-- @param #SOUNDFILE self
-- @return #string Name of the sound file.
function SOUNDFILE:GetName()
local path=self:GetPath()
local filename=self:GetFileName()
local name=string.format("%s%s", path, filename)
return name
end
--- Set whether sound files should be played via SRS.
-- @param #SOUNDFILE self
-- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission.
-- @return #SOUNDFILE self
function SOUNDFILE:SetPlayWithSRS(Switch)
if Switch==true or Switch==nil then
self.useSRS=true
else
self.useSRS=false
end
return self
end
end
do -- Text-To-Speech
--- @type SOUNDTEXT
-- @field #string ClassName Name of the class
-- @field #string text Text to speak.
-- @field #number duration Duration in seconds.
-- @field #string gender Gender: "male", "female".
-- @field #string culture Culture, e.g. "en-GB".
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
-- @extends Core.Base#BASE
--- Text-to-speech objects for other classes.
--
-- # The SOUNDTEXT Concept
--
-- A SOUNDTEXT object holds all necessary information to play a general text via SRS Simple-Text-To-Speech.
--
-- It can be created with the @{#SOUNDTEXT.New}(*Text*, *Duration*) function.
--
-- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object.
--
-- # Options
--
-- ## Gender
--
-- You can choose a gender ("male" or "femal") with the @{#SOUNDTEXT.SetGender}(*Gender*) function.
-- Note that the gender voice needs to be installed on your windows machine for the used culture (see below).
--
-- ## Culture
--
-- You can choose a "culture" (accent) with the @{#SOUNDTEXT.SetCulture}(*Culture*) function, where the default (SRS) culture is "en-GB".
--
-- Other examples for culture are: "en-US" (US accent), "de-DE" (German), "it-IT" (Italian), "ru-RU" (Russian), "zh-CN" (Chinese).
--
-- Note that the chosen culture needs to be installed on your windows machine.
--
-- ## Specific Voice
--
-- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples
--
-- * Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain)
-- * Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States)
-- * Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States)
-- * Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German
-- * Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain)
-- * Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French
-- * Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy)
-- * Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian
-- * Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified)
--
-- Note that this must be installed on your windos machine. Also note that this overrides any culture and gender settings.
--
-- @field #SOUNDTEXT
SOUNDTEXT={
ClassName = "SOUNDTEXT",
}
--- Constructor to create a new SOUNDTEXT object.
-- @param #SOUNDTEXT self
-- @param #string Text The text to speak.
-- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds.
-- @return #SOUNDTEXT self
function SOUNDTEXT:New(Text, Duration)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT
self:SetText(Text)
self:SetDuration(Duration or STTS.getSpeechTime(Text))
--self:SetGender()
--self:SetCulture()
-- Debug info:
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
return self
end
--- Set text.
-- @param #SOUNDTEXT self
-- @param #string Text Text to speak. Default "Hello World!".
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetText(Text)
self.text=Text or "Hello World!"
return self
end
--- Set duration, how long it takes to speak the text.
-- @param #SOUNDTEXT self
-- @param #number Duration Duration in seconds. Default 3 seconds.
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetDuration(Duration)
self.duration=Duration or 3
return self
end
--- Set gender.
-- @param #SOUNDTEXT self
-- @param #string Gender Gender: "male" or "female" (default).
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetGender(Gender)
self.gender=Gender or "female"
return self
end
--- Set TTS culture - local for the voice.
-- @param #SOUNDTEXT self
-- @param #string Culture TTS culture. Default "en-GB".
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetCulture(Culture)
self.culture=Culture or "en-GB"
return self
end
--- Set to use a specific voice name.
-- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see [google voices](https://cloud.google.com/text-to-speech/docs/voices).
-- @param #SOUNDTEXT self
-- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`.
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetVoice(VoiceName)
self.voice=VoiceName
return self
end
end

View File

@@ -1,4 +1,4 @@
--- **Core** - Manage user sound. --- **Sound** - Manage user sound.
-- --
-- === -- ===
-- --
@@ -16,7 +16,7 @@
-- --
-- === -- ===
-- --
-- @module Core.UserSound -- @module Sound.UserSound
-- @image Core_Usersound.JPG -- @image Core_Usersound.JPG
do -- UserSound do -- UserSound
@@ -40,7 +40,7 @@ do -- UserSound
-- @param #USERSOUND self -- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound. -- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND -- @return #USERSOUND
function USERSOUND:New( UserSoundFileName ) --R2.3 function USERSOUND:New( UserSoundFileName )
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
@@ -58,7 +58,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound. -- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
-- --
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3 function USERSOUND:SetFileName( UserSoundFileName )
self.UserSoundFileName = UserSoundFileName self.UserSoundFileName = UserSoundFileName
@@ -75,7 +75,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToAll() -- Play the sound that Blue has won. -- BlueVictory:ToAll() -- Play the sound that Blue has won.
-- --
function USERSOUND:ToAll() --R2.3 function USERSOUND:ToAll()
trigger.action.outSound( self.UserSoundFileName ) trigger.action.outSound( self.UserSoundFileName )
@@ -91,7 +91,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition. -- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
-- --
function USERSOUND:ToCoalition( Coalition ) --R2.3 function USERSOUND:ToCoalition( Coalition )
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName ) trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
@@ -107,7 +107,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country. -- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
-- --
function USERSOUND:ToCountry( Country ) --R2.3 function USERSOUND:ToCountry( Country )
trigger.action.outSoundForCountry( Country, self.UserSoundFileName ) trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
@@ -123,9 +123,9 @@ do -- UserSound
-- @usage -- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player. -- 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. -- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group.
-- --
function USERSOUND:ToGroup( Group, Delay ) --R2.3 function USERSOUND:ToGroup( Group, Delay )
Delay=Delay or 0 Delay=Delay or 0
if Delay>0 then if Delay>0 then
@@ -136,5 +136,27 @@ do -- UserSound
return self return self
end end
--- Play the usersound to the given @{Wrapper.Unit}.
-- @param #USERSOUND self
-- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} 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 PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
--
function USERSOUND:ToUnit( Unit, Delay )
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay)
else
trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName )
end
return self
end
end end

View File

@@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
self:SetAutoAcceptTasks( true ) self:SetAutoAcceptTasks( true )
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
self:SetFlashStatus( false ) self:SetFlashStatus( false )
self:SetMessageDuration(10)
self:HandleEvent( EVENTS.Birth, self:HandleEvent( EVENTS.Birth,
--- @param #COMMANDCENTER self --- @param #COMMANDCENTER self
@@ -215,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu ) local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup ) local 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 ) local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
self:ReportSummary( EventGroup ) --self:ReportSummary( EventGroup )
local PlayerUnit = EventData.IniUnit local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION local Mission = Mission -- Tasking.Mission#MISSION
@@ -559,9 +560,11 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false self.AutoAssignTasks = AutoAssign or false
if self.AutoAssignTasks == true then if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self ) self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else else
self:ScheduleStop( self.AssignTasks ) self:ScheduleStop()
-- FF this is not the schedule ID
--self:ScheduleStop( self.AssignTasks )
end end
end end
@@ -682,7 +685,7 @@ end
-- @param #string Message The message text. -- @param #string Message The message text.
function COMMANDCENTER:MessageToAll( Message ) function COMMANDCENTER:MessageToAll( Message )
self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
end end
@@ -692,7 +695,7 @@ end
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
end end
@@ -715,7 +718,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
local CCCoalition = self:GetPositionable():GetCoalition() local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug! --TODO: Fix coalition bug!
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
end end
@@ -795,9 +798,18 @@ end
--- Let the command center flash a report of the status of the subscribed task to a group. --- Let the command center flash a report of the status of the subscribed task to a group.
-- @param #COMMANDCENTER self -- @param #COMMANDCENTER self
-- @param Flash #boolean
function COMMANDCENTER:SetFlashStatus( Flash ) function COMMANDCENTER:SetFlashStatus( Flash )
self:F() self:F()
self.FlashStatus = Flash or true self.FlashStatus = Flash and true
end
--- Duration a command center message is shown.
-- @param #COMMANDCENTER self
-- @param seconds #number
function COMMANDCENTER:SetMessageDuration(seconds)
self:F()
self.MessageDuration = 10 or seconds
end end

View File

@@ -132,7 +132,7 @@ MISSION = {
-- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players. -- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players.
-- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field. -- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field.
-- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{CLIENT}. -- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{CLIENT}.
-- @param DCS#coaliton.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. -- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL.
-- @return #MISSION self -- @return #MISSION self
function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition )

View File

@@ -82,7 +82,7 @@
-- --
-- ![Mission](../Tasking/Report_Statistics_Progress.JPG) -- ![Mission](../Tasking/Report_Statistics_Progress.JPG)
-- --
-- A statistic report on the progress of the mission. Each task achievement will increase the %-tage to 100% as a goal to complete the task. -- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task.
-- --
-- ## 1.3) Join a Task. -- ## 1.3) Join a Task.
-- --
@@ -881,6 +881,9 @@ do -- Group Assignment
local Mission = self:GetMission() local Mission = self:GetMission()
local CommandCenter = Mission:GetCommandCenter() local CommandCenter = Mission:GetCommandCenter()
CommandCenter:SetMenu() CommandCenter:SetMenu()
self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312
end end
end end
@@ -1252,7 +1255,7 @@ function TASK:MenuFlashTaskStatus( TaskGroup, Flash )
self.FlashTaskStatus = Flash self.FlashTaskStatus = Flash
if self.FlashTaskStatus then if self.FlashTaskStatus then
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 ) self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages
else else
if self.FlashTaskScheduler then if self.FlashTaskScheduler then
self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID ) self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID )

View File

@@ -344,7 +344,9 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
Text = DataText Text = DataText
else else
local DataText = Data.Data -- #string local DataText = Data.Data -- #string
Text = DataText if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string
Text = DataText
end
end end
if Line < math.floor( Data.Order / 10 ) then if Line < math.floor( Data.Order / 10 ) then

View File

@@ -1,13 +1,13 @@
--- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements. --- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements.
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- === -- ===
-- --
-- @module Tasking.Task_A2A -- @module Tasking.Task_A2A
-- @image MOOSE.JPG -- @image MOOSE.JPG
@@ -35,12 +35,12 @@ do -- TASK_A2A
-- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed. -- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed.
-- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed. -- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed.
-- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed. -- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed.
-- --
-- @field #TASK_A2A -- @field #TASK_A2A
TASK_A2A = { TASK_A2A = {
ClassName = "TASK_A2A", ClassName = "TASK_A2A"
} }
--- Instantiates a new TASK_A2A. --- Instantiates a new TASK_A2A.
-- @param #TASK_A2A self -- @param #TASK_A2A self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -54,51 +54,49 @@ do -- TASK_A2A
function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing ) function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A
self:F() self:F()
self.TargetSetUnit = TargetSetUnit self.TargetSetUnit = TargetSetUnit
self.TaskType = TaskType self.TaskType = TaskType
local Fsm = self:GetUnitProcess() local Fsm = self:GetUnitProcess()
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
-- Fsm:AddTransition( "Accounted", "Success", "Success" ) -- Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" ) Fsm:AddTransition( "Failed", "Fail", "Failed" )
---- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param #TASK_CARGO Task -- @param #TASK_CARGO Task
function Fsm:OnLeaveAssigned( TaskUnit, Task ) function Fsm:OnLeaveAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:SelectAction() self:SelectAction()
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task -- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit -- Determine the first Unit from the self.RendezVousSetUnit
if Task:GetRendezVousZone( TaskUnit ) then if Task:GetRendezVousZone( TaskUnit ) then
self:__RouteToRendezVousZone( 0.1 ) self:__RouteToRendezVousZone( 0.1 )
else else
@@ -110,36 +108,36 @@ do -- TASK_A2A
end end
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task -- @param Tasking.Task#TASK_A2A Task
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit -- Determine the first Unit from the self.TargetSetUnit
self:__Engage( 0.1 ) self:__Engage( 0.1 )
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task -- @param Tasking.Task#TASK_A2A Task
function Fsm:onafterEngage( TaskUnit, Task ) function Fsm:onafterEngage( TaskUnit, Task )
self:F( { self } ) self:F( { self } )
self:__Account( 0.1 ) self:__Account( 0.1 )
self:__RouteToTarget(0.1 ) self:__RouteToTarget( 0.1 )
self:__RouteToTargets( -10 ) self:__RouteToTargets( -10 )
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task -- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToTarget( TaskUnit, Task ) function Fsm:onafterRouteToTarget( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit -- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then if Task:GetTargetZone( TaskUnit ) then
self:__RouteToTargetZone( 0.1 ) self:__RouteToTargetZone( 0.1 )
else else
@@ -152,8 +150,8 @@ do -- TASK_A2A
self:__RouteToTargetPoint( 0.1 ) self:__RouteToTargetPoint( 0.1 )
end end
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task -- @param Tasking.Task_A2A#TASK_A2A Task
@@ -165,20 +163,18 @@ do -- TASK_A2A
end end
self:__RouteToTargets( -10 ) self:__RouteToTargets( -10 )
end end
return self return self
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) function TASK_A2A:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit self.TargetSetUnit = TargetSetUnit
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
function TASK_A2A:GetPlannedMenuText() function TASK_A2A:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
@@ -188,34 +184,32 @@ do -- TASK_A2A
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
ActRouteRendezVous:SetRange( RendezVousRange ) ActRouteRendezVous:SetRange( RendezVousRange )
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
function TASK_A2A:GetRendezVousCoordinate( TaskUnit ) function TASK_A2A:GetRendezVousCoordinate( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
@@ -232,18 +226,17 @@ do -- TASK_A2A
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteRendezVous:GetZone() return ActRouteRendezVous:GetZone()
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteTarget:SetCoordinate( TargetCoordinate ) ActRouteTarget:SetCoordinate( TargetCoordinate )
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
@@ -256,18 +249,16 @@ do -- TASK_A2A
return ActRouteTarget:GetCoordinate() return ActRouteTarget:GetCoordinate()
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) ActRouteTarget:SetZone( TargetZone, Altitude, Heading )
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
@@ -281,45 +272,43 @@ do -- TASK_A2A
end end
function TASK_A2A:SetGoalTotal() function TASK_A2A:SetGoalTotal()
self.GoalTotal = self.TargetSetUnit:Count() self.GoalTotal = self.TargetSetUnit:Count()
end end
function TASK_A2A:GetGoalTotal() function TASK_A2A:GetGoalTotal()
return self.GoalTotal return self.GoalTotal
end end
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. --- 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 -- @param #TASK_A2A self
function TASK_A2A:ReportOrder( ReportGroup ) function TASK_A2A:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem ) self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance return Distance
end end
--- This method checks every 10 seconds if the goal has been reached of the task. --- This method checks every 10 seconds if the goal has been reached of the task.
-- @param #TASK_A2A self -- @param #TASK_A2A self
function TASK_A2A:onafterGoal( TaskUnit, From, Event, To ) function TASK_A2A:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then if TargetSetUnit:Count() == 0 then
self:Success() self:Success()
end end
self:__Goal( -10 ) self:__Goal( -10 )
end end
--- @param #TASK_A2A self --- @param #TASK_A2A self
function TASK_A2A:UpdateTaskInfo( DetectedItem ) function TASK_A2A:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then if self:IsStatePlanned() or self:IsStateAssigned() then
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddTaskName( 0, "MSOD" )
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
@@ -343,12 +332,12 @@ do -- TASK_A2A
end end
end end
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
else else
local DetectedItemsCount = self.TargetSetUnit:Count() local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
end end
end end
end end
@@ -359,8 +348,8 @@ do -- TASK_A2A
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group. -- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 ) return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Coordinate = self.TaskInfo:GetData( "Coordinate" )
@@ -373,8 +362,7 @@ do -- TASK_A2A
return 0 return 0
end end
end end
do -- TASK_A2A_INTERCEPT do -- TASK_A2A_INTERCEPT
@@ -384,44 +372,39 @@ do -- TASK_A2A_INTERCEPT
-- @extends Tasking.Task#TASK -- @extends Tasking.Task#TASK
--- Defines an intercept task for a human player to be executed. --- Defines an intercept task for a human player to be executed.
-- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there! -- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there!
-- --
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
-- based on detected airborne enemy targets intruding friendly airspace. -- based on detected airborne enemy targets intruding friendly airspace.
-- --
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
-- --
-- @field #TASK_A2A_INTERCEPT -- @field #TASK_A2A_INTERCEPT
TASK_A2A_INTERCEPT = { TASK_A2A_INTERCEPT = {
ClassName = "TASK_A2A_INTERCEPT", ClassName = "TASK_A2A_INTERCEPT"
} }
--- Instantiates a new TASK_A2A_INTERCEPT. --- Instantiates a new TASK_A2A_INTERCEPT.
-- @param #TASK_A2A_INTERCEPT self -- @param #TASK_A2A_INTERCEPT self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @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 #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT TargetSetUnit -- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task. -- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_INTERCEPT -- @return #TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT
self:F() self:F()
Mission:AddTask( self ) Mission:AddTask( self )
self:SetBriefing( self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" )
TaskBriefing or
"Intercept incoming intruders.\n"
)
return self return self
end end
--- Set a score when a target in scope of the A2A attack, has been destroyed . --- Set a score when a target in scope of the A2A attack, has been destroyed.
-- @param #TASK_A2A_INTERCEPT self -- @param #TASK_A2A_INTERCEPT self
-- @param #string PlayerName The name of the player. -- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved. -- @param #number Score The score in points to be granted when task process has been achieved.
@@ -433,7 +416,7 @@ do -- TASK_A2A_INTERCEPT
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score )
return self return self
end end
@@ -449,7 +432,7 @@ do -- TASK_A2A_INTERCEPT
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score ) ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score )
return self return self
end end
@@ -465,14 +448,12 @@ do -- TASK_A2A_INTERCEPT
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty )
return self return self
end end
end end
do -- TASK_A2A_SWEEP do -- TASK_A2A_SWEEP
--- The TASK_A2A_SWEEP class --- The TASK_A2A_SWEEP class
@@ -484,20 +465,18 @@ do -- TASK_A2A_SWEEP
-- A sweep task needs to be given when targets were detected but somehow the detection was lost. -- A sweep task needs to be given when targets were detected but somehow the detection was lost.
-- Most likely, these enemy planes are hidden in the mountains or are flying under radar. -- Most likely, these enemy planes are hidden in the mountains or are flying under radar.
-- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters. -- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters.
-- --
-- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
-- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds. -- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds.
-- --
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
-- --
-- @field #TASK_A2A_SWEEP -- @field #TASK_A2A_SWEEP
TASK_A2A_SWEEP = { TASK_A2A_SWEEP = {
ClassName = "TASK_A2A_SWEEP", ClassName = "TASK_A2A_SWEEP"
} }
--- Instantiates a new TASK_A2A_SWEEP. --- Instantiates a new TASK_A2A_SWEEP.
-- @param #TASK_A2A_SWEEP self -- @param #TASK_A2A_SWEEP self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -509,29 +488,26 @@ do -- TASK_A2A_SWEEP
function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP
self:F() self:F()
Mission:AddTask( self ) Mission:AddTask( self )
self:SetBriefing( self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" )
TaskBriefing or
"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
)
return self return self
end end
--- @param #TASK_A2A_SWEEP self --- @param #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then if TargetSetUnit:Count() == 0 then
self:Success() self:Success()
end end
self:__Goal( -10 ) self:__Goal( -10 )
end end
--- Set a score when a target in scope of the A2A attack, has been destroyed . --- Set a score when a target in scope of the A2A attack, has been destroyed.
-- @param #TASK_A2A_SWEEP self -- @param #TASK_A2A_SWEEP self
-- @param #string PlayerName The name of the player. -- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved. -- @param #number Score The score in points to be granted when task process has been achieved.
@@ -543,7 +519,7 @@ do -- TASK_A2A_SWEEP
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score )
return self return self
end end
@@ -559,7 +535,7 @@ do -- TASK_A2A_SWEEP
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score ) ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score )
return self return self
end end
@@ -575,13 +551,12 @@ do -- TASK_A2A_SWEEP
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty )
return self return self
end end
end end
do -- TASK_A2A_ENGAGE do -- TASK_A2A_ENGAGE
--- The TASK_A2A_ENGAGE class --- The TASK_A2A_ENGAGE class
@@ -591,42 +566,37 @@ do -- TASK_A2A_ENGAGE
--- Defines an engage task for a human player to be executed. --- Defines an engage task for a human player to be executed.
-- When enemy planes are close to human players, use this task type is used urge the players to get out there! -- When enemy planes are close to human players, use this task type is used urge the players to get out there!
-- --
-- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
-- based on detected airborne enemy targets intruding friendly airspace. -- based on detected airborne enemy targets intruding friendly airspace.
-- --
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
-- --
-- @field #TASK_A2A_ENGAGE -- @field #TASK_A2A_ENGAGE
TASK_A2A_ENGAGE = { TASK_A2A_ENGAGE = {
ClassName = "TASK_A2A_ENGAGE", ClassName = "TASK_A2A_ENGAGE"
} }
--- Instantiates a new TASK_A2A_ENGAGE. --- Instantiates a new TASK_A2A_ENGAGE.
-- @param #TASK_A2A_ENGAGE self -- @param #TASK_A2A_ENGAGE self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @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 #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT TargetSetUnit -- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task. -- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_ENGAGE self -- @return #TASK_A2A_ENGAGE self
function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE
self:F() self:F()
Mission:AddTask( self ) Mission:AddTask( self )
self:SetBriefing( self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" )
TaskBriefing or
"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
)
return self return self
end end
--- Set a score when a target in scope of the A2A attack, has been destroyed . --- Set a score when a target in scope of the A2A attack, has been destroyed .
-- @param #TASK_A2A_ENGAGE self -- @param #TASK_A2A_ENGAGE self
-- @param #string PlayerName The name of the player. -- @param #string PlayerName The name of the player.
@@ -639,7 +609,7 @@ do -- TASK_A2A_ENGAGE
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score )
return self return self
end end
@@ -655,7 +625,7 @@ do -- TASK_A2A_ENGAGE
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score ) ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score )
return self return self
end end
@@ -671,7 +641,7 @@ do -- TASK_A2A_ENGAGE
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty )
return self return self
end end

View File

@@ -1,7 +1,7 @@
--- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. --- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network.
-- --
-- **Features:** -- **Features:**
-- --
-- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically assign tasks to human players based on detected targets.
-- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically change the tasks as the tactical situation evolves during the mission.
-- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP. -- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP.
@@ -11,15 +11,15 @@
-- * Define different ranges to engage upon intruders. -- * Define different ranges to engage upon intruders.
-- * Keep task achievements. -- * Keep task achievements.
-- * Score task achievements. -- * Score task achievements.
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- === -- ===
-- --
-- @module Tasking.Task_A2A_Dispatcher -- @module Tasking.Task_A2A_Dispatcher
-- @image Task_A2A_Dispatcher.JPG -- @image Task_A2A_Dispatcher.JPG
@@ -30,72 +30,72 @@ do -- TASK_A2A_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER -- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups.
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG)
-- --
-- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. -- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched.
-- Find a summary below describing for which situation a task type is created: -- Find a summary below describing for which situation a task type is created:
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG)
-- --
-- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range. -- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range.
-- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range. -- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range.
-- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task. -- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task.
-- --
-- ## 1. TASK\_A2A\_DISPATCHER constructor: -- ## 1. TASK\_A2A\_DISPATCHER constructor:
-- --
-- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance. -- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance.
-- --
-- ### 1.1. Define or set the **Mission**: -- ### 1.1. Define or set the **Mission**:
-- --
-- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter. -- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter.
-- --
-- local HQ = GROUP:FindByName( "HQ", "Bravo" ) -- local HQ = GROUP:FindByName( "HQ", "Bravo" )
-- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" ) -- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" )
-- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED ) -- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED )
-- --
-- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission. -- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission.
-- Create the MISSION object, and hook it under the command center. -- Create the MISSION object, and hook it under the command center.
-- --
-- ### 1.2. Build a set of the groups seated by human players: -- ### 1.2. Build a set of the groups seated by human players:
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG)
-- --
-- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into. -- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into.
-- --
-- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart() -- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart()
-- --
-- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission. -- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission.
-- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available. -- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available.
-- --
-- ### 1.3. Define the **EWR network**: -- ### 1.3. Define the **EWR network**:
-- --
-- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter. -- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter.
-- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG)
-- --
-- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units.
-- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US).
-- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar.
-- The position of these units is very important as they need to provide enough coverage -- The position of these units is very important as they need to provide enough coverage
-- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG)
-- --
-- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates.
-- For example if they are a long way forward and can detect enemy planes on the ground and taking off -- For example if they are a long way forward and can detect enemy planes on the ground and taking off
-- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition.
-- Having the radars further back will mean a slower escalation because fewer targets will be detected and -- Having the radars further back will mean a slower escalation because fewer targets will be detected and
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
-- It all depends on what the desired effect is. -- It all depends on what the desired effect is.
-- --
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class. -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
-- increasing or decreasing the radar coverage of the Early Warning System. -- increasing or decreasing the radar coverage of the Early Warning System.
-- --
-- See the following example to setup an EWR network containing EWR stations and AWACS. -- See the following example to setup an EWR network containing EWR stations and AWACS.
-- --
-- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart() -- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart()
-- --
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 ) -- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
@@ -104,50 +104,50 @@ do -- TASK_A2A_DISPATCHER
-- --
-- -- Setup the A2A dispatcher, and initialize it. -- -- Setup the A2A dispatcher, and initialize it.
-- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection ) -- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
-- --
-- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**. -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**.
-- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set. -- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set.
-- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. -- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km. -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km.
-- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism. -- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism.
-- --
-- ### 2. Define the detected **target grouping radius**: -- ### 2. Define the detected **target grouping radius**:
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG)
-- --
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.
-- Fast planes like in the 80s, need a larger radius than WWII planes. -- Fast planes like in the 80s, need a larger radius than WWII planes.
-- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft.
-- --
-- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate
-- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!
-- --
-- ## 3. Set the **Engage radius**: -- ## 3. Set the **Engage radius**:
-- --
-- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. -- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.
-- --
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG) -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG)
-- --
-- So, if there is a target area detected and reported, -- So, if there is a target area detected and reported,
-- then any friendlies that are airborne near this target area, -- then any friendlies that are airborne near this target area,
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded). -- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
-- will be considered to receive the command to engage that target area. -- will be considered to receive the command to engage that target area.
-- You need to evaluate the value of this parameter carefully. -- You need to evaluate the value of this parameter carefully.
-- If too small, more intercept missions may be triggered upon detected target areas. -- If too small, more intercept missions may be triggered upon detected target areas.
-- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
-- --
-- ## 4. Set **Scoring** and **Messages**: -- ## 4. Set **Scoring** and **Messages**:
-- --
-- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER. -- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER.
-- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_, -- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_,
-- when the player reaches certain achievements in the task. -- when the player reaches certain achievements in the task.
-- --
-- The prototype to handle the **Assign** event needs to be developed as follows: -- The prototype to handle the **Assign** event needs to be developed as follows:
-- --
-- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... )
-- --
-- --- @param #TaskDispatcher self -- --- @param #TaskDispatcher self
-- -- @param #string From Contains the name of the state from where the Event was triggered. -- -- @param #string From Contains the name of the state from where the Event was triggered.
-- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign.
@@ -160,22 +160,22 @@ do -- TASK_A2A_DISPATCHER
-- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit ) -- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit )
-- Task:SetScoreOnFail( PlayerName, -100, TaskUnit ) -- Task:SetScoreOnFail( PlayerName, -100, TaskUnit )
-- end -- end
-- --
-- The **OnAfterAssign** method (function) is added to the TaskDispatcher object. -- The **OnAfterAssign** method (function) is added to the TaskDispatcher object.
-- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher. -- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher.
-- So, this method will be called only **ONCE** when a player joins a unit in scope of the task. -- So, this method will be called only **ONCE** when a player joins a unit in scope of the task.
-- --
-- The TASK class implements various methods to additional **set scoring** for player achievements: -- The TASK class implements various methods to additional **set scoring** for player achievements:
-- --
-- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task. -- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task.
-- Examples of **task progress** can be destroying units, arriving at zones etc. -- Examples of **task progress** can be destroying units, arriving at zones etc.
-- --
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state.
-- This means the **task has been successfully completed**. -- This means the **task has been successfully completed**.
-- --
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state.
-- This means the **task has not been successfully completed**, and the scores must be given with a negative value! -- This means the **task has not been successfully completed**, and the scores must be given with a negative value!
-- --
-- @field #TASK_A2A_DISPATCHER -- @field #TASK_A2A_DISPATCHER
TASK_A2A_DISPATCHER = { TASK_A2A_DISPATCHER = {
ClassName = "TASK_A2A_DISPATCHER", ClassName = "TASK_A2A_DISPATCHER",
@@ -184,8 +184,7 @@ do -- TASK_A2A_DISPATCHER
Tasks = {}, Tasks = {},
SweepZones = {}, SweepZones = {},
} }
--- TASK_A2A_DISPATCHER constructor. --- TASK_A2A_DISPATCHER constructor.
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
@@ -193,22 +192,21 @@ do -- TASK_A2A_DISPATCHER
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
-- @return #TASK_A2A_DISPATCHER self -- @return #TASK_A2A_DISPATCHER self
function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection ) function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection )
-- Inherits from DETECTION_MANAGER -- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER
self.Detection = Detection self.Detection = Detection
self.Mission = Mission self.Mission = Mission
self.FlashNewTask = false
-- TODO: Check detection through radar. -- TODO: Check detection through radar.
self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER )
self.Detection:InitDetectRadar( true ) self.Detection:InitDetectRadar( true )
self.Detection:SetRefreshTimeInterval( 30 ) self.Detection:SetRefreshTimeInterval( 30 )
self:AddTransition( "Started", "Assign", "Started" ) self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign. --- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
@@ -220,14 +218,13 @@ do -- TASK_A2A_DISPATCHER
-- @param #string PlayerName -- @param #string PlayerName
self:__Start( 5 ) self:__Start( 5 )
return self return self
end end
--- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission. --- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission.
-- So, if there is a target area detected and reported, -- So, if there is a target area detected and reported,
-- then any friendlies that are airborne near this target area, -- then any friendlies that are airborne near this target area,
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded). -- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
-- An ENGAGE task will be created for those pilots. -- An ENGAGE task will be created for those pilots.
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
@@ -239,22 +236,27 @@ do -- TASK_A2A_DISPATCHER
-- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target.
-- @return #TASK_A2A_DISPATCHER -- @return #TASK_A2A_DISPATCHER
-- @usage -- @usage
-- --
-- -- Set 50km as the radius to engage any target by airborne friendlies. -- -- Set 50km as the radius to engage any target by airborne friendlies.
-- TaskA2ADispatcher:SetEngageRadius( 50000 ) -- TaskA2ADispatcher:SetEngageRadius( 50000 )
-- --
-- -- Set 100km as the radius to engage any target by airborne friendlies. -- -- Set 100km as the radius to engage any target by airborne friendlies.
-- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value. -- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value.
-- --
function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius )
self.Detection:SetFriendliesRange( EngageRadius or 100000 ) self.Detection:SetFriendliesRange( EngageRadius or 100000 )
return self return self
end end
--- Set flashing player messages on or off
-- @param #TASK_A2A_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function TASK_A2A_DISPATCHER:SetSendMessages( onoff )
self.FlashNewTask = onoff
end
--- Creates an INTERCEPT task when there are targets for it. --- Creates an INTERCEPT task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
@@ -262,26 +264,25 @@ do -- TASK_A2A_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem ) function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
-- Check if there is at least one UNIT in the DetectedSet is visible. -- Check if there is at least one UNIT in the DetectedSet is visible.
if DetectedItem.IsDetected == true then if DetectedItem.IsDetected == true then
-- Here we're doing something advanced... We're copying the DetectedSet. -- Here we're doing something advanced... We're copying the DetectedSet.
local TargetSetUnit = SET_UNIT:New() local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
--- Creates an SWEEP task when there are targets for it. --- Creates an SWEEP task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
@@ -289,10 +290,9 @@ do -- TASK_A2A_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
if DetectedItem.IsDetected == false then if DetectedItem.IsDetected == false then
@@ -300,14 +300,13 @@ do -- TASK_A2A_DISPATCHER
local TargetSetUnit = SET_UNIT:New() local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
--- Creates an ENGAGE task when there are human friendlies airborne near the targets. --- Creates an ENGAGE task when there are human friendlies airborne near the targets.
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
@@ -315,13 +314,12 @@ do -- TASK_A2A_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
-- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone. -- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone.
if PlayersCount > 0 and DetectedItem.IsDetected == true then if PlayersCount > 0 and DetectedItem.IsDetected == true then
@@ -329,16 +327,13 @@ do -- TASK_A2A_DISPATCHER
local TargetSetUnit = SET_UNIT:New() local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
--- Evaluates the removal of the Task from the Mission. --- Evaluates the removal of the Task from the Mission.
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
@@ -349,24 +344,24 @@ do -- TASK_A2A_DISPATCHER
-- @param #boolean DetectedItemChange -- @param #boolean DetectedItemChange
-- @return Tasking.Task#TASK -- @return Tasking.Task#TASK
function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged ) function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged )
if Task then if Task then
if Task:IsStatePlanned() then if Task:IsStatePlanned() then
local TaskName = Task:GetName() local TaskName = Task:GetName()
local TaskType = TaskName:match( "(%u+)%.%d+" ) local TaskType = TaskName:match( "(%u+)%.%d+" )
self:T2( { TaskType = TaskType } ) self:T2( { TaskType = TaskType } )
local Remove = false local Remove = false
local IsPlayers = Detection:IsPlayersNearBy( DetectedItem ) local IsPlayers = Detection:IsPlayersNearBy( DetectedItem )
if TaskType == "ENGAGE" then if TaskType == "ENGAGE" then
if IsPlayers == false then if IsPlayers == false then
Remove = true Remove = true
end end
end end
if TaskType == "INTERCEPT" then if TaskType == "INTERCEPT" then
if IsPlayers == true then if IsPlayers == true then
Remove = true Remove = true
@@ -375,7 +370,7 @@ do -- TASK_A2A_DISPATCHER
Remove = true Remove = true
end end
end end
if TaskType == "SWEEP" then if TaskType == "SWEEP" then
if DetectedItem.IsDetected == true then if DetectedItem.IsDetected == true then
Remove = true Remove = true
@@ -383,18 +378,18 @@ do -- TASK_A2A_DISPATCHER
end end
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
--DetectedSet:Flush( self ) -- DetectedSet:Flush( self )
--self:F( { DetectedSetCount = DetectedSet:Count() } ) -- self:F( { DetectedSetCount = DetectedSet:Count() } )
if DetectedSet:Count() == 0 then if DetectedSet:Count() == 0 then
Remove = true Remove = true
end end
if DetectedItemChanged == true or Remove then if DetectedItemChanged == true or Remove then
Task = self:RemoveTask( DetectedItemIndex ) Task = self:RemoveTask( DetectedItemIndex )
end end
end end
end end
return Task return Task
end end
@@ -403,10 +398,10 @@ do -- TASK_A2A_DISPATCHER
-- @param DetectedItem -- @param DetectedItem
-- @return #number, Core.CommandCenter#REPORT -- @return #number, Core.CommandCenter#REPORT
function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
local FriendlyTypes = {} local FriendlyTypes = {}
local FriendliesCount = 0 local FriendliesCount = 0
@@ -418,27 +413,26 @@ do -- TASK_A2A_DISPATCHER
local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel()
FriendliesCount = FriendliesCount + 1 FriendliesCount = FriendliesCount + 1
local FriendlyType = FriendlyUnit:GetTypeName() local FriendlyType = FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
end end
end end
end end
end end
--self:F( { FriendliesCount = FriendliesCount } ) -- self:F( { FriendliesCount = FriendliesCount } )
local FriendlyTypesReport = REPORT:New() local FriendlyTypesReport = REPORT:New()
if FriendliesCount > 0 then if FriendliesCount > 0 then
for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do
FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) )
end end
else else
FriendlyTypesReport:Add( "-" ) FriendlyTypesReport:Add( "-" )
end end
return FriendliesCount, FriendlyTypesReport return FriendliesCount, FriendlyTypesReport
end end
@@ -447,10 +441,10 @@ do -- TASK_A2A_DISPATCHER
-- @param DetectedItem -- @param DetectedItem
-- @return #number, Core.CommandCenter#REPORT -- @return #number, Core.CommandCenter#REPORT
function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem )
local PlayerTypes = {} local PlayerTypes = {}
local PlayersCount = 0 local PlayersCount = 0
@@ -459,7 +453,7 @@ do -- TASK_A2A_DISPATCHER
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
local PlayerName = PlayerUnit:GetPlayerName() local PlayerName = PlayerUnit:GetPlayerName()
--self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
PlayersCount = PlayersCount + 1 PlayersCount = PlayersCount + 1
@@ -469,20 +463,19 @@ do -- TASK_A2A_DISPATCHER
end end
end end
end end
end end
local PlayerTypesReport = REPORT:New() local PlayerTypesReport = REPORT:New()
if PlayersCount > 0 then if PlayersCount > 0 then
for PlayerName, PlayerType in pairs( PlayerTypes ) do for PlayerName, PlayerType in pairs( PlayerTypes ) do
PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) )
end end
else else
PlayerTypesReport:Add( "-" ) PlayerTypesReport:Add( "-" )
end end
return PlayersCount, PlayerTypesReport return PlayersCount, PlayerTypesReport
end end
@@ -491,24 +484,23 @@ do -- TASK_A2A_DISPATCHER
self.Tasks[TaskIndex] = nil self.Tasks[TaskIndex] = nil
end end
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
-- @param #TASK_A2A_DISPATCHER self -- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_A2A_DISPATCHER:ProcessDetected( Detection ) function TASK_A2A_DISPATCHER:ProcessDetected( Detection )
self:F() self:F()
local AreaMsg = {} local AreaMsg = {}
local TaskMsg = {} local TaskMsg = {}
local ChangeMsg = {} local ChangeMsg = {}
local Mission = self.Mission local Mission = self.Mission
if Mission:IsIDLE() or Mission:IsENGAGED() then if Mission:IsIDLE() or Mission:IsENGAGED() then
local TaskReport = REPORT:New() local TaskReport = REPORT:New()
-- Checking the task queue for the dispatcher, and removing any obsolete task! -- Checking the task queue for the dispatcher, and removing any obsolete task!
for TaskIndex, TaskData in pairs( self.Tasks ) do for TaskIndex, TaskData in pairs( self.Tasks ) do
local Task = TaskData -- Tasking.Task#TASK local Task = TaskData -- Tasking.Task#TASK
@@ -526,18 +518,18 @@ do -- TASK_A2A_DISPATCHER
-- Now that all obsolete tasks are removed, loop through the detected targets. -- Now that all obsolete tasks are removed, loop through the detected targets.
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedCount = DetectedSet:Count() local DetectedCount = DetectedSet:Count()
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
--DetectedSet:Flush( self ) -- DetectedSet:Flush( self )
local DetectedID = DetectedItem.ID local DetectedID = DetectedItem.ID
local TaskIndex = DetectedItem.Index local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed local DetectedItemChanged = DetectedItem.Changed
local Task = self.Tasks[TaskIndex] local Task = self.Tasks[TaskIndex]
Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed. Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed.
@@ -560,7 +552,7 @@ do -- TASK_A2A_DISPATCHER
Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit ) Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem ) Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem ) Task:UpdateTaskInfo( DetectedItem )
end end
end end
end end
@@ -577,7 +569,7 @@ do -- TASK_A2A_DISPATCHER
function Task.OnEnterCancelled( Task, From, Event, To ) function Task.OnEnterCancelled( Task, From, Event, To )
self:Cancelled( Task ) self:Cancelled( Task )
end end
function Task.OnEnterFailed( Task, From, Event, To ) function Task.OnEnterFailed( Task, From, Event, To )
self:Failed( Task ) self:Failed( Task )
end end
@@ -585,38 +577,38 @@ do -- TASK_A2A_DISPATCHER
function Task.OnEnterAborted( Task, From, Event, To ) function Task.OnEnterAborted( Task, From, Event, To )
self:Aborted( Task ) self:Aborted( Task )
end end
TaskReport:Add( Task:GetName() ) TaskReport:Add( Task:GetName() )
else else
self:F("This should not happen") self:F( "This should not happen" )
end end
end end
if Task then if Task then
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" ) Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" )
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" )
end end
-- OK, so the tasking has been done, now delete the changes reported for the area. -- OK, so the tasking has been done, now delete the changes reported for the area.
Detection:AcceptChanges( DetectedItem ) Detection:AcceptChanges( DetectedItem )
end end
-- TODO set menus using the HQ coordinator -- TODO set menus using the HQ coordinator
Mission:GetCommandCenter():SetMenu() Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text(", ") local TaskText = TaskReport:Text( ", " )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end end
end end
end end
return true return true
end end

View File

@@ -1,13 +1,13 @@
--- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements. --- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements.
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- === -- ===
-- --
-- @module Tasking.Task_A2G -- @module Tasking.Task_A2G
-- @image MOOSE.JPG -- @image MOOSE.JPG
@@ -18,29 +18,29 @@ do -- TASK_A2G
-- @field Core.Set#SET_UNIT TargetSetUnit -- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK -- @extends Tasking.Task#TASK
--- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, --- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units,
-- based on the tasking capabilities defined in @{Tasking.Task#TASK}. -- based on the tasking capabilities defined in @{Tasking.Task#TASK}.
-- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: -- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
-- --
-- * **None**: Start of the process -- * **None**: Start of the process
-- * **Planned**: The A2G task is planned. -- * **Planned**: The A2G task is planned.
-- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}.
-- * **Success**: The A2G task is successfully completed. -- * **Success**: The A2G task is successfully completed.
-- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
-- --
-- ## 1) Set the scoring of achievements in an A2G attack. -- ## 1) Set the scoring of achievements in an A2G attack.
-- --
-- Scoring or penalties can be given in the following circumstances: -- Scoring or penalties can be given in the following circumstances:
-- --
-- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. -- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed.
-- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. -- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. -- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed.
-- --
-- @field #TASK_A2G -- @field #TASK_A2G
TASK_A2G = { TASK_A2G = {
ClassName = "TASK_A2G", ClassName = "TASK_A2G"
} }
--- Instantiates a new TASK_A2G. --- Instantiates a new TASK_A2G.
-- @param #TASK_A2G self -- @param #TASK_A2G self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -54,53 +54,51 @@ do -- TASK_A2G
function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing ) function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G
self:F() self:F()
self.TargetSetUnit = TargetSetUnit self.TargetSetUnit = TargetSetUnit
self.TaskType = TaskType self.TaskType = TaskType
local Fsm = self:GetUnitProcess() local Fsm = self:GetUnitProcess()
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
--Fsm:AddTransition( "Accounted", "Success", "Success" ) -- Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" ) Fsm:AddTransition( "Failed", "Fail", "Failed" )
--- Test
--- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task -- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterAssigned( TaskUnit, Task ) function Fsm:onafterAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit -- Determine the first Unit from the self.RendezVousSetUnit
self:RouteToRendezVous() self:RouteToRendezVous()
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task -- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit -- Determine the first Unit from the self.RendezVousSetUnit
if Task:GetRendezVousZone( TaskUnit ) then if Task:GetRendezVousZone( TaskUnit ) then
self:__RouteToRendezVousZone( 0.1 ) self:__RouteToRendezVousZone( 0.1 )
else else
@@ -112,36 +110,36 @@ do -- TASK_A2G
end end
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2G Task -- @param Tasking.Task#TASK_A2G Task
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit -- Determine the first Unit from the self.TargetSetUnit
self:__Engage( 0.1 ) self:__Engage( 0.1 )
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2G Task -- @param Tasking.Task#TASK_A2G Task
function Fsm:onafterEngage( TaskUnit, Task ) function Fsm:onafterEngage( TaskUnit, Task )
self:F( { self } ) self:F( { self } )
self:__Account( 0.1 ) self:__Account( 0.1 )
self:__RouteToTarget(0.1 ) self:__RouteToTarget( 0.1 )
self:__RouteToTargets( -10 ) self:__RouteToTargets( -10 )
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task -- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterRouteToTarget( TaskUnit, Task ) function Fsm:onafterRouteToTarget( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit -- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then if Task:GetTargetZone( TaskUnit ) then
self:__RouteToTargetZone( 0.1 ) self:__RouteToTargetZone( 0.1 )
else else
@@ -154,8 +152,8 @@ do -- TASK_A2G
self:__RouteToTargetPoint( 0.1 ) self:__RouteToTargetPoint( 0.1 )
end end
end end
--- Test --- Test
-- @param #FSM_PROCESS self -- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task -- @param Tasking.Task_A2G#TASK_A2G Task
@@ -167,20 +165,18 @@ do -- TASK_A2G
end end
self:__RouteToTargets( -10 ) self:__RouteToTargets( -10 )
end end
return self return self
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) function TASK_A2G:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit self.TargetSetUnit = TargetSetUnit
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
function TASK_A2G:GetPlannedMenuText() function TASK_A2G:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
@@ -190,34 +186,32 @@ do -- TASK_A2G
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
ActRouteRendezVous:SetRange( RendezVousRange ) ActRouteRendezVous:SetRange( RendezVousRange )
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
function TASK_A2G:GetRendezVousCoordinate( TaskUnit ) function TASK_A2G:GetRendezVousCoordinate( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
@@ -234,18 +228,17 @@ do -- TASK_A2G
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteRendezVous:GetZone() return ActRouteRendezVous:GetZone()
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteTarget:SetCoordinate( TargetCoordinate ) ActRouteTarget:SetCoordinate( TargetCoordinate )
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
@@ -258,18 +251,16 @@ do -- TASK_A2G
return ActRouteTarget:GetCoordinate() return ActRouteTarget:GetCoordinate()
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) function TASK_A2G:SetTargetZone( TargetZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteTarget:SetZone( TargetZone ) ActRouteTarget:SetZone( TargetZone )
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
@@ -283,47 +274,46 @@ do -- TASK_A2G
end end
function TASK_A2G:SetGoalTotal() function TASK_A2G:SetGoalTotal()
self.GoalTotal = self.TargetSetUnit:Count() self.GoalTotal = self.TargetSetUnit:Count()
end end
function TASK_A2G:GetGoalTotal() function TASK_A2G:GetGoalTotal()
return self.GoalTotal return self.GoalTotal
end end
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. --- 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 -- @param #TASK_A2G self
function TASK_A2G:ReportOrder( ReportGroup ) function TASK_A2G:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem ) self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance return Distance
end end
--- This method checks every 10 seconds if the goal has been reached of the task. --- This method checks every 10 seconds if the goal has been reached of the task.
-- @param #TASK_A2G self -- @param #TASK_A2G self
function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) function TASK_A2G:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then if TargetSetUnit:Count() == 0 then
self:Success() self:Success()
end end
self:__Goal( -10 ) self:__Goal( -10 )
end end
--- @param #TASK_A2G self --- @param #TASK_A2G self
function TASK_A2G:UpdateTaskInfo( DetectedItem ) function TASK_A2G:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then if self:IsStatePlanned() or self:IsStateAssigned() then
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddTaskName( 0, "MSOD" )
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
local ThreatLevel, ThreatText local ThreatLevel, ThreatText
if DetectedItem then if DetectedItem then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem )
@@ -331,7 +321,7 @@ do -- TASK_A2G
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end end
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
if self.Detection then if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count() local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New() local ReportTypes = REPORT:New()
@@ -344,33 +334,33 @@ do -- TASK_A2G
end end
end end
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
else else
local DetectedItemsCount = self.TargetSetUnit:Count() local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
end end
self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" ) self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" )
self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" ) self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" )
self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" ) self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" )
end end
end end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_A2G self -- @param #TASK_A2G self
-- @param #number AutoAssignMethod The method to be applied to the task. -- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group. -- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 ) return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
self:F({Distance=Distance}) self:F( { Distance = Distance } )
return math.floor( Distance ) return math.floor( Distance )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1 return 1
@@ -379,8 +369,7 @@ do -- TASK_A2G
return 0 return 0
end end
end end
do -- TASK_A2G_SEAD do -- TASK_A2G_SEAD
@@ -397,9 +386,9 @@ do -- TASK_A2G_SEAD
-- --
-- @field #TASK_A2G_SEAD -- @field #TASK_A2G_SEAD
TASK_A2G_SEAD = { TASK_A2G_SEAD = {
ClassName = "TASK_A2G_SEAD", ClassName = "TASK_A2G_SEAD"
} }
--- Instantiates a new TASK_A2G_SEAD. --- Instantiates a new TASK_A2G_SEAD.
-- @param #TASK_A2G_SEAD self -- @param #TASK_A2G_SEAD self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -408,19 +397,16 @@ do -- TASK_A2G_SEAD
-- @param Core.Set#SET_UNIT TargetSetUnit -- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task. -- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2G_SEAD self -- @return #TASK_A2G_SEAD self
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing) function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD
self:F() self:F()
Mission:AddTask( self ) Mission:AddTask( self )
self:SetBriefing( self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." )
TaskBriefing or
"Execute a Suppression of Enemy Air Defenses."
)
return self return self
end end
--- Set a score when a target in scope of the A2G attack, has been destroyed . --- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_SEAD self -- @param #TASK_A2G_SEAD self
@@ -434,7 +420,7 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score )
return self return self
end end
@@ -450,7 +436,7 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score ) ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score )
return self return self
end end
@@ -466,11 +452,10 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty )
return self return self
end end
end end
do -- TASK_A2G_BAI do -- TASK_A2G_BAI
@@ -488,10 +473,8 @@ do -- TASK_A2G_BAI
-- based on detected enemy ground targets. -- based on detected enemy ground targets.
-- --
-- @field #TASK_A2G_BAI -- @field #TASK_A2G_BAI
TASK_A2G_BAI = { TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" }
ClassName = "TASK_A2G_BAI",
}
--- Instantiates a new TASK_A2G_BAI. --- Instantiates a new TASK_A2G_BAI.
-- @param #TASK_A2G_BAI self -- @param #TASK_A2G_BAI self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -503,14 +486,11 @@ do -- TASK_A2G_BAI
function TASK_A2G_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) function TASK_A2G_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI", TaskBriefing ) ) -- #TASK_A2G_BAI local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI", TaskBriefing ) ) -- #TASK_A2G_BAI
self:F() self:F()
Mission:AddTask( self ) Mission:AddTask( self )
self:SetBriefing( self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." )
TaskBriefing or
"Execute a Battlefield Air Interdiction of a group of enemy targets."
)
return self return self
end end
@@ -526,7 +506,7 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score )
return self return self
end end
@@ -542,7 +522,7 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score ) ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score )
return self return self
end end
@@ -558,15 +538,12 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty )
return self return self
end end
end end
do -- TASK_A2G_CAS do -- TASK_A2G_CAS
--- The TASK_A2G_CAS class --- The TASK_A2G_CAS class
@@ -581,10 +558,8 @@ do -- TASK_A2G_CAS
-- based on detected enemy ground targets. -- based on detected enemy ground targets.
-- --
-- @field #TASK_A2G_CAS -- @field #TASK_A2G_CAS
TASK_A2G_CAS = { TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" }
ClassName = "TASK_A2G_CAS",
}
--- Instantiates a new TASK_A2G_CAS. --- Instantiates a new TASK_A2G_CAS.
-- @param #TASK_A2G_CAS self -- @param #TASK_A2G_CAS self
-- @param Tasking.Mission#MISSION Mission -- @param Tasking.Mission#MISSION Mission
@@ -596,19 +571,13 @@ do -- TASK_A2G_CAS
function TASK_A2G_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) function TASK_A2G_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS", TaskBriefing ) ) -- #TASK_A2G_CAS local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS", TaskBriefing ) ) -- #TASK_A2G_CAS
self:F() self:F()
Mission:AddTask( self )
self:SetBriefing(
TaskBriefing or
"Execute a Close Air Support for a group of enemy targets. " ..
"Beware of friendlies at the vicinity! "
)
Mission:AddTask( self )
self:SetBriefing( TaskBriefing or ( "Execute a Close Air Support for a group of enemy targets. " .. "Beware of friendlies at the vicinity! " ) )
return self return self
end end
--- Set a score when a target in scope of the A2G attack, has been destroyed . --- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_CAS self -- @param #TASK_A2G_CAS self
@@ -622,7 +591,7 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score )
return self return self
end end
@@ -638,7 +607,7 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score ) ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score )
return self return self
end end
@@ -654,9 +623,8 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty ) ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty )
return self return self
end end
end end

View File

@@ -1,24 +1,25 @@
--- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. --- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance.
-- --
-- **Features:** -- **Features:**
-- --
-- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically assign tasks to human players based on detected targets.
-- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically change the tasks as the tactical situation evolves during the mission.
-- * Dynamically assign (CAS) Close Air Support tasks for human players. -- * Dynamically assign (CAS) Close Air Support tasks for human players.
-- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players. -- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players.
-- * Dynamically assign (SEAD) Supression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. -- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats.
-- * Define and use an EWR (Early Warning Radar) network. -- * Define and use an EWR (Early Warning Radar) network.
-- * Define different ranges to engage upon intruders. -- * Define different ranges to engage upon intruders.
-- * Keep task achievements. -- * Keep task achievements.
-- * Score task achievements.-- -- * Score task achievements.
--
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- === -- ===
-- --
-- @module Tasking.Task_A2G_Dispatcher -- @module Tasking.Task_A2G_Dispatcher
-- @image Task_A2G_Dispatcher.JPG -- @image Task_A2G_Dispatcher.JPG
@@ -32,151 +33,151 @@ do -- TASK_A2G_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER -- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object. --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object.
-- --
-- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system.
-- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon,
-- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**. -- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**.
-- --
-- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Set} of @{Wrapper.Group}s that will be manned by **Players**. -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Set} of @{Wrapper.Group}s that will be manned by **Players**.
-- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Wrapper.Group} @{Set}. -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Wrapper.Group} @{Set}.
-- --
-- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation. -- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation.
-- The detection object will group the detected targets by its grouping method, and integrates a @{Set} of @{Wrapper.Group}s that are Recce vehicles or air units. -- The detection object will group the detected targets by its grouping method, and integrates a @{Set} of @{Wrapper.Group}s that are Recce vehicles or air units.
-- We call this the **RecceSet** of the A2G dispatcher. -- We call this the **RecceSet** of the A2G dispatcher.
-- --
-- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet.. -- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet..
-- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER: -- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER:
-- --
-- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area. -- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area.
-- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. -- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy.
-- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. -- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy.
-- --
-- # 0. Tactical Situations -- # 0. Tactical Situations
-- --
-- This chapters provides some insights in the tactical situations when certain Task Types are created. -- This chapters provides some insights in the tactical situations when certain Task Types are created.
-- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units. -- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG)
-- --
-- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test], -- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test],
-- the tactical situation is a demonstration how the A2G detection works. -- the tactical situation is a demonstration how the A2G detection works.
-- This example will be taken further in the explanation in the following chapters. -- This example will be taken further in the explanation in the following chapters.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG)
-- --
-- The red coalition are the players, the blue coalition is the enemy. -- The red coalition are the players, the blue coalition is the enemy.
-- --
-- Red reconnaissance vehicles and airborne units are detecting the targets. -- Red reconnaissance vehicles and airborne units are detecting the targets.
-- We call this the RecceSet as explained above, which is a Set of Groups that -- We call this the RecceSet as explained above, which is a Set of Groups that
-- have a group name starting with `Recce` (configured in the mission script). -- have a group name starting with `Recce` (configured in the mission script).
-- --
-- Red attack units are responsible for executing the mission for the command center. -- Red attack units are responsible for executing the mission for the command center.
-- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script). -- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script).
-- These units are setup in this demonstration mission to be ground vehicles and airplanes. -- These units are setup in this demonstration mission to be ground vehicles and airplanes.
-- For demonstration purposes, the attack airplane is stationed on the ground to explain -- For demonstration purposes, the attack airplane is stationed on the ground to explain
-- the messages and the menus properly. -- the messages and the menus properly.
-- Further test missions demonstrate the A2G task dispatcher from within air. -- Further test missions demonstrate the A2G task dispatcher from within air.
-- --
-- Depending upon the detection results, the A2G dispatcher will create different tasks. -- Depending upon the detection results, the A2G dispatcher will create different tasks.
-- --
-- # 0.1. SEAD Task -- # 0.1. SEAD Task
-- --
-- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area. -- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG)
-- --
-- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task! -- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task!
-- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area! -- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area!
-- --
-- # 0.2. CAS Task -- # 0.2. CAS Task
-- --
-- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. -- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG)
-- --
-- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task! -- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task!
-- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support. -- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support.
-- --
-- # 0.3. BAI Task -- # 0.3. BAI Task
-- --
-- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. -- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG)
-- --
-- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range! -- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range!
-- --
-- # 1. Player Experience -- # 1. Player Experience
-- --
-- The A2G dispatcher is residing under a @{CommandCenter}, which is orchestrating a @{Mission}. -- The A2G dispatcher is residing under a @{CommandCenter}, which is orchestrating a @{Mission}.
-- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**. -- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**.
-- --
-- For example, if there are 2 Command Centers (CC). -- For example, if there are 2 Command Centers (CC).
-- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this: -- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha (Primary)" -- F1. Mission "Alpha (Primary)"
-- F2. Mission "Beta (Secondary)" -- F2. Mission "Beta (Secondary)"
-- F3. Mission "Gamma (Tactical)" -- F3. Mission "Gamma (Tactical)"
-- F1. Command Center [Lima] -- F1. Command Center [Lima]
-- F1. Mission "Overlord (High)" -- F1. Mission "Overlord (High)"
-- --
-- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tacical mission Gamma. -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma.
-- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority. -- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority.
-- --
-- ## 1.1. Mission Menu (Under the Command Center Menu) -- ## 1.1. Mission Menu (Under the Command Center Menu)
-- --
-- The Mission Menu controls the information of the mission, including the: -- The Mission Menu controls the information of the mission, including the:
-- --
-- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message. -- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message.
-- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker. -- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker.
-- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher. -- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher.
-- - **Create Mission Reports**: A menu to create various reports on the current mission. -- - **Create Mission Reports**: A menu to create various reports on the current mission.
-- --
-- For CC [Lima], Mission "Overlord", the menu structure could look like this: -- For CC [Lima], Mission "Overlord", the menu structure could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Lima] -- F1. Command Center [Lima]
-- F1. Mission "Overlord" -- F1. Mission "Overlord"
-- F1. Mission Briefing -- F1. Mission Briefing
-- F2. Mark Task Locations on Map -- F2. Mark Task Locations on Map
-- F3. Task Reports -- F3. Task Reports
-- F4. Mission Reports -- F4. Mission Reports
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG)
-- --
-- ### 1.1.1. Mission Briefing Menu -- ### 1.1.1. Mission Briefing Menu
-- --
-- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations. -- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations.
-- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation, -- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation,
-- and explains the mission goals. -- and explains the mission goals.
-- --
-- --
-- ### 1.1.2. Mark Task Locations Menu -- ### 1.1.2. Mark Task Locations Menu
-- --
-- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center. -- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center.
-- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant. -- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant.
-- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked. -- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked.
-- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness. -- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness.
-- --
-- ### 1.1.3. Task Reports Menu -- ### 1.1.3. Task Reports Menu
-- --
-- The Task Reports Menu is a sub menu, that allows to create various reports: -- The Task Reports Menu is a sub menu, that allows to create various reports:
-- --
-- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status. -- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status.
-- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed. -- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed.
-- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed. -- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed.
-- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully. -- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully.
-- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed. -- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed.
-- --
-- The information shown of the tasks will vary according the underlying task type, but are self explanatory. -- The information shown of the tasks will vary according the underlying task type, but are self explanatory.
-- --
-- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this: -- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -188,21 +189,21 @@ do -- TASK_A2G_DISPATCHER
-- F4. Successful Tasks -- F4. Successful Tasks
-- F5. Failed Tasks -- F5. Failed Tasks
-- F4. Mission Reports -- F4. Mission Reports
-- --
-- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu. -- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu.
-- (See later). -- (See later).
-- --
-- ### 1.1.4. Mission Reports Menu -- ### 1.1.4. Mission Reports Menu
-- --
-- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission: -- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission:
-- --
-- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a %-tage of completion. -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion.
-- - **Report Players per Task**: Show which players are engaged on which Task within the Mission. -- - **Report Players per Task**: Show which players are engaged on which Task within the Mission.
-- --
-- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this: -- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -211,25 +212,25 @@ do -- TASK_A2G_DISPATCHER
-- F4. Mission Reports -- F4. Mission Reports
-- F1. Report Mission Progress -- F1. Report Mission Progress
-- F2. Report Players per Task -- F2. Report Players per Task
-- --
-- --
-- ## 1.2. Task Management Menus -- ## 1.2. Task Management Menus
-- --
-- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!** -- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!**
-- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task. -- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task.
-- Depending on whether a Task has been Planned or Assigned to a Player (Group), -- Depending on whether a Task has been Planned or Assigned to a Player (Group),
-- **the Mission Menu will contain extra Menus to control specific Tasks.** -- **the Mission Menu will contain extra Menus to control specific Tasks.**
-- --
-- #### 1.2.1. Join a Planned Task -- #### 1.2.1. Join a Planned Task
-- --
-- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a: -- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a:
-- --
-- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned). -- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned).
-- --
-- For CC |Gori|, Mission "Alpha", the menu structure could look like this: -- For CC |Gori|, Mission "Alpha", the menu structure could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -237,23 +238,23 @@ do -- TASK_A2G_DISPATCHER
-- F3. Task Reports -- F3. Task Reports
-- F4. Mission Reports -- F4. Mission Reports
-- F5. Join Planned Task -- F5. Join Planned Task
-- --
-- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.** -- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.**
-- --
-- #### 1.2.2. Manage an Assigned Task -- #### 1.2.2. Manage an Assigned Task
-- --
-- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra: -- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra:
-- --
-- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task. -- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task.
-- --
-- In this example, the Group currently seated by the player is not assigned yet to a Task. -- In this example, the Group currently seated by the player is not assigned yet to a Task.
-- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha". -- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha".
-- --
-- This would be an example menu structure, -- This would be an example menu structure,
-- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001: -- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -261,26 +262,25 @@ do -- TASK_A2G_DISPATCHER
-- F3. Task Reports -- F3. Task Reports
-- F4. Mission Reports -- F4. Mission Reports
-- F5. Assigned Task CAS.001 -- F5. Assigned Task CAS.001
-- --
-- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.** -- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.**
-- --
--
-- ## 1.3. Join Planned Task Menu -- ## 1.3. Join Planned Task Menu
-- --
-- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**. -- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**.
-- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**.
-- --
-- For example, for CC [Gori], Mission "Alpha", -- For example, for CC [Gori], Mission "Alpha",
-- if a Mission "ALpha" contains 5 Planned Tasks, which would be: -- if a Mission "ALpha" contains 5 Planned Tasks, which would be:
-- --
-- - 2 CAS Tasks -- - 2 CAS Tasks
-- - 1 BAI Task -- - 1 BAI Task
-- - 2 SEAD Tasks -- - 2 SEAD Tasks
-- --
-- the Join Planned Task Menu Hierarchy could look like this: -- the Join Planned Task Menu Hierarchy could look like this:
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center [Gori] -- F1. Command Center [Gori]
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -296,26 +296,26 @@ do -- TASK_A2G_DISPATCHER
-- F1. SEAD.003 -- F1. SEAD.003
-- F2. SEAD.004 -- F2. SEAD.004
-- F3. SEAD.005 -- F3. SEAD.005
-- --
-- An example from within a running simulation: -- An example from within a running simulation:
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG)
-- --
-- Each Task Type Menu would have a list of the Task Menus underneath. -- Each Task Type Menu would have a list of the Task Menus underneath.
-- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**! -- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**!
-- --
-- ### 1.3.1. Planned Task Menu -- ### 1.3.1. Planned Task Menu
-- --
-- Each Planned Task Menu will allow for the following actions: -- Each Planned Task Menu will allow for the following actions:
-- --
-- - Report Task Details: Provides a detailed report on the Planned Task. -- - Report Task Details: Provides a detailed report on the Planned Task.
-- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant. -- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant.
-- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission. -- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission.
-- --
-- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha":
-- --
-- Radio MENU Structure (F10. Other) -- Radio MENU Structure (F10. Other)
-- --
-- F1. Command Center |Gori| -- F1. Command Center |Gori|
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -328,22 +328,22 @@ do -- TASK_A2G_DISPATCHER
-- F1. Report Task Details -- F1. Report Task Details
-- F2. Mark Task Location on Map -- F2. Mark Task Location on Map
-- F3. Join Task -- F3. Join Task
-- --
-- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.** -- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.**
-- --
-- --
-- ## 1.4. Assigned Task Menu -- ## 1.4. Assigned Task Menu
-- --
-- The Assigned Task Menu allows to control the **current assigned task** within the Mission. -- The Assigned Task Menu allows to control the **current assigned task** within the Mission.
-- --
-- Depending on the Type of Task, the following menu options will be available: -- Depending on the Type of Task, the following menu options will be available:
-- --
-- - **Report Task Details**: Provides a detailed report on the Planned Task. -- - **Report Task Details**: Provides a detailed report on the Planned Task.
-- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant. -- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant.
-- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task. -- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task.
-- --
-- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be: -- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be:
-- --
-- F1. Command Center |Gori| -- F1. Command Center |Gori|
-- F1. Mission "Alpha" -- F1. Mission "Alpha"
-- F1. Mission Briefing -- F1. Mission Briefing
@@ -354,90 +354,89 @@ do -- TASK_A2G_DISPATCHER
-- F1. Report Task Details -- F1. Report Task Details
-- F2. Mark Task Location on Map -- F2. Mark Task Location on Map
-- F3. Abort Task -- F3. Abort Task
-- --
-- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**. -- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**.
-- However, this will depend on the setup of each Mission. -- However, this will depend on the setup of each Mission.
-- --
-- ## 1.5. Messages -- ## 1.5. Messages
-- --
-- During game play, different messages are displayed. -- During game play, different messages are displayed.
-- These messages provide an update of the achievements made, and the state wherein the task is. -- These messages provide an update of the achievements made, and the state wherein the task is.
-- --
-- The various reports can be used also to retrieve the current status of the mission and its tasks. -- The various reports can be used also to retrieve the current status of the mission and its tasks.
-- --
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG) -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG)
-- --
-- The @{Settings} menu provides additional options to control the timing of the messages. -- The @{Settings} menu provides additional options to control the timing of the messages.
-- There are: -- There are:
-- --
-- - Status messages, which are quick status updates. The settings menu allows to switch off these messages. -- - Status messages, which are quick status updates. The settings menu allows to switch off these messages.
-- - Information messages, which are shown a bit longer, as they contain important information. -- - Information messages, which are shown a bit longer, as they contain important information.
-- - Summary reports, which are quick reports showing a high level summary. -- - Summary reports, which are quick reports showing a high level summary.
-- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read. -- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read.
-- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer. -- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer.
-- --
-- # 2. TASK\_A2G\_DISPATCHER constructor -- # 2. TASK\_A2G\_DISPATCHER constructor
-- --
-- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance. -- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance.
-- --
-- # 3. Usage -- # 3. Usage
-- --
-- To use the TASK\_A2G\_DISPATCHER class, you need: -- To use the TASK\_A2G\_DISPATCHER class, you need:
-- --
-- - A @{CommandCenter} object. The master communication channel. -- - A @{CommandCenter} object. The master communication channel.
-- - A @{Mission} object. Each task belongs to a Mission. -- - A @{Mission} object. Each task belongs to a Mission.
-- - A @{Detection} object. There are several detection grouping methods to choose from. -- - A @{Detection} object. There are several detection grouping methods to choose from.
-- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher. -- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher.
-- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object. -- - A @{Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Detection} object.
-- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object. -- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object.
--
-- Below an example mission declaration that is defines a Task A2G Dispatcher object.
-- --
-- -- Declare the Command Center -- Below an example mission declaration that is defines a Task A2G Dispatcher object.
--
-- -- Declare the Command Center
-- local HQ = GROUP -- local HQ = GROUP
-- :FindByName( "HQ", "Bravo HQ" ) -- :FindByName( "HQ", "Bravo HQ" )
-- --
-- local CommandCenter = COMMANDCENTER -- local CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" ) -- :New( HQ, "Lima" )
-- --
-- -- Declare the Mission for the Command Center. -- -- Declare the Mission for the Command Center.
-- local Mission = MISSION -- local Mission = MISSION
-- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ) -- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED )
-- --
-- -- Define the RecceSet that will detect the enemy. -- -- Define the RecceSet that will detect the enemy.
-- local RecceSet = SET_GROUP -- local RecceSet = SET_GROUP
-- :New() -- :New()
-- :FilterPrefixes( "FAC" ) -- :FilterPrefixes( "FAC" )
-- :FilterCoalitions("red") -- :FilterCoalitions("red")
-- :FilterStart() -- :FilterStart()
-- --
-- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius. -- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius.
-- local DetectionAreas = DETECTION_AREAS -- local DetectionAreas = DETECTION_AREAS
-- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies. -- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies.
-- --
-- -- Setup the AttackSet, which is a SET_GROUP. -- -- Setup the AttackSet, which is a SET_GROUP.
-- -- The SET_GROUP is a dynamic collection of GROUP objects. -- -- The SET_GROUP is a dynamic collection of GROUP objects.
-- local AttackSet = SET_GROUP -- local AttackSet = SET_GROUP
-- :New() -- Create the SET_GROUP object. -- :New() -- Create the SET_GROUP object.
-- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions. -- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions.
-- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack. -- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack.
-- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack. -- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack.
-- --
-- -- Now we have everything to setup the main A2G TaskDispatcher. -- -- Now we have everything to setup the main A2G TaskDispatcher.
-- TaskDispatcher = TASK_A2G_DISPATCHER -- TaskDispatcher = TASK_A2G_DISPATCHER
-- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will recieve the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher.
-- --
-- --
-- --
-- @field #TASK_A2G_DISPATCHER -- @field #TASK_A2G_DISPATCHER
TASK_A2G_DISPATCHER = { TASK_A2G_DISPATCHER = {
ClassName = "TASK_A2G_DISPATCHER", ClassName = "TASK_A2G_DISPATCHER",
Mission = nil, Mission = nil,
Detection = nil, Detection = nil,
Tasks = {}, Tasks = {}
} }
--- TASK_A2G_DISPATCHER constructor. --- TASK_A2G_DISPATCHER constructor.
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
@@ -445,17 +444,18 @@ do -- TASK_A2G_DISPATCHER
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
-- @return #TASK_A2G_DISPATCHER self -- @return #TASK_A2G_DISPATCHER self
function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection )
-- Inherits from DETECTION_MANAGER -- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER
self.Detection = Detection self.Detection = Detection
self.Mission = Mission self.Mission = Mission
self.FlashNewTask = true -- set to false to suppress flash messages
self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } )
self:AddTransition( "Started", "Assign", "Started" ) self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign. --- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign -- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
@@ -465,13 +465,19 @@ do -- TASK_A2G_DISPATCHER
-- @param Tasking.Task_A2G#TASK_A2G Task -- @param Tasking.Task_A2G#TASK_A2G Task
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName -- @param #string PlayerName
self:__Start( 5 ) self:__Start( 5 )
return self return self
end end
--- Set flashing player messages on or off
-- @param #TASK_A2G_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function TASK_A2G_DISPATCHER:SetSendMessages( onoff )
self.FlashNewTask = onoff
end
--- Creates a SEAD task when there are targets for it. --- Creates a SEAD task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
@@ -479,7 +485,7 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
@@ -493,10 +499,10 @@ do -- TASK_A2G_DISPATCHER
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterHasSEAD() TargetSetUnit:FilterHasSEAD()
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
@@ -507,11 +513,10 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
-- Determine if the set has ground units. -- Determine if the set has ground units.
-- There should be ground unit friendlies nearby. Airborne units are valid friendlies types. -- There should be ground unit friendlies nearby. Airborne units are valid friendlies types.
-- And there shouldn't be any radar. -- And there shouldn't be any radar.
@@ -525,13 +530,13 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = SET_UNIT:New() local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
--- Creates a BAI task when there are targets for it. --- Creates a BAI task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
@@ -539,11 +544,10 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set. -- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition )
self:F( { DetectedItem.ItemID } ) self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
-- Determine if the set has ground units. -- Determine if the set has ground units.
-- There shouldn't be any ground unit friendlies nearby. -- There shouldn't be any ground unit friendlies nearby.
-- And there shouldn't be any radar. -- And there shouldn't be any radar.
@@ -557,19 +561,18 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = SET_UNIT:New() local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit return TargetSetUnit
end end
return nil return nil
end end
function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex ) function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] ) self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil self.Tasks[TaskIndex] = nil
end end
--- Evaluates the removal of the Task from the Mission. --- Evaluates the removal of the Task from the Mission.
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
@@ -579,17 +582,16 @@ do -- TASK_A2G_DISPATCHER
-- @param #boolean DetectedItemChange -- @param #boolean DetectedItemChange
-- @return Tasking.Task#TASK -- @return Tasking.Task#TASK
function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged )
if Task then if Task then
if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then
--self:F( "Removing Tasking: " .. Task:GetTaskName() ) -- self:F( "Removing Tasking: " .. Task:GetTaskName() )
self:RemoveTask( TaskIndex ) self:RemoveTask( TaskIndex )
end end
end end
return Task return Task
end end
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
-- @param #TASK_A2G_DISPATCHER self -- @param #TASK_A2G_DISPATCHER self
@@ -597,15 +599,15 @@ do -- TASK_A2G_DISPATCHER
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) function TASK_A2G_DISPATCHER:ProcessDetected( Detection )
self:F() self:F()
local AreaMsg = {} local AreaMsg = {}
local TaskMsg = {} local TaskMsg = {}
local ChangeMsg = {} local ChangeMsg = {}
local Mission = self.Mission local Mission = self.Mission
if Mission:IsIDLE() or Mission:IsENGAGED() then if Mission:IsIDLE() or Mission:IsENGAGED() then
local TaskReport = REPORT:New() local TaskReport = REPORT:New()
-- Checking the task queue for the dispatcher, and removing any obsolete task! -- Checking the task queue for the dispatcher, and removing any obsolete task!
@@ -616,7 +618,9 @@ do -- TASK_A2G_DISPATCHER
if not DetectedItem then if not DetectedItem then
local TaskText = Task:GetName() local TaskText = Task:GetName()
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) if self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
end
end end
Task = self:RemoveTask( TaskIndex ) Task = self:RemoveTask( TaskIndex )
end end
@@ -625,21 +629,21 @@ do -- TASK_A2G_DISPATCHER
--- First we need to the detected targets. --- First we need to the detected targets.
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedZone = DetectedItem.Zone local DetectedZone = DetectedItem.Zone
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
--DetectedSet:Flush( self ) -- DetectedSet:Flush( self )
local DetectedItemID = DetectedItem.ID local DetectedItemID = DetectedItem.ID
local TaskIndex = DetectedItem.Index local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed local DetectedItemChanged = DetectedItem.Changed
self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } ) self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } )
local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G
if Task then if Task then
-- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets. -- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets.
if Task:IsStateAssigned() then if Task:IsStateAssigned() then
@@ -651,7 +655,7 @@ do -- TASK_A2G_DISPATCHER
Task:SetTargetSetUnit( TargetSetUnit ) Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, DetectedItem ) Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem ) Task:UpdateTaskInfo( DetectedItem )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else else
Task:Cancel() Task:Cancel()
end end
@@ -682,18 +686,18 @@ do -- TASK_A2G_DISPATCHER
end end
end end
end end
-- Now we send to each group the changes, if any. -- Now we send to each group the changes, if any.
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
local TargetsText = TargetsReport:Text(", ") local TargetsText = TargetsReport:Text( ", " )
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" then if (Mission:IsGroupAssigned( TaskGroup )) and TargetsText ~= "" and self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup ) Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup )
end end
end end
end end
end end
end end
if Task then if Task then
if Task:IsStatePlanned() then if Task:IsStatePlanned() then
if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set
@@ -744,6 +748,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
if TargetSetUnit then if TargetSetUnit then
Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit )
DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
Task:SetDetection( Detection, DetectedItem ) Task:SetDetection( Detection, DetectedItem )
end end
@@ -752,6 +757,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
if TargetSetUnit then if TargetSetUnit then
Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit )
DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
Task:SetDetection( Detection, DetectedItem ) Task:SetDetection( Detection, DetectedItem )
end end
@@ -760,18 +766,19 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed...
if TargetSetUnit then if TargetSetUnit then
Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit )
DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
Task:SetDetection( Detection, DetectedItem ) Task:SetDetection( Detection, DetectedItem )
end end
end end
end end
if Task then if Task then
self.Tasks[TaskIndex] = Task self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone ) Task:SetTargetZone( DetectedZone )
Task:SetDispatcher( self ) Task:SetDispatcher( self )
Task:UpdateTaskInfo( DetectedItem ) Task:UpdateTaskInfo( DetectedItem )
Mission:AddTask( Task ) Mission:AddTask( Task )
function Task.OnEnterSuccess( Task, From, Event, To ) function Task.OnEnterSuccess( Task, From, Event, To )
self:Success( Task ) self:Success( Task )
end end
@@ -779,7 +786,7 @@ do -- TASK_A2G_DISPATCHER
function Task.OnEnterCancelled( Task, From, Event, To ) function Task.OnEnterCancelled( Task, From, Event, To )
self:Cancelled( Task ) self:Cancelled( Task )
end end
function Task.OnEnterFailed( Task, From, Event, To ) function Task.OnEnterFailed( Task, From, Event, To )
self:Failed( Task ) self:Failed( Task )
end end
@@ -787,32 +794,30 @@ do -- TASK_A2G_DISPATCHER
function Task.OnEnterAborted( Task, From, Event, To ) function Task.OnEnterAborted( Task, From, Event, To )
self:Aborted( Task ) self:Aborted( Task )
end end
TaskReport:Add( Task:GetName() ) TaskReport:Add( Task:GetName() )
else else
self:F("This should not happen") self:F( "This should not happen" )
end end
end end
-- OK, so the tasking has been done, now delete the changes reported for the area. -- OK, so the tasking has been done, now delete the changes reported for the area.
Detection:AcceptChanges( DetectedItem ) Detection:AcceptChanges( DetectedItem )
end end
-- TODO set menus using the HQ coordinator -- TODO set menus using the HQ coordinator
Mission:GetCommandCenter():SetMenu() Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text(", ") local TaskText = TaskReport:Text( ", " )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end end
end end
end end
return true return true
end end
end end

View File

@@ -83,7 +83,7 @@ do -- TASK_CAPTURE_DISPATCHER
-- ## 1.1. Create a command center. -- ## 1.1. Create a command center.
-- --
-- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. -- 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. -- 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. -- This group will act as the command center object.
-- It is a good practice to mark this group as invisible and invulnerable. -- It is a good practice to mark this group as invisible and invulnerable.
-- --
@@ -184,6 +184,7 @@ do -- TASK_CAPTURE_DISPATCHER
local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER
self.Mission = Mission self.Mission = Mission
self.FlashNewTask = false
self:AddTransition( "Started", "Assign", "Started" ) self:AddTransition( "Started", "Assign", "Started" )
self:AddTransition( "Started", "ZoneCaptured", "Started" ) self:AddTransition( "Started", "ZoneCaptured", "Started" )
@@ -383,7 +384,7 @@ do -- TASK_CAPTURE_DISPATCHER
local TaskText = TaskReport:Text(", ") local TaskText = TaskReport:Text(", ")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not self.FlashNewTask ) then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end end
end end

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