Compare commits

...

325 Commits

Author SHA1 Message Date
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
73 changed files with 32164 additions and 19668 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'))
install:
- cmd:
# 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 lua51
# - call choco install luarocks
# - call refreshenv
# - 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: 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
# - call luarocks install luasrcdiet
# - call luarocks install checks
# - call luarocks install luadocumentor
# - call luarocks install luacheck
call choco install 7zip.commandline
call choco install lua51
call choco install luarocks
call refreshenv
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: 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
call luarocks install luasrcdiet
call luarocks install checks
call luarocks install luadocumentor
call luarocks install luacheck
#cache:
# - C:\ProgramData\chocolatey\lib
# - C:\ProgramData\chocolatey\bin
cache:
C:\ProgramData\chocolatey\lib
C:\ProgramData\chocolatey\bin
@@ -51,8 +52,9 @@ build_script:
- ps: |
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{
echo "Hello World!"
$apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw'
$token = 'v2.6hcv3ige78kg3yvg4ge8'
$headers = @{
"Authorization" = "Bearer $token"
"Content-type" = "application/json"
@@ -65,7 +67,7 @@ build_script:
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{
$apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw'
$token = 'v2.6hcv3ige78kg3yvg4ge8'
$headers = @{
"Authorization" = "Bearer $token"
"Content-type" = "application/json"

2
.gitignore vendored
View File

@@ -18,6 +18,8 @@ local.properties
# External tool builders
.externalToolBuilders/
# AppVeyor
.appveyor/
# CDT-specific
.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.
}

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

@@ -143,7 +143,7 @@ end
-- @return #AI_A2A_CAP
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType )
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
end

File diff suppressed because it is too large Load Diff

View File

@@ -585,7 +585,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
if AIGroup and AIGroup:IsAlive() then
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
--AIGroup:ClearTasks()

View File

@@ -47,19 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet )
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
-- Board
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" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Loaded", "Board", "Loaded" )
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" )
-- Unload
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unboard", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
--- Pickup Handler OnBefore for AI_CARGO
-- @function [parent=#AI_CARGO] OnBeforePickup
@@ -393,7 +395,7 @@ end
function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone )
self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } )
if Carrier and Carrier:IsAlive() and From == "Boarding" then
if Carrier and Carrier:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
@@ -509,7 +511,7 @@ end
function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
if Carrier and Carrier:IsAlive() and From == "Unboarding" then
if Carrier and Carrier:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
return
@@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
end
end

View File

@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Home", "*" )
self:AddTransition( "*", "Reload", "Boarding" )
self:AddTransition( "*", "Deployed", "*" )
self:AddTransition( "*", "PickedUp", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
self:SetCombatRadius( CombatRadius )

View File

@@ -33,7 +33,7 @@
--
-- ## 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.
-- 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.
@@ -64,32 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" )
-- Boarding
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
--[[
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
self:AddTransition( "Boarding", "Deploy", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
self:AddTransition( "Unboarding", "Pickup", "Unloaded" )
--]]
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Unboarding", "Pickup", "Unloaded" )
self:AddTransition( "Unloaded", "Unboard", "Unloaded" )
self:AddTransition( "Unloaded", "Unloaded", "Unloaded" )
self:AddTransition( "*", "PickedUp", "*" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
-- Unboarding
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Destroyed", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
-- RTB
self:AddTransition( "*", "Home" , "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
@@ -107,13 +99,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event
-- @param #string To
-- @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
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @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
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
@@ -129,7 +139,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
-- @param #number Speed Speed in km/h to 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
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
@@ -139,21 +149,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
-- @param #string Event
-- @param #string To
-- @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
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to 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
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
-- @param #number Delay Delay in seconds.
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
-- @param #number Speed Speed in km/h to 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.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
@@ -180,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self:SetCarrier( Helicopter )
self.landingspeed = 15 -- kph
self.landingheight = 5.5 -- meter
return self
end
@@ -228,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
return self
end
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
-- @param #AI_CARGO_HELICOPTER self
-- @param #number speed Landing speed in kph(!), e.g. 15
-- @param #number height Landing height in meters(!), e.g. 5.5
-- @return #AI_CARGO_HELICOPTER self
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
--
-- BASE:TraceOn()
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
--
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
-- Adjust if necessary.
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
local _speed = speed or 15
local _height = height or 5.5
self.landingheight = _height
self.landingspeed = _speed
return self
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
@@ -235,7 +288,7 @@ end
-- @param Event
-- @param To
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
self:F({From, Event, To})
Helicopter:F( { Name = Helicopter:GetName() } )
if Helicopter and Helicopter:IsAlive() then
@@ -244,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
-- 1 - When the helo lands normally on the ground.
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
-- For point 2, this is an issue, the infantry may not unload in this case!
-- So we check if the helo is on the ground, and velocity< 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)!
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 Helicopter:GetHeight( true ) <= 5.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( self.PickupZone )
self.RoutePickup = false
@@ -258,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
end
if self.RouteDeploy == true then
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 10 then
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
self:Unload( self.DeployZone )
self.RouteDeploy = false
end
@@ -276,7 +329,7 @@ end
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
self:F({From, Event, To, Coordinate, Speed, DeployZone})
local HelicopterInZone = false
if Helicopter and Helicopter:IsAlive() == true then
@@ -359,7 +412,8 @@ end
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
self:F({From, Event, To, Coordinate})
if Helicopter and Helicopter:IsAlive() then
local Route = {}
@@ -394,7 +448,7 @@ end
-- @param #boolean Deployed Cargo is deployed.
-- @return #boolean True if all cargo has been unloaded.
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } )
self:F( { From, Event, To, DeployZone = DeployZone } )
self:Orbit( Helicopter:GetCoordinate(), 50 )
@@ -416,7 +470,7 @@ end
-- @param Event
-- @param To
-- @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 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 )
@@ -485,10 +539,10 @@ end
-- @param Event
-- @param To
-- @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.
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteDeploy = true
@@ -550,11 +604,12 @@ end
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate Home place.
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
-- @param #number Height Height in meters to move to the home coordinate.
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
self:F({From, Event, To, Coordinate, Speed, Height})
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteHome = true
@@ -563,7 +618,8 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
--- Calculate the target route point.
Coordinate.y = Height
--Coordinate.y = Height
Height = Height or 50
Speed = Speed or Helicopter:GetSpeedMax()*0.5
@@ -576,7 +632,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
--- Create a route point of type air.
local CoordinateTo = Coordinate
local landheight = CoordinateTo:GetLandHeight() -- get target height
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
@@ -589,12 +645,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route(Route, 0)
end
end

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,441 @@
--- **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 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 Omnidirectional 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 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 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 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 stopping 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 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 Frequency
-- @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

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,89 +1,87 @@
--- **Core** - Models the process to achieve goal(s).
--
-- ===
--
--
-- ## Features:
--
--
-- * Define the goal.
-- * Monitor the goal achievement.
-- * 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.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky**
--
--
-- ===
--
--
-- @module Core.Goal
-- @image Core_Goal.JPG
do -- Goal
--- @type GOAL
-- @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.
--
--
-- # 1. GOAL constructor
--
--
-- * @{#GOAL.New}(): Creates a new GOAL object.
--
--
-- # 2. GOAL is a finite state machine (FSM).
--
--
-- ## 2.1. GOAL States
--
--
-- * **Pending**: The goal object is in progress.
-- * **Achieved**: The goal objective is Achieved.
--
--
-- ## 2.2. GOAL Events
--
--
-- * **Achieved**: Set the goal objective to Achieved.
--
--
-- # 3. Player contributions.
--
--
-- 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.
-- 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}().
-- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}().
--
--
-- # 4. Goal achievement.
--
--
-- 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:
--
--
-- Goal:Achieved() -- Achieve the goal immediately.
-- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds.
--
--
-- # 5. Check goal achievement.
--
--
-- 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.
--
--
-- @field #GOAL
GOAL = {
ClassName = "GOAL",
}
--- @field #table GOAL.Players
GOAL.Players = {}
--- @field #number GOAL.TotalContributions
GOAL.TotalContributions = 0
--- GOAL Constructor.
-- @param #GOAL self
-- @return #GOAL
function GOAL:New()
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
self:F( {} )
@@ -104,11 +102,10 @@ do -- Goal
-- @param #string From
-- @param #string Event
-- @param #string To
self:SetStartState( "Pending" )
self:AddTransition( "*", "Achieved", "Achieved" )
self:AddTransition( "*", "Achieved", "Achieved" )
--- Achieved Handler OnBefore for GOAL
-- @function [parent=#GOAL] OnBeforeAchieved
-- @param #GOAL self
@@ -116,47 +113,44 @@ do -- Goal
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Achieved Handler OnAfter for GOAL
-- @function [parent=#GOAL] OnAfterAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Achieved Trigger for GOAL
-- @function [parent=#GOAL] Achieved
-- @param #GOAL self
--- Achieved Asynchronous Trigger for GOAL
-- @function [parent=#GOAL] __Achieved
-- @param #GOAL self
-- @param #number Delay
self:SetEventPriority( 5 )
return self
end
--- Add a new contribution by a player.
-- @param #GOAL self
-- @param #string PlayerName The name of the player.
function GOAL:AddPlayerContribution( PlayerName )
self:F({PlayerName})
self:F( { PlayerName } )
self.Players[PlayerName] = self.Players[PlayerName] or 0
self.Players[PlayerName] = self.Players[PlayerName] + 1
self.TotalContributions = self.TotalContributions + 1
end
--- @param #GOAL self
-- @param #number Player contribution.
function GOAL:GetPlayerContribution( PlayerName )
return self.Players[PlayerName] or 0
return self.Players[PlayerName] or 0
end
--- Get the players who contributed to achieve the goal.
-- The result is a list of players, sorted by the name of the players.
-- @param #GOAL self
@@ -165,7 +159,6 @@ do -- Goal
return self.Players or {}
end
--- Gets the total contributions that happened to achieve the goal.
-- The result is a number.
-- @param #GOAL self
@@ -173,9 +166,7 @@ do -- Goal
function GOAL:GetTotalContributions()
return self.TotalContributions or 0
end
--- Validates if the goal is achieved.
-- @param #GOAL self
-- @return #boolean true if the goal is achieved.
@@ -183,4 +174,4 @@ do -- Goal
return self:Is( "Achieved" )
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
--- **Core** - Informs the players using messages during a simulation.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * A more advanced messaging system using the DCS message system.
-- * Time messages.
-- * 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 messages to a coalition.
-- * Send messages to a specific group.
--
--
-- ===
--
--
-- @module Core.Message
-- @image Core_Message.JPG
@@ -23,14 +23,14 @@
--- 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 can contain a category which is indicating the category of the message.
--
--
-- ## MESSAGE construction
--
--
-- 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.
--
--
-- ## Send messages to an audience
--
--
-- Messages are sent:
--
-- * To a @{Client} using @{#MESSAGE.ToClient}().
@@ -39,26 +39,26 @@
-- * To the red coalition using @{#MESSAGE.ToRed}().
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
-- * To all Players using @{#MESSAGE.ToAll}().
--
--
-- ## Send conditionally to an audience
--
--
-- Messages can be sent conditionally to an audience (when a condition is true):
--
--
-- * To all players using @{#MESSAGE.ToAllIf}().
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
--
--
-- ===
--
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ### Contributions:
--
-- ===
--
--
-- @field #MESSAGE
MESSAGE = {
ClassName = "MESSAGE",
MessageCategory = 0,
MessageID = 0,
ClassName = "MESSAGE",
MessageCategory = 0,
MessageID = 0,
}
--- Message Types
@@ -68,10 +68,9 @@ MESSAGE.Type = {
Information = "Information",
Briefing = "Briefing 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.
-- @param self
-- @param #string MessageText is the text of the Message.
@@ -80,52 +79,52 @@ MESSAGE.Type = {
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
-- @return #MESSAGE
-- @usage
-- -- Create a series of new Messages.
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
-- -- 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" )
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
--
-- -- Create a series of new Messages.
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
-- -- 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" )
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
--
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub(-1) ~= "\n" then
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub( -1 ) ~= "\n" then
self.MessageCategory = MessageCategory .. ": "
else
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
end
else
self.MessageCategory = ""
end
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
self.ClearScreen = false
if ClearScreen ~= nil then
self.ClearScreen = ClearScreen
end
self.MessageDuration = MessageDuration or 5
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
self.MessageSent = false
self.MessageGroup = false
self.MessageCoalition = false
self.MessageDuration = MessageDuration or 5
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
return self
self.MessageSent = false
self.MessageGroup = false
self.MessageCoalition = false
return self
end
--- Creates a new MESSAGE object of a certain type.
-- Note that these MESSAGE objects are not yet displayed on the display panel.
--- Creates a new MESSAGE object of a certain type.
-- 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.
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
-- @param self
@@ -134,83 +133,83 @@ end
-- @param #boolean ClearScreen (optional) Clear all previous messages.
-- @return #MESSAGE
-- @usage
--
-- 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 )
-- 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 )
--
function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText } )
self.MessageType = MessageType
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
self.ClearScreen = false
if ClearScreen ~= nil then
self.ClearScreen = ClearScreen
end
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
return self
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
-- @return #MESSAGE
function MESSAGE:Clear()
self:F()
self.ClearScreen=true
self.ClearScreen = true
return self
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".
-- @param #MESSAGE self
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
-- @param Core.Settings#SETTINGS Settings Settings used to display the message.
-- @return #MESSAGE
-- @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 )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- or
-- 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 )
-- 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 )
-- -- 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 )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- or
-- 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 )
-- 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 )
self:F( Client )
self:F( Client )
if Client and Client:GetClientGroupID() then
if Client and Client:GetClientGroupID() 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.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID()
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)
end
end
return self
local ClientGroupID = Client:GetClientGroupID()
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 )
end
end
return self
end
--- Sends a MESSAGE to a Group.
--- Sends a MESSAGE to a Group.
-- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group to which the message is displayed.
-- @return #MESSAGE Message object.
@@ -218,74 +217,80 @@ function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName )
if Group then
if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
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 )
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 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 )
return self
end
--- 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()
--
-- -- 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.RED )
return self
self:ToCoalition( coalition.side.BLUE )
return self
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 #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object.
-- @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 )
-- 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 )
-- 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:ToCoalition( coalition.side.RED )
--
-- -- 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 )
-- 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 )
-- 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:ToCoalition( coalition.side.RED )
--
function MESSAGE:ToCoalition( CoalitionSide, Settings )
self:F( CoalitionSide )
self:F( CoalitionSide )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
@@ -293,20 +298,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if CoalitionSide then
if CoalitionSide then
if self.MessageDuration ~= 0 then
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 )
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 )
end
end
return self
end
return self
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 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
function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
self:F( CoalitionSide )
@@ -314,7 +319,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
if Condition and Condition == true then
self:ToCoalition( CoalitionSide )
end
return self
end
@@ -323,14 +328,16 @@ end
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE
-- @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()
-- or
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- or
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
-- MessageAll:ToAll()
function MESSAGE:ToAll(Settings)
--
-- -- 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()
-- or
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- or
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
-- MessageAll:ToAll()
--
function MESSAGE:ToAll( Settings )
self:F()
if self.MessageType then
@@ -340,14 +347,13 @@ function MESSAGE:ToAll(Settings)
end
if self.MessageDuration ~= 0 then
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 )
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 )
end
return self
end
--- Sends a MESSAGE to all players if the given Condition is true.
-- @param #MESSAGE self
-- @return #MESSAGE
@@ -357,5 +363,5 @@ function MESSAGE:ToAllIf( Condition )
self:ToAll()
end
return self
return self
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.
--
-- ===
--
--
-- ## Features:
--
--
-- * Create text blocks that are formatted.
-- * Create automatic indents.
-- * Variate the delimiters between reporting lines.
--
--
-- ===
--
-- ### Authors: FlightControl : Design & Programming
@@ -15,7 +15,6 @@
-- @module Core.Report
-- @image Core_Report.JPG
--- @type REPORT
-- @extends Core.Base#BASE
@@ -36,7 +35,7 @@ function REPORT:New( Title )
self.Report = {}
self:SetTitle( Title or "" )
self:SetTitle( Title or "" )
self:SetIndent( 3 )
return self
@@ -45,28 +44,26 @@ end
--- Has the REPORT Text?
-- @param #REPORT self
-- @return #boolean
function REPORT:HasText() --R2.1
function REPORT:HasText() -- R2.1
return #self.Report > 0
end
--- Set indent of a REPORT.
-- @param #REPORT self
-- @param #number Indent
-- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1
function REPORT:SetIndent( Indent ) -- R2.1
self.Indent = Indent
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:Add( Text )
self.Report[#self.Report+1] = Text
self.Report[#self.Report + 1] = Text
return self
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.
-- @return #REPORT
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
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 #string Delimiter (optional) A delimiter text.
-- @return #string The report text.
function REPORT:Text( Delimiter )
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
end
@@ -95,7 +92,7 @@ end
-- @param #string Title The title of the report.
-- @return #REPORT
function REPORT:SetTitle( Title )
self.Title = Title
self.Title = Title
return self
end

View File

@@ -1,36 +1,36 @@
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
--
--
-- ===
--
--
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
--
--
-- This class is tricky and needs some thorough explanation.
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
-- The SCHEDULEDISPATCHER class ensures that:
--
--
-- - Scheduled functions are planned 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.
--
--
-- 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* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
--
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
--
-- 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,
-- 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 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.
--
--
-- ===
--
--
-- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming
--
--
-- @module Core.ScheduleDispatcher
-- @image Core_Schedule_Dispatcher.JPG
@@ -38,7 +38,7 @@
-- @type SCHEDULEDISPATCHER
-- @field #string ClassName Name of the class.
-- @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 Schedule Meta table setmetatable( {}, { __mode = "k" } ).
-- @extends Core.Base#BASE
@@ -46,11 +46,11 @@
--- The SCHEDULEDISPATCHER structure
-- @type SCHEDULEDISPATCHER
SCHEDULEDISPATCHER = {
ClassName = "SCHEDULEDISPATCHER",
CallID = 0,
PersistentSchedulers = {},
ObjectSchedulers = {},
Schedule = nil,
ClassName = "SCHEDULEDISPATCHER",
CallID = 0,
PersistentSchedulers = {},
ObjectSchedulers = {},
Schedule = nil,
}
--- 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 #table Arguments Schedule function arguments.
-- @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 Stop Stop time in seconds.
-- @field #number StartTime Time in seconds when the scheduler is created.
@@ -77,7 +77,7 @@ end
--- Add a Schedule to the ScheduleDispatcher.
-- 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.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
@@ -85,7 +85,7 @@ end
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
-- @param #number Start Start time in seconds.
-- @param #number Repeat Repeat interval in seconds.
-- @param #number Randomize Radomization factor [0,1].
-- @param #number Randomize Randomization factor [0,1].
-- @param #number Stop Stop time in seconds.
-- @param #number TraceLevel Trace level [0,3].
-- @param Core.Fsm#FSM Fsm Finite state model.
@@ -95,39 +95,38 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
-- Increase counter.
self.CallID = self.CallID + 1
-- Create ID.
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
-- Initialize PersistentSchedulers
self.PersistentSchedulers = self.PersistentSchedulers or {}
-- 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.
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then
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
self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
end
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
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].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].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
-- This section handles the tracing of the scheduled calls.
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
@@ -149,10 +148,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.
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
local Info = {}
if debug then
TraceLevel = TraceLevel or 2
Info = debug.getinfo( TraceLevel, "nlS" )
@@ -166,7 +165,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
--- Function passed to the DCS timer.scheduleFunction()
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
local CallID = Params.CallID
local Info = Params.Info or {}
local Source = Info.source or "?"
@@ -180,27 +179,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
end
return errmsg
end
-- Get object or persistant scheduler object.
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
-- Get object or persistent scheduler object.
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
if not Scheduler then
Scheduler = self.PersistentSchedulers[CallID]
end
--self:T3( { Scheduler = Scheduler } )
-- self:T3( { Scheduler = Scheduler } )
if Scheduler then
local MasterObject = tostring(Scheduler.MasterObject)
-- Schedule object.
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
--self:T3( { Schedule = Schedule } )
local MasterObject = tostring( Scheduler.MasterObject )
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 ScheduleFunction = Schedule.Function
local ScheduleArguments = Schedule.Arguments or {}
local Start = Schedule.Start
@@ -208,18 +207,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local Randomize = Schedule.Randomize or 0
local Stop = Schedule.Stop or 0
local ScheduleID = Schedule.ScheduleID
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
local Prefix = (Repeat == 0) and "--->" or "+++>"
local Status, Result
--self:E( { SchedulerObject = SchedulerObject } )
-- self:E( { SchedulerObject = SchedulerObject } )
if SchedulerObject then
local function Timer()
if ShowTrace then
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
else
@@ -227,40 +225,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if ShowTrace then
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return ScheduleFunction( unpack( ScheduleArguments ) )
return ScheduleFunction( unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
end
local CurrentTime = timer.getTime()
local StartTime = Schedule.StartTime
-- Debug info.
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
if Status and ((Result == nil) or (Result and Result ~= false)) then
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
return ScheduleTime -- returns the next time the function needs to be called.
else
self:Stop( Scheduler, CallID )
end
else
self:Stop( Scheduler, CallID )
end
else
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return nil
end
self:Start( Scheduler, CallID, Info )
return CallID
end
@@ -284,33 +281,33 @@ end
-- @param #string Info (Optional) Debug info.
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
self:F2( { Start = CallID, Scheduler = Scheduler } )
if CallID then
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
-- Only start when there is no ScheduleID defined!
-- This prevents to "Start" the scheduler twice with the same CallID...
if not Schedule.ScheduleID then
-- Current time in seconds.
local Tnow=timer.getTime()
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
local Tnow = timer.getTime()
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
end
else
-- Recursive.
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Start( Scheduler, CallID, Info ) -- Recursive
end
end
end
@@ -322,29 +319,29 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
self:F2( { Stop = CallID, Scheduler = Scheduler } )
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.
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
timer.removeFunction(Schedule.ScheduleID)
timer.removeFunction( Schedule.ScheduleID )
Schedule.ScheduleID = nil
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
else
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Stop( Scheduler, CallID ) -- Recursive
end
end
end
@@ -359,7 +356,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
end
end
--- Shopw tracing info.
--- Show tracing info.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )

View File

@@ -1,41 +1,41 @@
--- **Core** - Prepares and handles the execution of functions over scheduled time (intervals).
--
-- ===
--
--
-- ## Features:
--
--
-- * Schedule functions over time,
-- * optionally in an optional specified time interval,
-- * optionally **repeating** with a specified time repeat interval,
-- * optionally **randomizing** with a specified time interval randomization factor,
-- * optionally **stop** the repeating after a specified time interval.
-- * optionally in an optional specified time interval,
-- * optionally **repeating** with a specified time repeat interval,
-- * optionally **randomizing** with a specified time interval randomization factor,
-- * optionally **stop** the repeating after a specified time interval.
--
-- ===
--
--
-- # Demo Missions
--
--
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/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)
--
-- ===
--
-- # YouTube Channel
--
-- ### [SCHEDULER YouTube Channel (none)]()
--
--
-- ===
--
-- ### Contributions:
--
-- # YouTube Channel
--
-- ### [SCHEDULER YouTube Channel (none)]()
--
-- ===
--
-- ### Contributions:
--
-- * FlightControl : Concept & Testing
--
-- ### Authors:
--
--
-- ### Authors:
--
-- * FlightControl : Design & Programming
--
--
-- ===
--
-- @module Core.Scheduler
@@ -48,62 +48,61 @@
-- @field #boolean ShowTrace Trace info if true.
-- @extends Core.Base#BASE
--- 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**.
-- 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}(),
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
--
-- ## SCHEDULER constructor
--
--
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
--
--
-- The @{#SCHEDULER.New}() method returns 2 variables:
--
--
-- 1. The SCHEDULER object reference.
-- 2. The first schedule planned in the SCHEDULER object.
--
--
-- To clarify the different appliances, lets have a look at the following examples:
--
--
-- ### Construct a SCHEDULER object without a persistent schedule.
--
--
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
--
--
-- MasterObject = SCHEDULER:New()
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
--
--
-- The above example creates a new MasterObject, but does not schedule anything.
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
--
--
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
--
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
--
--
-- * @{#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" )
-- MasterObject = SCHEDULER:New( ZoneObject )
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ...
-- ZoneObject = nil
-- 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.
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the MasterObject will cancel any planned schedule.
--
--
-- ### Construct a SCHEDULER object with a persistent schedule.
--
--
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
--
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
--
--
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
--
--
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
--
--
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- ZoneObject = ZONE:New( "ZoneName" )
@@ -112,13 +111,13 @@
-- ...
-- ZoneObject = nil
-- garbagecollect()
--
--
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
-- Both a MasterObject and a SchedulerID variable are returned.
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the MasterObject will cancel the planned schedule.
--
--
-- ## SCHEDULER timer stopping and (re-)starting.
--
-- The SCHEDULER can be stopped and restarted with the following methods:
@@ -133,70 +132,70 @@
-- MasterObject:Stop( SchedulerID )
-- ...
-- MasterObject:Start( SchedulerID )
--
--
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
--
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
--
-- ## 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.
--
--
-- Consider the following code fragment of the SCHEDULER object creation.
--
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- 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
--
--
-- 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
--
--
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
--
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
--
--
-- ### A schedule with a repeating time interval, planned over time
--
--
-- SchedulerID = 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 ...
--
--
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
--
--
-- 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 ...
-- 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,
-- 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,
-- 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
--
--
-- 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.
-- 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,
-- 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,
-- which is in this example between **30** and **90** seconds.
-- The schedule will stop after **300** seconds.
--
--
-- @field #SCHEDULER
SCHEDULER = {
ClassName = "SCHEDULER",
Schedules = {},
MasterObject = nil,
ShowTrace = nil,
ClassName = "SCHEDULER",
Schedules = {},
MasterObject = nil,
ShowTrace = nil,
}
--- SCHEDULER constructor.
@@ -211,15 +210,15 @@ SCHEDULER = {
-- @return #SCHEDULER self.
-- @return #table The ScheduleID of the planned schedule.
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
local ScheduleID = nil
self.MasterObject = MasterObject
self.ShowTrace = false
if SchedulerFunction then
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
end
@@ -235,7 +234,7 @@ 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 Repeat Specifies the time interval in seconds when the scheduler will call the event function.
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop 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 Core.Fsm#FSM Fsm Finite state model.
-- @return #table The ScheduleID of the planned schedule.
@@ -245,28 +244,27 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments
-- Debug info.
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
end
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
-- Set master object.
self.MasterObject = MasterObject
-- Add schedule.
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
SchedulerArguments,
Start,
Repeat,
RandomizeFactor,
Stop,
TraceLevel or 3,
Fsm
)
self.Schedules[#self.Schedules+1] = ScheduleID
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
SchedulerFunction,
SchedulerArguments,
Start,
Repeat,
RandomizeFactor,
Stop,
TraceLevel or 3,
Fsm
)
self.Schedules[#self.Schedules + 1] = ScheduleID
return ScheduleID
end
@@ -276,7 +274,7 @@ end
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Start( ScheduleID )
self:F3( { ScheduleID } )
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
end
@@ -285,7 +283,7 @@ end
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Stop( ScheduleID )
self:F3( { ScheduleID } )
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
end
@@ -294,15 +292,15 @@ end
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
function SCHEDULER:Remove( 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 )
end
--- Clears all pending schedules.
-- @param #SCHEDULER self
function SCHEDULER:Clear()
self:F3( )
self:T(string.format("Clearing scheduler"))
self:F3()
self:T( string.format( "Clearing scheduler" ) )
_SCHEDULEDISPATCHER:Clear( self )
end

File diff suppressed because it is too large Load Diff

View File

@@ -29,15 +29,14 @@
-- @module Core.Settings
-- @image Core_Settings.JPG
--- @type SETTINGS
-- @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:
--
-- - **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.
-- 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
--
@@ -69,7 +68,7 @@
--
-- ## 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.
-- 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.
@@ -94,8 +93,8 @@
--
-- - 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 LL DMS: Lattitude 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 DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). 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**
--
@@ -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.
--
-- 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.
-- Note that **Update** messages can be chosen not to be displayed at all!
--
@@ -196,7 +195,7 @@
--
-- ## 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:
--
-- - **WWII** era: Use for warfare with equipment during the world war II time.
@@ -213,8 +212,8 @@
SETTINGS = {
ClassName = "SETTINGS",
ShowPlayerMenu = true,
MenuShort = false,
MenuStatic = false,
MenuShort = false,
MenuStatic = false,
}
SETTINGS.__Enum = {}
@@ -231,11 +230,11 @@ SETTINGS.__Enum.Era = {
Modern = 4,
}
do -- SETTINGS
--- SETTINGS constructor.
-- @param #SETTINGS self
-- @param #string PlayerName (Optional) Set settings for this player.
-- @return #SETTINGS
function SETTINGS:Set( PlayerName )
@@ -267,14 +266,14 @@ do -- SETTINGS
-- Short text are better suited for, e.g., VR.
-- @param #SETTINGS self
-- @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
end
--- Set menu to be static.
-- @param #SETTINGS self
-- @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
end
@@ -288,7 +287,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if metric.
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
--- Sets the SETTINGS imperial.
@@ -301,7 +300,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if imperial.
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:IsMetric())
end
--- Sets the SETTINGS LL accuracy.
@@ -343,13 +342,12 @@ do -- SETTINGS
self.MessageTypeTimings[MessageType] = MessageTime
end
--- Gets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @return #number
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
--- Sets A2G LL DMS
@@ -370,14 +368,14 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Sets A2G MGRS
@@ -391,7 +389,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Sets A2G BRA
@@ -405,7 +403,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if BRA
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
--- Sets A2A BRA
@@ -419,7 +417,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if BRA
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
--- Sets A2A BULLS
@@ -433,7 +431,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Sets A2A LL DMS
@@ -454,14 +452,14 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if 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
--- Sets A2A MGRS
@@ -475,7 +473,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if 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
--- @param #SETTINGS self
@@ -494,37 +492,37 @@ do -- SETTINGS
-- A2G Coordinate System
-------
local text="A2G Coordinate System"
local text = "A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
text = "A2G Coordinates"
end
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
-- Set LL DMS
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
text="LL DMS"
text = "LL DMS"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end
-- Set LL DDM
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
text="LL DDM"
text = "LL DDM"
end
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
-- Set LL DMS accuracy.
if self:IsA2G_LL_DDM() then
local text1="LL DDM Accuracy 1"
local text2="LL DDM Accuracy 2"
local text3="LL DDM Accuracy 3"
local text1 = "LL DDM Accuracy 1"
local text2 = "LL DDM Accuracy 2"
local text3 = "LL DDM Accuracy 3"
if _SETTINGS.MenuShort then
text1="LL DDM"
text1 = "LL DDM"
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 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
@@ -533,18 +531,18 @@ do -- SETTINGS
-- Set BR.
if not self:IsA2G_BR() then
local text="Bearing, Range (BR)"
local text = "Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="BR"
text = "BR"
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
-- Set MGRS.
if not self:IsA2G_MGRS() then
local text="Military Grid (MGRS)"
local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
text = "MGRS"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end
@@ -562,24 +560,24 @@ do -- SETTINGS
-- A2A Coordinate System
-------
local text="A2A Coordinate System"
local text = "A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
text = "A2A Coordinates"
end
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
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
text="LL DMS"
text = "LL DMS"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end
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
text="LL DDM"
text = "LL DDM"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
@@ -592,25 +590,25 @@ do -- SETTINGS
end
if not self:IsA2A_BULLS() then
local text="Bullseye (BULLS)"
local text = "Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="Bulls"
text = "Bulls"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
end
if not self:IsA2A_BRAA() then
local text="Bearing Range Altitude Aspect (BRAA)"
local text = "Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then
text="BRAA"
text = "BRAA"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
end
if not self:IsA2A_MGRS() then
local text="Military Grid (MGRS)"
local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
text = "MGRS"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end
@@ -623,31 +621,31 @@ do -- SETTINGS
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end
local text="Measures and Weights System"
local text = "Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
text = "Unit System"
end
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
if self:IsMetric() then
local text="Imperial (Miles,Feet)"
local text = "Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
text = "Imperial"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
end
if self:IsImperial() then
local text="Metric (Kilometers,Meters)"
local text = "Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
text = "Metric"
end
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
end
local text="Messages and Reports"
local text = "Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
text = "Messages & Reports"
end
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
@@ -688,7 +686,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, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
SettingsMenu:Remove( MenuTime )
return self
@@ -732,11 +729,11 @@ do -- SETTINGS
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 )
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 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 )
@@ -753,40 +750,40 @@ do -- SETTINGS
-- A2G Coordinate System
------
local text="A2G Coordinate System"
local text = "A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
text = "A2G Coordinates"
end
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
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
text="A2G LL DMS"
text = "A2G LL DMS"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
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
text="A2G LL DDM"
text = "A2G LL DDM"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
local text="Bearing, Range (BR)"
local text = "Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="A2G BR"
text = "A2G BR"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
end
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2G MGRS"
text = "A2G MGRS"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
@@ -795,49 +792,48 @@ do -- SETTINGS
-- A2A Coordinates Menu
------
local text="A2A Coordinate System"
local text = "A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
text = "A2A Coordinates"
end
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
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
text="A2A LL DMS"
text = "A2A LL DMS"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
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
text="A2A LL DDM"
text = "A2A LL DDM"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
local text="Bullseye (BULLS)"
local text = "Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="A2A BULLS"
text = "A2A BULLS"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
end
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
text="A2A BRAA"
text = "A2A BRAA"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
end
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
local text = "Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2A MGRS"
text = "A2A MGRS"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
@@ -846,24 +842,24 @@ do -- SETTINGS
-- Unit system
---
local text="Measures and Weights System"
local text = "Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
text = "Unit System"
end
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
if self:IsMetric() or _SETTINGS.MenuStatic then
local text="Imperial (Miles,Feet)"
local text = "Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
text = "Imperial"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end
if self:IsImperial() or _SETTINGS.MenuStatic then
local text="Metric (Kilometers,Meters)"
local text = "Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
text = "Metric"
end
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end
@@ -872,9 +868,9 @@ do -- SETTINGS
-- Messages and Reports
---
local text="Messages and Reports"
local text = "Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
text = "Messages & Reports"
end
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
@@ -934,39 +930,38 @@ do -- SETTINGS
return self
end
--- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, 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 )
end
--- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, 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 )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, 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 )
end
--- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, 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 )
end
--- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, 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 )
end
@@ -979,12 +974,12 @@ do -- SETTINGS
do
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu( PlayerUnit )
end
end
@@ -992,9 +987,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu( PlayerUnit )
end
end
@@ -1002,9 +997,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, 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 )
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu( PlayerUnit )
end
end
@@ -1012,9 +1007,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, 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 )
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu( PlayerUnit )
end
end
@@ -1022,9 +1017,9 @@ do -- SETTINGS
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, 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 )
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
if _SETTINGS.MenuStatic == false then
self:RemovePlayerMenu( PlayerUnit )
self:SetPlayerMenu( PlayerUnit )
end
end
@@ -1054,7 +1049,6 @@ do -- SETTINGS
end
--- Configures the era of the mission to be Cold war.
-- @param #SETTINGS self
-- @return #SETTINGS self
@@ -1064,7 +1058,6 @@ do -- SETTINGS
end
--- Configures the era of the mission to be Modern war.
-- @param #SETTINGS self
-- @return #SETTINGS self
@@ -1074,7 +1067,4 @@ do -- SETTINGS
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -138,24 +138,24 @@ SPAWNSTATIC = {
-- @return #SPAWNSTATIC self
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)
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end
self:SetEventPriority( 5 )
return self
return self
end
--- Creates the main object to spawn a @{Static} given a template table.
@@ -422,7 +422,11 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
end
if self.InitCargo~=nil then
Template.isCargo=self.InitCargo
Template.canCargo=self.InitCargo
end
if self.InitCargoMass~=nil then
Template.mass=self.InitCargoMass
end
if self.InitLinkUnit then

File diff suppressed because it is too large Load Diff

View File

@@ -295,6 +295,17 @@ do -- country
-- @field QATAR
-- @field OMAN
-- @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
@@ -736,7 +747,66 @@ do -- 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
--- 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.
@@ -1093,6 +1163,10 @@ do -- Unit
-- @param #Unit self
-- @return #Unit.Desc
--- GROUND - Switch on/off radar emissions
-- @function [parent=#Unit] enableEmission
-- @param #Unit self
-- @param #boolean switch
Unit = {} --#Unit
@@ -1163,7 +1237,7 @@ do -- Group
-- @param #Group self
-- @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
-- @param #Group self
-- @return #number
@@ -1178,6 +1252,11 @@ do -- Group
-- @param #Group self
-- @return #Controller
--- GROUND - Switch on/off radar emissions
-- @function [parent=#Group] enableEmission
-- @param #Group self
-- @param #boolean switch
Group = {} --#Group
end -- Group
@@ -1393,4 +1472,4 @@ do -- AI
AI = {} --#AI
end -- AI
end -- AI

View File

@@ -1001,7 +1001,13 @@ do -- DESIGNATE
local ID = self.Detection:GetDetectedItemID( DetectedItem )
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 )
-- Build the Lasing menu.

View File

@@ -23,7 +23,6 @@
-- @module Functional.FOX
-- @image Functional_FOX.png
--- FOX class.
-- @type FOX
-- @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 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 dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
-- @field #boolean
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
-- @extends Core.Fsm#FSM
--- Fox 3!
@@ -1707,8 +1705,8 @@ end
--- 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 DCS#Weapon weapon The weapon.
-- @return #number Notching heading right, i.e. missile heading +90<EFBFBD>
-- @return #number Notching heading left, 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°.
function FOX:_GetNotchingHeadings(weapon)
if weapon then
@@ -1813,4 +1811,4 @@ end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
-- ## Features:
--
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range <EFBFBD>
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
-- * Provide alerts when a missile would have killed your aircraft.
-- * Provide alerts when the missile self destructs.
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.

View File

@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
local T=position:GetTemperature()
-- 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
_T=string.format('%d°C', T)
end

View File

@@ -5371,7 +5371,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
if spawnonground then
-- Sh<EFBFBD>ps and FARPS seem to have a build in queue.
-- Sh°ps and FARPS seem to have a build in queue.
if spawnonship or spawnonfarp or spawnonrunway or automatic then
self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName()))
@@ -5786,6 +5786,8 @@ end
-- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops.
-- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero.
--
-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups.
--
-- ## Example
-- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft
-- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours.

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,146 @@
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
--
--
-- ===
--
--
-- ## 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 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:
--
--
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
--
--
-- ===
--
--
-- ### Authors: **FlightControl**, **applevangelist**
--
-- Last Update: Dec 2020
--
--
-- Last Update: Nov 2021
--
-- ===
--
--
-- @module Functional.Sead
-- @image SEAD.JPG
--- @type SEAD
---
-- @type SEAD
-- @extends Core.Base#BASE
--- 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.
-- 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:
--
--
-- 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' } )
--
--
-- @field #SEAD
SEAD = {
ClassName = "SEAD",
TargetSkill = {
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
High = { Evade = 15, DelayOn = { 20, 40 } } ,
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
SEADGroupPrefixes = {},
SuppressedGroups = {},
EngagementRange = 75 -- default 75% engagement range Feature Request #1355
ClassName = "SEAD",
TargetSkill = {
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
High = { Evade = 15, DelayOn = { 20, 40 } } ,
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
SEADGroupPrefixes = {},
SuppressedGroups = {},
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
Padding = 10,
CallBack = nil,
UseCallBack = 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",
}
--- 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.85},
["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},
}
--- 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...
-- 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 DCS mission editor on which evasive actions need to be taken.
-- @return SEAD
-- @param #SEAD self
-- @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
-- -- CCCP SEAD Defenses
-- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes )
function SEAD:New( SEADGroupPrefixes, Padding )
local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end
self:HandleEvent( EVENTS.Shot )
self:I("*** SEAD - Started Version 0.2.2")
return self
local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end
local padding = Padding or 10
if padding < 10 then padding = 10 end
self.Padding = padding
self.UseEmissionsOnOff = false
self.CallBack = nil
self.UseCallBack = false
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
self:I("*** SEAD - Started Version 0.3.3")
return self
end
--- Update the active SEAD Set
--- Update the active SEAD Set (while running)
-- @param #SEAD self
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
-- @return #SEAD self
function SEAD:UpdateSet( SEADGroupPrefixes )
self:F( SEADGroupPrefixes )
self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@@ -99,10 +154,10 @@ 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. 50
-- @return self
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
-- @return #SEAD self
function SEAD:SetEngagementRange(range)
self:F( { range } )
self:T( { range } )
range = range or 75
if range < 0 or range > 100 then
range = 75
@@ -112,110 +167,218 @@ function SEAD:SetEngagementRange(range)
return self
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.
-- @see SEAD
-- @param #SEAD
-- @param Core.Event#EVENTDATA EventData
function SEAD:OnEventShot( EventData )
self:T( { EventData } )
local SEADUnit = EventData.IniDCSUnit
local SEADUnitName = EventData.IniDCSUnitName
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
local SEADWeaponName = EventData.WeaponName -- return weapon type
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
self:T({ SEADWeapon })
--check for SEAD missiles
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired
then
local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = EventData.Weapon:getTarget() -- Identify target
local _targetMimname = Unit.getName(_targetMim) -- Unit name
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted grouo
local _targetMimgroupName = _targetMimgroup:getName() -- group name
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill
self:T( self.SEADGroupPrefixes )
self:T( _targetMimgroupName )
-- see if we are shot at
local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
SEADGroupFound = true
self:T( '*** SEAD - Group Found' )
break
end
end
if SEADGroupFound == true then -- yes we are being attacked
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
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) )
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
local _targetMimcont= _targetMimgroup:getController()
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
--tracker ID table to switch groups off and on again
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local function SuppressionEnd(id) --switch group back on
local range = self.EngagementRange -- Feature Request #1355
self:T(string.format("*** SEAD - Engagement Range is %d", range))
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
local SuppressionEndTime = timer.getTime() + delay
--create entry
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet
self.SuppressedGroups[id.groupName] = {
SuppressionEndTime = delay
}
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
end
end
end
end
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) 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) 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
-- @return #SEAD self
function SEAD:HandleEventShot( EventData )
self:T( { EventData.id } )
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
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
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
--self:T({ SEADWeapon })
if self:_CheckHarms(SEADWeaponName) then
self:T( '*** SEAD - Weapon Match' )
local _targetskill = "Random"
local _targetgroupname = "none"
local _target = EventData.Weapon:getTarget() -- Identify target
local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
local _targetgroup = nil -- Wrapper.Group#GROUP
if _targetUnit and _targetUnit:IsAlive() then
_targetgroup = _targetUnit:GetGroup()
_targetgroupname = _targetgroup:GetName() -- group name
local _targetUnitName = _targetUnit:GetName()
_targetUnit:GetSkill()
_targetskill = _targetUnit:GetSkill()
end
-- see if we are shot at
local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
self:T( _targetgroupname, SEADGroupPrefix )
if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then
SEADGroupFound = true
self:T( '*** SEAD - Group Match Found' )
break
end
end
if SEADGroupFound == true then -- yes we are being attacked
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) -- 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
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)
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:OptionAlarmStateAuto()
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 > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
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},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)
end
end
end
end
end
end
end
return self
end

View File

@@ -0,0 +1,548 @@
--- **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: July 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",
}
--- 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 groups
-- @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, BASE: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.2.8")
-- 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)
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) 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) 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) 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) 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")
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:WakeUpShorad(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
--- 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
local targetcat = targetdata:getCategory() -- Identify category
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
local targetunit = nil
if targetcat == Object.Category.UNIT then -- UNIT
targetunit = UNIT:Find(targetdata)
elseif targetcat == Object.Category.STATIC then -- STATIC
targetunit = STATIC:Find(targetdata)
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
targetgroup = targetunit:GetGroup()
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
end
--
end
-----------------------------------------------------------------------
-- SHORAD end
-----------------------------------------------------------------------

View File

@@ -1601,7 +1601,7 @@ WAREHOUSE = {
-- @field #number range Range of the unit in meters.
-- @field #number speedmax Maximum speed in km/h the group can do.
-- @field #number size Maximum size in length and with of the asset in meters.
-- @field #number weight The weight of the whole asset group in kilo gramms.
-- @field #number weight The weight of the whole asset group in kilograms.
-- @field DCS#Object.Desc DCSdesc All DCS descriptors.
-- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group.
-- @field #table cargobay Array of cargo bays of all units in an asset group.

View File

@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
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.
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
-- Update last hit time.
self.HitTimeLast=timer.getTime()
-- Only trigger attacked event if not already in state "Attacked".
if self:GetState()~="Attacked" then
self:F2("Hit ==> Attack")
self:Attack()
end
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
-- Update last hit time.
self.HitTimeLast=timer.getTime()
-- Only trigger attacked event if not already in state "Attacked".
if self:GetState()~="Attacked" then
self:F2("Hit ==> Attack")
self:Attack()
end
end
end
end
end
@@ -890,12 +891,14 @@ do -- ZONE_CAPTURE_COALITION
end
-- Status text.
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
local NewState = self:GetState()
if NewState~=State then
text=text..string.format(" --> %s", NewState)
if false then
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
local NewState = self:GetState()
if NewState~=State then
text=text..string.format(" --> %s", NewState)
end
self:I(text)
end
self:I(text)
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
_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.
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
--- Settings
_SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
--- Register cargos.
_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,12 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
@@ -20,9 +22,6 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
@@ -68,11 +67,14 @@ __Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
@@ -111,6 +113,13 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/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/Mission.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

@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
-- Debug.
local text=string.format("Unit %s crashed or ejected.", unitname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
self:T(self.lid..text)
-- Get coordinate of unit.
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:
--
-- * Managed Radio Transmissions.
-- * Manage Radio Transmissions
--
-- ===
--
-- ### Authors: funkyfranky
--
-- @module Core.RadioQueue
-- @module Sound.RadioQueue
-- @image Core_Radio.JPG
--- 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
-- @field #string ClassName Name of the class "RADIOQUEUE".
-- @field #boolean Debugmode Debug mode. More info.
@@ -35,6 +39,7 @@
-- @field #table numbers Table of number transmission parameters.
-- @field #boolean checking Scheduler is checking the radio queue.
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
-- @field Sound.SRS#MSRS msrs Moose SRS class.
-- @extends Core.Base#BASE
RADIOQUEUE = {
ClassName = "RADIOQUEUE",
@@ -69,12 +74,14 @@ RADIOQUEUE = {
-- @field #boolean isplaying If true, transmission is currently playing.
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
-- @field #number interval Interval in seconds before next transmission.
-- @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.
-- @param #RADIOQUEUE self
-- @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.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation, alias)
@@ -125,9 +132,9 @@ function RADIOQUEUE:Start(delay, dt)
-- Start Scheduler.
if self.schedonce then
self:_CheckRadioQueueDelayed(delay)
self:_CheckRadioQueueDelayed(self.delay)
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
return self
@@ -170,6 +177,17 @@ function RADIOQUEUE:SetRadioPower(power)
return self
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.
-- @param #RADIOQUEUE self
-- @param #number digit The digit 0-9.
@@ -202,7 +220,7 @@ end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
-- @return #RADIOQUEUE self
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
@@ -221,7 +239,7 @@ function RADIOQUEUE:AddTransmission(transmission)
return self
end
--- Add a transmission to the radio queue.
--- Create a new transmission and add it to the radio queue.
-- @param #RADIOQUEUE self
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
-- @param #number duration Duration in seconds the file lasts.
@@ -230,7 +248,7 @@ end
-- @param #number interval Interval in seconds after the last transmission finished.
-- @param #string subtitle Subtitle of the transmission.
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
-- @return #RADIOQUEUE.Transmission Radio transmission table.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
-- Sanity checks.
@@ -269,9 +287,36 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval,
-- Add transmission to queue.
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
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.
-- E.g. for board number or headings.
-- @param #RADIOQUEUE self
@@ -280,19 +325,9 @@ end
-- @param #number interval Interval between the next call.
-- @return #number Duration of the call in seconds.
function RADIOQUEUE:Number2Transmission(number, delay, interval)
--- Split string into characters.
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars, c)
end
return chars
end
-- Split string into characters.
local numbers=_split(number)
local numbers=UTILS.GetCharacters(number)
local wait=0
for i=1,#numbers do
@@ -325,6 +360,11 @@ end
-- @param #RADIOQUEUE.Transmission transmission The 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.
local sender=self:_GetRadioSender()
@@ -416,6 +456,19 @@ function RADIOQUEUE:Broadcast(transmission)
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.
-- @param #RADIOQUEUE self
-- @param #number delay Delay in seconds before checking.
@@ -547,7 +600,7 @@ function RADIOQUEUE:_GetRadioSender()
return nil
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
-- @return DCS#Vec3 Vector 3D.
function RADIOQUEUE:_GetRadioSenderCoord()

View File

@@ -11,7 +11,7 @@
--
-- ### Authors: FlightControl
--
-- @module Core.RadioSpeech
-- @module Sound.RadioSpeech
-- @image Core_Radio.JPG
--- Makes the radio speak.
@@ -162,33 +162,32 @@ RADIOSPEECH.Vocabulary.RU = {
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["степени"] = { "degrees", 0.5 },
["километров"] = { "kilometers", 0.65 },
["градусы"] = { "degrees", 0.5 },
["километры"] = { "kilometers", 0.65 },
["km"] = { "kilometers", 0.65 },
["миль"] = { "miles", 0.45 },
["мили"] = { "miles", 0.45 },
["mi"] = { "miles", 0.45 },
["метры"] = { "meters", 0.41 },
["метров"] = { "meters", 0.41 },
["m"] = { "meters", 0.41 },
["ноги"] = { "feet", 0.37 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
["возвращение на базу"] = { "returning_to_base", 1.40 },
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
["перехват боги"] = { "intercepting_bogeys", 1.22 },
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
["колеса вверх"] = { "wheels_up", 0.92 },
["привлечение болотных птиц"] = { "engaging_bogeys", 1.68 },
["колёса вверх..."] = { "wheels_up", 0.92 },
["посадка на базу"] = { "landing at base", 1.04 },
["патрулирующий"] = { "patrolling", 0.96 },
["патрулирование"] = { "patrolling", 0.96 },
["за"] = { "for", 0.27 },
["для"] = { "for", 0.27 },
["и"] = { "and", 0.17 },
["в"] = { "at", 0.19 },
["dot"] = { "dot", 0.51 },
["defender"] = { "defender", 0.45 },
["на сайте"] = { "at", 0.19 },
["точка"] = { "dot", 0.51 },
["защитник"] = { "defender", 0.45 },
}
--- Create a new RADIOSPEECH object for a given radio frequency/modulation.

View File

@@ -0,0 +1,701 @@
--- **Sound** - Simple Radio Standalone (SRS) Integration.
--
-- ===
--
-- **Main Features:**
--
-- * Play sound files via SRS
-- * Play text-to-speach via SRS
--
-- ===
--
-- ## Youtube Videos: None yet
--
-- ===
--
-- ## Missions: None yet
--
-- ===
--
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
--
-- ===
--
-- The goal of the [SRS](https://github.com/ciribob/DCS-SimpleRadioStandalone) project is to bring VoIP communication into DCS and to make communication as frictionless as possible.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Sound.MSRS
-- @image Sound_MSRS.png
--- MSRS class.
-- @type MSRS
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table frequencies Frequencies used in the transmissions.
-- @field #table modulations Modulations used in the transmissions.
-- @field #number coalition Coalition of the transmission.
-- @field #number port Port. Default 5002.
-- @field #string name Name. Default "DCS-STTS".
-- @field #number volume Volume between 0 (min) and 1 (max). Default 1.
-- @field #string culture Culture. Default "en-GB".
-- @field #string gender Gender. Default "female".
-- @field #string voice Specifc voce.
-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send.
-- @field #string path Path to the SRS exe. This includes the final slash "/".
-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
-- @extends Core.Base#BASE
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
--
-- ===
--
-- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png)
--
-- # The MSRS Concept
--
-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS).
--
-- ## Prerequisites
--
-- This script needs SRS version >= 1.9.6.
--
-- # Play Sound Files
--
-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "D:\\Sounds For DCS")
-- local msrs=MSRS:New("C:\\Path To SRS", 251, radio.modulation.AM)
-- msrs:PlaySoundFile(soundfile)
--
-- # Play Text-To-Speech
--
-- Basic example:
--
-- -- Create a SOUNDTEXT object.
-- local text=SOUNDTEXT:New("All Enemies destroyed")
--
-- -- MOOSE SRS
-- local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, radio.modulation.AM)
--
-- -- Text-to speech with default voice after 2 seconds.
-- msrs:PlaySoundText(text, 2)
--
-- ## Set Gender
--
-- Use a specific gender with the @{#MSRS.SetGender} function, e.g. `SetGender("male")` or `:SetGender("female")`.
--
-- ## Set Culture
--
-- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`.
--
-- ## Set Google
--
-- Use Google's text-to-speech engine with the @{#MSRS.SetGoogle} function, e.g. ':SetGoogle()'.
-- By enabling this it also allows you to utilize SSML in your text for added flexibilty.
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
--
-- ## Set Voice
--
-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
-- Note that this must be installed on your windows system.
-- If enabling SetGoogle(), you can use voices provided by Google
-- Google's supported voices: https://cloud.google.com/text-to-speech/docs/voices
--
-- ## Set Coordinate
--
-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted.
--
-- @field #MSRS
MSRS = {
ClassName = "MSRS",
lid = nil,
port = 5002,
name = "MSRS",
frequencies = {},
modulations = {},
coalition = 0,
gender = "female",
culture = nil,
voice = nil,
volume = 1,
speed = 1,
coordinate = nil,
}
--- MSRS class version.
-- @field #string version
MSRS.version="0.0.3"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add functions to add/remove freqs and modulations.
-- DONE: Add coordinate.
-- DONE: Add google.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new MSRS object.
-- @param #MSRS self
-- @param #string PathToSRS Path to the directory, where SRS is located.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
-- @return #MSRS self
function MSRS:New(PathToSRS, Frequency, Modulation)
-- Defaults.
Frequency =Frequency or 143
Modulation= Modulation or radio.modulation.AM
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, BASE:New()) -- #MSRS
self:SetPath(PathToSRS)
self:SetPort()
self:SetFrequencies(Frequency)
self:SetModulations(Modulation)
self:SetGender()
self:SetCoalition()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set path to SRS install directory. More precisely, path to where the DCS-
-- @param #MSRS self
-- @param #string Path Path to the directory, where the sound file is located. This does **not** contain a final backslash or slash.
-- @return #MSRS self
function MSRS:SetPath(Path)
if Path==nil then
self:E("ERROR: No path to SRS directory specified!")
return nil
end
-- Set path.
self.path=Path
-- Remove (back)slashes.
local n=1 ; local nmax=1000
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
-- Debug output.
self:T(string.format("SRS path=%s", self:GetPath()))
return self
end
--- Get path to SRS directory.
-- @param #MSRS self
-- @return #string Path to the directory. This includes the final slash "/".
function MSRS:GetPath()
return self.path
end
--- Set port.
-- @param #MSRS self
-- @param #number Port Port. Default 5002.
-- @return #MSRS self
function MSRS:SetPort(Port)
self.port=Port or 5002
end
--- Get port.
-- @param #MSRS self
-- @return #number Port.
function MSRS:GetPort()
return self.port
end
--- Set coalition.
-- @param #MSRS self
-- @param #number Coalition Coalition. Default 0.
-- @return #MSRS self
function MSRS:SetCoalition(Coalition)
self.coalition=Coalition or 0
end
--- Get coalition.
-- @param #MSRS self
-- @return #number Coalition.
function MSRS:GetCoalition()
return self.coalition
end
--- Set frequencies.
-- @param #MSRS self
-- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used.
-- @return #MSRS self
function MSRS:SetFrequencies(Frequencies)
-- Ensure table.
if type(Frequencies)~="table" then
Frequencies={Frequencies}
end
self.frequencies=Frequencies
return self
end
--- Get frequencies.
-- @param #MSRS self
-- @param #table Frequencies in MHz.
function MSRS:GetFrequencies()
return self.frequencies
end
--- Set modulations.
-- @param #MSRS self
-- @param #table Modulations Modulations. Can also be given as a #number if only one modulation should be used.
-- @return #MSRS self
function MSRS:SetModulations(Modulations)
-- Ensure table.
if type(Modulations)~="table" then
Modulations={Modulations}
end
self.modulations=Modulations
return self
end
--- Get modulations.
-- @param #MSRS self
-- @param #table Modulations.
function MSRS:GetModulations()
return self.modulations
end
--- Set gender.
-- @param #MSRS self
-- @param #string Gender Gender: "male" or "female" (default).
-- @return #MSRS self
function MSRS:SetGender(Gender)
Gender=Gender or "female"
self.gender=Gender:lower()
-- Debug output.
self:T("Setting gender to "..tostring(self.gender))
return self
end
--- Set culture.
-- @param #MSRS self
-- @param #string Culture Culture, e.g. "en-GB" (default).
-- @return #MSRS self
function MSRS:SetCulture(Culture)
self.culture=Culture
return self
end
--- Set to use a specific voice. Will override gender and culture settings.
-- @param #MSRS self
-- @param #string Voice Voice.
-- @return #MSRS self
function MSRS:SetVoice(Voice)
self.voice=Voice
return self
end
--- Set the coordinate from which the transmissions will be broadcasted.
-- @param #MSRS self
-- @param Core.Point#COORDINATE Coordinate Origin of the transmission.
-- @return #MSRS self
function MSRS:SetCoordinate(Coordinate)
self.coordinate=Coordinate
return self
end
--- Use google text-to-speech.
-- @param #MSRS self
-- @param PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
-- @return #MSRS self
function MSRS:SetGoogle(PathToCredentials)
self.google=PathToCredentials
return self
end
--- Print SRS STTS help to DCS log file.
-- @param #MSRS self
-- @return #MSRS self
function MSRS:Help()
-- Path and exe.
local path=self:GetPath() or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
-- Text file for output.
local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt"
-- Print help.
local command=string.format("%s/%s --help > %s", path, exe, filename)
os.execute(command)
local f=assert(io.open(filename, "rb"))
local data=f:read("*all")
f:close()
-- Print to log file.
env.info("SRS STTS help output:")
env.info("======================================================================")
env.info(data)
env.info("======================================================================")
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Transmission Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Play sound file (ogg or mp3) via SRS.
-- @param #MSRS self
-- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play.
-- @param #number Delay Delay in seconds, before the sound file is played.
-- @return #MSRS self
function MSRS:PlaySoundFile(Soundfile, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0)
else
-- Sound file name.
local soundfile=Soundfile:GetName()
-- Get command.
local command=self:_GetCommand()
-- Append file.
command=command.." --file="..tostring(soundfile)
self:_ExecCommand(command)
--[[
command=command.." > bla.txt"
-- Debug output.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
]]
end
return self
end
--- Play a SOUNDTEXT text-to-speech object.
-- @param #MSRS self
-- @param Sound.SoundFile#SOUNDTEXT SoundText Sound text.
-- @param #number Delay Delay in seconds, before the sound file is played.
-- @return #MSRS self
function MSRS:PlaySoundText(SoundText, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
else
-- Get command.
local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed)
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(SoundText.text))
-- Execute command.
self:_ExecCommand(command)
--[[
command=command.." > bla.txt"
-- Debug putput.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
]]
end
return self
end
--- Play text message via STTS.
-- @param #MSRS self
-- @param #string Text Text message.
-- @param #number Delay Delay in seconds, before the message is played.
-- @return #MSRS self
function MSRS:PlayText(Text, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0)
else
-- Get command line.
local command=self:_GetCommand()
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(Text))
-- Execute command.
self:_ExecCommand(command)
--[[
-- Check that length of command is max 255 chars or os.execute() will not work!
if string.len(command)>255 then
-- Create a tmp file.
local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat"
local script = io.open(filename, "w+")
script:write(command.." && exit")
script:close()
-- Play command.
command=string.format("\"%s\"", filename)
-- Play file in 0.05 seconds
timer.scheduleFunction(os.execute, command, timer.getTime()+0.05)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
else
-- Debug output.
self:I(string.format("MSRS Text command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
end
]]
end
return self
end
--- Play text file via STTS.
-- @param #MSRS self
-- @param #string TextFile Full path to the file.
-- @param #number Delay Delay in seconds, before the message is played.
-- @return #MSRS self
function MSRS:PlayTextFile(TextFile, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayTextFile, self, TextFile, 0)
else
-- First check if text file exists!
local exists=UTILS.FileExists(TextFile)
if not exists then
self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile))
return self
end
-- Get command line.
local command=self:_GetCommand()
-- Append text file.
command=command..string.format(" --textFile=\"%s\"", tostring(TextFile))
-- Debug output.
self:T(string.format("MSRS TextFile command=%s", command))
-- Count length of command.
local l=string.len(command)
-- Execute command.
self:_ExecCommand(command)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
-- @param #MSRS self
-- @param #string command Command to executer
-- @return #number Return value of os.execute() command.
function MSRS:_ExecCommand(command)
-- Create a tmp file.
local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat"
local script=io.open(filename, "w+")
script:write(command.." && exit")
script:close()
-- Play command.
command=string.format('start /b "" "%s"', filename)
local res=nil
if true then
-- Create a tmp file.
local filenvbs = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".vbs"
-- VBS script
local script = io.open(filenvbs, "w+")
script:write(string.format('Dim WinScriptHost\n'))
script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n'))
script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n', filename))
script:write(string.format('Set WinScriptHost = Nothing'))
script:close()
-- Run visual basic script. This still pops up a window but very briefly and does not put the DCS window out of focus.
local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs)
-- Debug output.
self:T("MSRS execute command="..command)
self:T("MSRS execute VBS command="..runvbs)
-- Play file in 0.01 seconds
res=os.execute(runvbs)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1)
else
-- Debug output.
self:T("MSRS execute command="..command)
-- Execute command
res=os.execute(command)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
end
return res
end
--- Get lat, long and alt from coordinate.
-- @param #MSRS self
-- @param Core.Point#Coordinate Coordinate Coordinate. Can also be a DCS#Vec3.
-- @return #number Latitude.
-- @return #number Longitude.
-- @return #number Altitude.
function MSRS:_GetLatLongAlt(Coordinate)
local lat, lon, alt=coord.LOtoLL(Coordinate)
return lat, lon, math.floor(alt)
end
--- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
-- @param #MSRS self
-- @param #table freqs Frequencies in MHz.
-- @param #table modus Modulations.
-- @param #number coal Coalition.
-- @param #string gender Gender.
-- @param #string voice Voice.
-- @param #string culture Culture.
-- @param #number volume Volume.
-- @param #number speed Speed.
-- @param #number port Port.
-- @return #string Command.
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
local path=self:GetPath() or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
freqs=table.concat(freqs or self.frequencies, ",")
modus=table.concat(modus or self.modulations, ",")
coal=coal or self.coalition
gender=gender or self.gender
voice=voice or self.voice
culture=culture or self.culture
volume=volume or self.volume
speed=speed or self.speed
port=port or self.port
-- Replace modulation
modus=modus:gsub("0", "AM")
modus=modus:gsub("1", "FM")
-- This did not work well. Stopped if the transmission was a bit longer with no apparent error.
--local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed)
-- Command from orig STTS script. Works better for some unknown reason!
local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
--local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT")
-- Command.
local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT")
-- Set voice or gender/culture.
if voice then
-- Use a specific voice (no need for gender and/or culture.
command=command..string.format(" --voice=\"%s\"", tostring(voice))
else
-- Add gender.
if gender and gender~="female" then
command=command..string.format(" --gender=%s", tostring(gender))
end
-- Add culture.
if culture and culture~="en-GB" then
command=command..string.format(" -l %s", tostring(culture))
end
end
-- Set coordinate.
if self.coordinate then
local lat,lon,alt=self:_GetLatLongAlt(self.coordinate)
command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt)
end
-- Set google.
if self.google then
command=command..string.format(' --ssml -G "%s"', self.google)
end
-- Debug output.
self:T("MSRS command="..command)
return command
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,408 @@
--- **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.
-- @return #SOUNDFILE self
function SOUNDFILE:SetPath(Path)
-- Init path.
self.path=Path or "l10n/DEFAULT/"
-- 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
do -- UserSound

View File

@@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
self:SetAutoAcceptTasks( true )
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
self:SetFlashStatus( false )
self:SetMessageDuration(10)
self:HandleEvent( EVENTS.Birth,
--- @param #COMMANDCENTER self
@@ -682,7 +683,7 @@ end
-- @param #string Message The message text.
function COMMANDCENTER:MessageToAll( Message )
self:GetPositionable():MessageToAll( Message, 20, self:GetName() )
self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
end
@@ -692,7 +693,7 @@ end
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() )
self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
end
@@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
end
@@ -795,9 +796,18 @@ end
--- Let the command center flash a report of the status of the subscribed task to a group.
-- @param #COMMANDCENTER self
-- @param Flash #boolean
function COMMANDCENTER:SetFlashStatus( Flash )
self:F()
self.FlashStatus = Flash 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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
--- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
--
-- ### Contributions:
--
-- ===
--
--
-- @module Tasking.Task_A2G
-- @image MOOSE.JPG
@@ -18,29 +18,29 @@ do -- TASK_A2G
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @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}.
-- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
--
--
-- * **None**: Start of the process
-- * **Planned**: The A2G task is planned.
-- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}.
-- * **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.
--
--
-- ## 1) Set the scoring of achievements in an A2G attack.
--
--
-- 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.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.
--
--
-- @field #TASK_A2G
TASK_A2G = {
ClassName = "TASK_A2G",
ClassName = "TASK_A2G"
}
--- Instantiates a new TASK_A2G.
-- @param #TASK_A2G self
-- @param Tasking.Mission#MISSION Mission
@@ -54,53 +54,51 @@ do -- TASK_A2G
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
self:F()
self.TargetSetUnit = TargetSetUnit
self.TaskType = TaskType
local Fsm = self:GetUnitProcess()
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
--Fsm:AddTransition( "Accounted", "Success", "Success" )
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit
self:RouteToRendezVous()
end
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit
if Task:GetRendezVousZone( TaskUnit ) then
self:__RouteToRendezVousZone( 0.1 )
else
@@ -112,36 +110,36 @@ do -- TASK_A2G
end
end
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2G Task
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
self:__Engage( 0.1 )
self:__Engage( 0.1 )
end
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2G Task
function Fsm:onafterEngage( TaskUnit, Task )
self:F( { self } )
self:__Account( 0.1 )
self:__RouteToTarget(0.1 )
self:__RouteToTarget( 0.1 )
self:__RouteToTargets( -10 )
end
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterRouteToTarget( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
self:__RouteToTargetZone( 0.1 )
else
@@ -154,8 +152,8 @@ do -- TASK_A2G
self:__RouteToTargetPoint( 0.1 )
end
end
--- Test
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task
@@ -167,20 +165,18 @@ do -- TASK_A2G
end
self:__RouteToTargets( -10 )
end
return self
end
--- @param #TASK_A2G self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2G:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit
end
--- @param #TASK_A2G self
function TASK_A2G:GetPlannedMenuText()
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 #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
ActRouteRendezVous:SetRange( RendezVousRange )
end
--- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
function TASK_A2G:GetRendezVousCoordinate( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
end
--- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
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
return ActRouteRendezVous:GetZone()
end
--- @param #TASK_A2G self
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteTarget:SetCoordinate( TargetCoordinate )
end
--- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit
@@ -258,18 +251,16 @@ do -- TASK_A2G
return ActRouteTarget:GetCoordinate()
end
--- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetZone( TargetZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteTarget:SetZone( TargetZone )
end
--- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit
@@ -283,47 +274,46 @@ do -- TASK_A2G
end
function TASK_A2G:SetGoalTotal()
self.GoalTotal = self.TargetSetUnit:Count()
end
function TASK_A2G:GetGoalTotal()
return self.GoalTotal
end
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
-- @param #TASK_A2G self
function TASK_A2G:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem )
function TASK_A2G:ReportOrder( ReportGroup )
self:UpdateTaskInfo( self.DetectedItem )
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- This method checks every 10 seconds if the goal has been reached of the task.
-- @param #TASK_A2G self
function TASK_A2G:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
--- @param #TASK_A2G self
function TASK_A2G:UpdateTaskInfo( DetectedItem )
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:AddCoordinate( TargetCoordinate, 1, "SOD" )
local ThreatLevel, ThreatText
if DetectedItem then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem )
@@ -331,7 +321,7 @@ do -- TASK_A2G
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
@@ -344,33 +334,33 @@ do -- TASK_A2G
end
end
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
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
end
self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" )
self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" )
self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" )
end
end
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
-- @param #TASK_A2G self
-- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
self:F({Distance=Distance})
self:F( { Distance = Distance } )
return math.floor( Distance )
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
return 1
@@ -379,8 +369,7 @@ do -- TASK_A2G
return 0
end
end
end
do -- TASK_A2G_SEAD
@@ -397,9 +386,9 @@ do -- TASK_A2G_SEAD
--
-- @field #TASK_A2G_SEAD
TASK_A2G_SEAD = {
ClassName = "TASK_A2G_SEAD",
ClassName = "TASK_A2G_SEAD"
}
--- Instantiates a new TASK_A2G_SEAD.
-- @param #TASK_A2G_SEAD self
-- @param Tasking.Mission#MISSION Mission
@@ -408,19 +397,16 @@ do -- TASK_A2G_SEAD
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task.
-- @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
self:F()
Mission:AddTask( self )
self:SetBriefing(
TaskBriefing or
"Execute a Suppression of Enemy Air Defenses."
)
self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." )
return self
end
end
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_SEAD self
@@ -434,7 +420,7 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score )
return self
end
@@ -450,7 +436,7 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score )
return self
end
@@ -466,11 +452,10 @@ do -- TASK_A2G_SEAD
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty )
return self
end
end
do -- TASK_A2G_BAI
@@ -488,10 +473,8 @@ do -- TASK_A2G_BAI
-- based on detected enemy ground targets.
--
-- @field #TASK_A2G_BAI
TASK_A2G_BAI = {
ClassName = "TASK_A2G_BAI",
}
TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" }
--- Instantiates a new TASK_A2G_BAI.
-- @param #TASK_A2G_BAI self
-- @param Tasking.Mission#MISSION Mission
@@ -503,14 +486,11 @@ do -- TASK_A2G_BAI
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
self:F()
Mission:AddTask( self )
self:SetBriefing(
TaskBriefing or
"Execute a Battlefield Air Interdiction of a group of enemy targets."
)
self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." )
return self
end
@@ -526,7 +506,7 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score )
return self
end
@@ -542,7 +522,7 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score )
return self
end
@@ -558,15 +538,12 @@ do -- TASK_A2G_BAI
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty )
return self
end
end
do -- TASK_A2G_CAS
--- The TASK_A2G_CAS class
@@ -581,10 +558,8 @@ do -- TASK_A2G_CAS
-- based on detected enemy ground targets.
--
-- @field #TASK_A2G_CAS
TASK_A2G_CAS = {
ClassName = "TASK_A2G_CAS",
}
TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" }
--- Instantiates a new TASK_A2G_CAS.
-- @param #TASK_A2G_CAS self
-- @param Tasking.Mission#MISSION Mission
@@ -596,19 +571,13 @@ do -- TASK_A2G_CAS
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
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
end
end
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_CAS self
@@ -622,7 +591,7 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score )
return self
end
@@ -638,7 +607,7 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score )
return self
end
@@ -654,9 +623,8 @@ do -- TASK_A2G_CAS
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty )
return self
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:**
--
--
-- * Dynamically assign tasks to human players based on detected targets.
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
-- * Dynamically assign (CAS) Close Air Support 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 different ranges to engage upon intruders.
-- * Keep task achievements.
-- * Score task achievements.--
-- * Score task achievements.
--
-- ===
--
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
--
-- ### Contributions:
--
-- ===
--
--
-- @module Tasking.Task_A2G_Dispatcher
-- @image Task_A2G_Dispatcher.JPG
@@ -32,151 +33,151 @@ do -- TASK_A2G_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- 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 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**.
--
--
-- 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}.
--
--
-- 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.
-- 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..
-- 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.
-- - **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.
--
-- # 0. Tactical Situations
--
--
-- 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.
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG)
--
--
-- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test],
-- the tactical situation is a demonstration how the A2G detection works.
-- This example will be taken further in the explanation in the following chapters.
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG)
--
--
-- The red coalition are the players, the blue coalition is the enemy.
--
--
-- 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
-- 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.
-- 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.
-- For demonstration purposes, the attack airplane is stationed on the ground to explain
-- the messages and the menus properly.
-- Further test missions demonstrate the A2G task dispatcher from within air.
--
--
-- Depending upon the detection results, the A2G dispatcher will create different tasks.
--
--
-- # 0.1. SEAD Task
--
--
-- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area.
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG)
--
--
-- - 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!
--
--
-- # 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.
--
--
-- ![](..\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!
-- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support.
--
--
-- # 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.
--
--
-- ![](..\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!
--
-- # 1. Player Experience
--
--
-- 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**.
--
--
-- 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:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha (Primary)"
-- F2. Mission "Beta (Secondary)"
-- F3. Mission "Gamma (Tactical)"
-- F1. Command Center [Lima]
-- 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.
--
-- ## 1.1. Mission Menu (Under the Command Center Menu)
--
--
-- 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.
-- - **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 Mission Reports**: A menu to create various reports on the current mission.
--
--
-- For CC [Lima], Mission "Overlord", the menu structure could look like this:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Lima]
-- F1. Mission "Overlord"
-- F1. Mission Briefing
-- F2. Mark Task Locations on Map
-- F3. Task Reports
-- F4. Mission Reports
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG)
--
--
-- ### 1.1.1. Mission Briefing Menu
--
--
-- 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,
-- and explains the mission goals.
--
--
-- and explains the mission goals.
--
--
-- ### 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.
-- 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.
-- 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
--
--
-- 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.
-- - **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.
-- - **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.
--
--
-- 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:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -188,21 +189,21 @@ do -- TASK_A2G_DISPATCHER
-- F4. Successful Tasks
-- F5. Failed Tasks
-- 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.
-- (See later).
--
--
-- ### 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:
--
-- - **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.
--
--
-- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -211,25 +212,25 @@ do -- TASK_A2G_DISPATCHER
-- F4. Mission Reports
-- F1. Report Mission Progress
-- F2. Report Players per Task
--
--
--
--
-- ## 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!**
-- 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),
-- **the Mission Menu will contain extra Menus to control specific Tasks.**
--
--
-- #### 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:
--
--
-- - 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:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -237,23 +238,23 @@ do -- TASK_A2G_DISPATCHER
-- F3. Task Reports
-- F4. Mission Reports
-- 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.**
--
-- #### 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:
--
--
-- - 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.
-- 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,
-- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -261,26 +262,25 @@ do -- TASK_A2G_DISPATCHER
-- F3. Task Reports
-- F4. Mission Reports
-- F5. Assigned Task CAS.001
--
--
-- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.**
--
--
--
-- ## 1.3. Join Planned Task Menu
--
--
-- 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)**.
--
-- For example, for CC [Gori], Mission "Alpha",
-- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**.
--
-- For example, for CC [Gori], Mission "Alpha",
-- if a Mission "ALpha" contains 5 Planned Tasks, which would be:
--
-- - 2 CAS Tasks
--
-- - 2 CAS Tasks
-- - 1 BAI Task
-- - 2 SEAD Tasks
--
--
-- the Join Planned Task Menu Hierarchy could look like this:
--
--
-- Radio MENU Structure (F10. Other)
--
--
-- F1. Command Center [Gori]
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -296,26 +296,26 @@ do -- TASK_A2G_DISPATCHER
-- F1. SEAD.003
-- F2. SEAD.004
-- F3. SEAD.005
--
--
-- An example from within a running simulation:
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG)
--
--
-- 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**!
--
-- ### 1.3.1. Planned Task Menu
--
-- Each Planned Task Menu will allow for the following actions:
--
--
-- - 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.
-- - 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)
--
--
-- F1. Command Center |Gori|
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -328,22 +328,22 @@ do -- TASK_A2G_DISPATCHER
-- F1. Report Task Details
-- F2. Mark Task Location on Map
-- F3. Join Task
--
--
-- **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
--
--
-- 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:
--
--
-- - **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.
-- - **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:
--
--
-- F1. Command Center |Gori|
-- F1. Mission "Alpha"
-- F1. Mission Briefing
@@ -354,90 +354,89 @@ do -- TASK_A2G_DISPATCHER
-- F1. Report Task Details
-- F2. Mark Task Location on Map
-- F3. Abort Task
--
--
-- 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
--
--
-- During game play, different messages are displayed.
-- 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.
--
--
-- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG)
--
--
-- The @{Settings} menu provides additional options to control the timing of the messages.
-- There are:
--
--
-- - 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.
-- - 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.
-- - 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
--
--
-- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance.
--
-- # 3. Usage
--
-- To use the TASK\_A2G\_DISPATCHER class, you need:
--
--
-- - A @{CommandCenter} object. The master communication channel.
-- - A @{Mission} object. Each task belongs to a Mission.
-- - A @{Detection} object. There are several detection grouping methods to choose from.
-- - 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.
--
-- 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
-- :FindByName( "HQ", "Bravo HQ" )
--
-- local CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" )
--
--
-- -- Declare the Mission for the Command Center.
-- local Mission = MISSION
-- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED )
--
--
-- -- Define the RecceSet that will detect the enemy.
-- local RecceSet = SET_GROUP
-- :New()
-- :FilterPrefixes( "FAC" )
-- :FilterCoalitions("red")
-- :FilterStart()
--
--
-- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius.
-- local DetectionAreas = DETECTION_AREAS
-- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies.
--
--
-- -- 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
-- :New() -- Create the SET_GROUP object.
-- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions.
-- :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.
--
--
-- -- Now we have everything to setup the main A2G TaskDispatcher.
-- 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
TASK_A2G_DISPATCHER = {
ClassName = "TASK_A2G_DISPATCHER",
Mission = nil,
Detection = nil,
Tasks = {},
Tasks = {}
}
--- TASK_A2G_DISPATCHER constructor.
-- @param #TASK_A2G_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
@@ -445,18 +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.
-- @return #TASK_A2G_DISPATCHER self
function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER
self.Detection = Detection
self.Mission = Mission
self.FlashNewTask = true --set to false to suppress flash messages
self.FlashNewTask = true -- set to false to suppress flash messages
self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } )
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign
-- @param #TASK_A2G_DISPATCHER self
@@ -466,19 +465,19 @@ do -- TASK_A2G_DISPATCHER
-- @param Tasking.Task_A2G#TASK_A2G Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName
self:__Start( 5 )
return self
end
--- Set flashing player messages on or off
--- 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
self.FlashNewTask = onoff
end
--- Creates a SEAD task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
@@ -486,7 +485,7 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone
@@ -500,10 +499,10 @@ do -- TASK_A2G_DISPATCHER
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterHasSEAD()
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
@@ -514,11 +513,10 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone
-- Determine if the set has ground units.
-- There should be ground unit friendlies nearby. Airborne units are valid friendlies types.
-- And there shouldn't be any radar.
@@ -532,13 +530,13 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
--- Creates a BAI task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
@@ -546,11 +544,10 @@ do -- TASK_A2G_DISPATCHER
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition )
self:F( { DetectedItem.ItemID } )
local DetectedSet = DetectedItem.Set
local DetectedZone = DetectedItem.Zone
-- Determine if the set has ground units.
-- There shouldn't be any ground unit friendlies nearby.
-- And there shouldn't be any radar.
@@ -564,19 +561,18 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = SET_UNIT:New()
TargetSetUnit:SetDatabase( DetectedSet )
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
return TargetSetUnit
end
return nil
end
function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil
end
--- Evaluates the removal of the Task from the Mission.
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
-- @param #TASK_A2G_DISPATCHER self
@@ -586,17 +582,16 @@ do -- TASK_A2G_DISPATCHER
-- @param #boolean DetectedItemChange
-- @return Tasking.Task#TASK
function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged )
if Task then
if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then
--self:F( "Removing Tasking: " .. Task:GetTaskName() )
if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then
-- self:F( "Removing Tasking: " .. Task:GetTaskName() )
self:RemoveTask( TaskIndex )
end
end
return Task
end
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
-- @param #TASK_A2G_DISPATCHER self
@@ -604,15 +599,15 @@ do -- TASK_A2G_DISPATCHER
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_A2G_DISPATCHER:ProcessDetected( Detection )
self:F()
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
local Mission = self.Mission
if Mission:IsIDLE() or Mission:IsENGAGED() then
local TaskReport = REPORT:New()
-- Checking the task queue for the dispatcher, and removing any obsolete task!
@@ -634,21 +629,21 @@ do -- TASK_A2G_DISPATCHER
--- First we need to the detected targets.
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedZone = DetectedItem.Zone
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
--DetectedSet:Flush( self )
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
-- DetectedSet:Flush( self )
local DetectedItemID = DetectedItem.ID
local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } )
local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G
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 Task:IsStateAssigned() then
@@ -660,7 +655,7 @@ do -- TASK_A2G_DISPATCHER
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
Task:Cancel()
end
@@ -691,18 +686,18 @@ do -- TASK_A2G_DISPATCHER
end
end
end
-- Now we send to each group the changes, if any.
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
local TargetsText = TargetsReport:Text(", ")
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" and self.FlashNewTask then
local TargetsText = TargetsReport:Text( ", " )
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 )
end
end
end
end
end
if Task then
if Task:IsStatePlanned() then
if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set
@@ -753,6 +748,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
if TargetSetUnit then
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 )
end
@@ -761,6 +757,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
if TargetSetUnit then
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 )
end
@@ -769,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...
if TargetSetUnit then
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 )
end
end
end
if Task then
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone )
Task:SetDispatcher( self )
Task:UpdateTaskInfo( DetectedItem )
Mission:AddTask( Task )
function Task.OnEnterSuccess( Task, From, Event, To )
self:Success( Task )
end
@@ -788,7 +786,7 @@ do -- TASK_A2G_DISPATCHER
function Task.OnEnterCancelled( Task, From, Event, To )
self:Cancelled( Task )
end
function Task.OnEnterFailed( Task, From, Event, To )
self:Failed( Task )
end
@@ -796,31 +794,29 @@ do -- TASK_A2G_DISPATCHER
function Task.OnEnterAborted( Task, From, Event, To )
self:Aborted( Task )
end
TaskReport:Add( Task:GetName() )
else
self:F("This should not happen")
self:F( "This should not happen" )
end
end
-- OK, so the tasking has been done, now delete the changes reported for the area.
Detection:AcceptChanges( DetectedItem )
end
-- TODO set menus using the HQ coordinator
Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text(", ")
local TaskText = TaskReport:Text( ", " )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and self.FlashNewTask 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 )
end
end
end
return true
end

View File

@@ -1,18 +1,18 @@
--- **Utilities** Enumerators.
--
--
-- An enumerator is a variable that holds a constant value. Enumerators are very useful because they make the code easier to read and to change in general.
--
--
-- For example, instead of using the same value at multiple different places in your code, you should use a variable set to that value.
-- If, for whatever reason, the value needs to be changed, you only have to change the variable once and do not have to search through you code and reset
-- every value by hand.
--
--
-- Another big advantage is that the LDT intellisense "knows" the enumerators. So you can use the autocompletion feature and do not have to keep all the
-- values in your head or look them up in the docs.
--
-- values in your head or look them up in the docs.
--
-- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit.
--
-- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
--
--
-- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
--
-- @module ENUMS
-- @image MOOSE.JPG
@@ -20,7 +20,7 @@
-- @type ENUMS
--- Because ENUMS are just better practice.
--
--
-- The ENUMS class adds some handy variables, which help you to make your code better and more general.
--
-- @field #ENUMS
@@ -30,16 +30,16 @@ ENUMS = {}
-- @type ENUMS.ROE
-- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target.
-- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking.
-- @field #number OpenFire AI will engage only targets specified in its taskings.
-- @field #number OpenFire AI will engage only targets specified in its tasking.
-- @field #number ReturnFire AI will only engage threats that shoot first.
-- @field #number WeaponHold AI will hold fire under all circumstances.
ENUMS.ROE = {
WeaponFree=0,
OpenFireWeaponFree=1,
OpenFire=2,
ReturnFire=3,
WeaponHold=4,
}
WeaponFree = 0,
OpenFireWeaponFree = 1,
OpenFire = 2,
ReturnFire = 3,
WeaponHold = 4,
}
--- Reaction On Threat.
-- @type ENUMS.ROT
@@ -49,11 +49,11 @@ ENUMS.ROE = {
-- @field #number BypassAndEscape AI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats.
-- @field #number AllowAbortMission If a threat is deemed severe enough the AI will abort its mission and return to base.
ENUMS.ROT = {
NoReaction=0,
PassiveDefense=1,
EvadeFire=2,
BypassAndEscape=3,
AllowAbortMission=4,
NoReaction = 0,
PassiveDefense = 1,
EvadeFire = 2,
BypassAndEscape = 3,
AllowAbortMission = 4,
}
--- Alarm state.
@@ -62,12 +62,12 @@ ENUMS.ROT = {
-- @field #number Green Group is not combat ready. Sensors are stowed if possible.
-- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state.
ENUMS.AlarmState = {
Auto=0,
Green=1,
Red=2,
Auto = 0,
Green = 1,
Red = 2,
}
--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki.
--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on Hoggit wiki.
-- @type ENUMS.WeaponFlag
ENUMS.WeaponFlag={
-- Bombs
@@ -111,7 +111,7 @@ ENUMS.WeaponFlag={
--
-- Bombs
GuidedBomb = 14, -- (LGB + TvGB + SNSGB)
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb)
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispenser + CandleBomb + ParachuteBomb)
AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb)
--- Rockets
AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket
@@ -123,7 +123,7 @@ ENUMS.WeaponFlag={
--- Air-To-Air Missiles
AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
AnyMissile = 268402688, -- AnyASM + AnyAAM
AnyMissile = 268402688, -- AnyASM + AnyAAM
--- Guns
Cannons = 805306368, -- GUN_POD + BuiltInCannon
---
@@ -133,7 +133,7 @@ ENUMS.WeaponFlag={
AnyAG = 2956984318, -- Any Air-To-Ground Weapon
AnyAA = 264241152, -- Any Air-To-Air Weapon
AnyUnguided = 2952822768, -- Any Unguided Weapon
AnyGuided = 268402702, -- Any Guided Weapon
AnyGuided = 268402702, -- Any Guided Weapon
}
--- Mission tasks.
@@ -173,7 +173,7 @@ ENUMS.MissionTask={
TRANSPORT="Transport",
}
--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki.
-- @type ENUMS.Formation
ENUMS.Formation={}
ENUMS.Formation.FixedWing={}
@@ -216,23 +216,23 @@ ENUMS.Formation.FixedWing.FighterVic.Close = 917505
ENUMS.Formation.FixedWing.FighterVic.Open = 917506
ENUMS.Formation.RotaryWing={}
ENUMS.Formation.RotaryWing.Column={}
ENUMS.Formation.RotaryWing.Column.D70=720896
ENUMS.Formation.RotaryWing.Column.D70 = 720896
ENUMS.Formation.RotaryWing.Wedge={}
ENUMS.Formation.RotaryWing.Wedge.D70=8
ENUMS.Formation.RotaryWing.Wedge.D70 = 8
ENUMS.Formation.RotaryWing.FrontRight={}
ENUMS.Formation.RotaryWing.FrontRight.D300=655361
ENUMS.Formation.RotaryWing.FrontRight.D600=655362
ENUMS.Formation.RotaryWing.FrontRight.D300 = 655361
ENUMS.Formation.RotaryWing.FrontRight.D600 = 655362
ENUMS.Formation.RotaryWing.FrontLeft={}
ENUMS.Formation.RotaryWing.FrontLeft.D300=655617
ENUMS.Formation.RotaryWing.FrontLeft.D600=655618
ENUMS.Formation.RotaryWing.FrontLeft.D300 = 655617
ENUMS.Formation.RotaryWing.FrontLeft.D600 = 655618
ENUMS.Formation.RotaryWing.EchelonRight={}
ENUMS.Formation.RotaryWing.EchelonRight.D70 =589825
ENUMS.Formation.RotaryWing.EchelonRight.D300=589826
ENUMS.Formation.RotaryWing.EchelonRight.D600=589827
ENUMS.Formation.RotaryWing.EchelonRight.D70 = 589825
ENUMS.Formation.RotaryWing.EchelonRight.D300 = 589826
ENUMS.Formation.RotaryWing.EchelonRight.D600 = 589827
ENUMS.Formation.RotaryWing.EchelonLeft={}
ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
ENUMS.Formation.RotaryWing.EchelonLeft.D70 = 590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300 = 590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600 = 590083
ENUMS.Formation.Vehicle={}
ENUMS.Formation.Vehicle.Vee="Vee"
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
@@ -244,34 +244,34 @@ ENUMS.Formation.Vehicle.Cone="Cone"
ENUMS.Formation.Vehicle.Diamond="Diamond"
--- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings.
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki.
-- @type ENUMS.FormationOld
ENUMS.FormationOld={}
ENUMS.FormationOld.FixedWing={}
ENUMS.FormationOld.FixedWing.LineAbreast=1
ENUMS.FormationOld.FixedWing.Trail=2
ENUMS.FormationOld.FixedWing.Wedge=3
ENUMS.FormationOld.FixedWing.EchelonRight=4
ENUMS.FormationOld.FixedWing.EchelonLeft=5
ENUMS.FormationOld.FixedWing.FingerFour=6
ENUMS.FormationOld.FixedWing.SpreadFour=7
ENUMS.FormationOld.FixedWing.BomberElement=12
ENUMS.FormationOld.FixedWing.BomberElementHeight=13
ENUMS.FormationOld.FixedWing.FighterVic=14
ENUMS.FormationOld.FixedWing.LineAbreast = 1
ENUMS.FormationOld.FixedWing.Trail = 2
ENUMS.FormationOld.FixedWing.Wedge = 3
ENUMS.FormationOld.FixedWing.EchelonRight = 4
ENUMS.FormationOld.FixedWing.EchelonLeft = 5
ENUMS.FormationOld.FixedWing.FingerFour = 6
ENUMS.FormationOld.FixedWing.SpreadFour = 7
ENUMS.FormationOld.FixedWing.BomberElement = 12
ENUMS.FormationOld.FixedWing.BomberElementHeight = 13
ENUMS.FormationOld.FixedWing.FighterVic = 14
ENUMS.FormationOld.RotaryWing={}
ENUMS.FormationOld.RotaryWing.Wedge=8
ENUMS.FormationOld.RotaryWing.Echelon=9
ENUMS.FormationOld.RotaryWing.Front=10
ENUMS.FormationOld.RotaryWing.Column=11
ENUMS.FormationOld.RotaryWing.Wedge = 8
ENUMS.FormationOld.RotaryWing.Echelon = 9
ENUMS.FormationOld.RotaryWing.Front = 10
ENUMS.FormationOld.RotaryWing.Column = 11
--- Morse Code. See the [Wikipedia](https://en.wikipedia.org/wiki/Morse_code).
--
--
-- * Short pulse "*"
-- * Long pulse "-"
--
--
-- Pulses are separated by a blank character " ".
--
--
-- @type ENUMS.Morse
ENUMS.Morse={}
ENUMS.Morse.A="* -"
@@ -310,4 +310,53 @@ ENUMS.Morse.N7="- - * * *"
ENUMS.Morse.N8="- - - * *"
ENUMS.Morse.N9="- - - - *"
ENUMS.Morse.N0="- - - - -"
ENUMS.Morse[" "]=" "
ENUMS.Morse[" "]=" "
--- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
--
-- @type ENUMS.ISOLang
ENUMS.ISOLang =
{
Arabic = 'AR',
Chinese = 'ZH',
English = 'EN',
French = 'FR',
German = 'DE',
Russian = 'RU',
Spanish = 'ES',
Japanese = 'JA',
Italian = 'IT',
}
--- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet).
--
-- @type ENUMS.Phonetic
ENUMS.Phonetic =
{
A = 'Alpha',
B = 'Bravo',
C = 'Charlie',
D = 'Delta',
E = 'Echo',
F = 'Foxtrot',
G = 'Golf',
H = 'Hotel',
I = 'India',
J = 'Juliett',
K = 'Kilo',
L = 'Lima',
M = 'Mike',
N = 'November',
O = 'Oscar',
P = 'Papa',
Q = 'Quebec',
R = 'Romeo',
S = 'Sierra',
T = 'Tango',
U = 'Uniform',
V = 'Victor',
W = 'Whiskey',
X = 'Xray',
Y = 'Yankee',
Z = 'Zulu',
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
--- **Utilities** DCS Simple Text-To-Speech (STTS).
--
--
-- @module Utils.STTS
-- @image MOOSE.JPG
--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world)
-- @type STTS
-- @field #string DIRECTORY Path of the SRS directory.
--- Simple Text-To-Speech
--
-- Version 0.4 - Compatible with SRS version 1.9.6.0+
--
-- # DCS Modification Required
--
-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization.
-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)"
-- Do this without DCS running to allow mission scripts to use os functions.
--
-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE*
--
-- # USAGE:
--
-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it
-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission.
-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts.
--
-- Example calls:
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2)
--
-- Arguments in order are:
--
-- * Message to say, make sure not to use a newline (\n) !
-- * Frequency in MHz
-- * Modulation - AM/FM
-- * Volume - 1.0 max, 0.5 half
-- * Name of the transmitter - ATC, RockFM etc
-- * Coalition - 0 spectator, 1 red 2 blue
-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed
-- * OPTIONAL - Speed -10 to +10
-- * OPTIONAL - Gender male, female or neuter
-- * OPTIONAL - Culture - en-US, en-GB etc
-- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line
-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly
--
--
-- ## Example
--
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB")
--
-- ## Example
--
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT"
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB")
--
-- Arguments in order are:
--
-- * FULL path to the MP3 OR OGG to play
-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations
-- * Modulation - AM/FM - to use multiple
-- * Volume - 1.0 max, 0.5 half
-- * Name of the transmitter - ATC, RockFM etc
-- * Coalition - 0 spectator, 1 red 2 blue
--
-- ## Example
--
-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only
--
-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0)
--
-- @field #STTS
STTS = {
ClassName = "STTS",
DIRECTORY = "",
SRS_PORT = 5002,
GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json",
EXECUTABLE = "DCS-SR-ExternalAudio.exe"
}
--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER
STTS.DIRECTORY = "D:/DCS/_SRS"
--- LOCAL SRS PORT - DEFAULT IS 5002
STTS.SRS_PORT = 5002
--- Google credentials file
STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json"
--- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING
STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe"
--- Function for UUID.
function STTS.uuid()
local random = math.random
local template = 'yxxx-xxxxxxxxxxxx'
return string.gsub( template, '[xy]', function( c )
local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb )
return string.format( '%x', v )
end )
end
--- Round a number.
-- @param #number x Number.
-- @param #number n Precision.
function STTS.round( x, n )
n = math.pow( 10, n or 0 )
x = x * n
if x >= 0 then
x = math.floor( x + 0.5 )
else
x = math.ceil( x - 0.5 )
end
return x / n
end
--- Function returns estimated speech time in seconds.
-- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so
--
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
--
-- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
--
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
--
function STTS.getSpeechTime( length, speed, isGoogle )
local maxRateRatio = 3
speed = speed or 1.0
isGoogle = isGoogle or false
local speedFactor = 1.0
if isGoogle then
speedFactor = speed
else
if speed ~= 0 then
speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1
end
if speed < 0 then
speedFactor = 1 / speedFactor
end
end
local wpm = math.ceil( 100 * speedFactor )
local cps = math.floor( (wpm * 5) / 60 )
if type( length ) == "string" then
length = string.len( length )
end
return math.ceil( length / cps )
end
--- Text to speech function.
function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS )
if os == nil or io == nil then
env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " )
return
end
speed = speed or 1
gender = gender or "female"
culture = culture or ""
voice = voice or ""
coalition = coalition or "0"
name = name or "ROBOT"
volume = 1
speed = 1
message = message:gsub( "\"", "\\\"" )
local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name )
if voice ~= "" then
cmd = cmd .. string.format( " -V \"%s\"", voice )
else
if culture ~= "" then
cmd = cmd .. string.format( " -l %s", culture )
end
if gender ~= "" then
cmd = cmd .. string.format( " -g %s", gender )
end
end
if googleTTS == true then
cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS )
end
if speed ~= 1 then
cmd = cmd .. string.format( " -s %s", speed )
end
if volume ~= 1.0 then
cmd = cmd .. string.format( " -v %s", volume )
end
if point and type( point ) == "table" and point.x then
local lat, lon, alt = coord.LOtoLL( point )
lat = STTS.round( lat, 4 )
lon = STTS.round( lon, 4 )
alt = math.floor( alt )
cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt )
end
cmd = cmd .. string.format( " -t \"%s\"", message )
if string.len( cmd ) > 255 then
local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat"
local script = io.open( filename, "w+" )
script:write( cmd .. " && exit" )
script:close()
cmd = string.format( "\"%s\"", filename )
timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 )
end
if string.len( cmd ) > 255 then
env.info( "[DCS-STTS] - cmd string too long" )
env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" )
end
os.execute( cmd )
return STTS.getSpeechTime( message, speed, googleTTS )
end
--- Play mp3 function.
-- @param #string pathToMP3 Path to the sound file.
-- @param #string freqs Frequencies, e.g. "305, 256".
-- @param #string modulations Modulations, e.g. "AM, FM".
-- @param #string volume Volume, e.g. "0.5".
function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point )
local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" )
if point and type( point ) == "table" and point.x then
local lat, lon, alt = coord.LOtoLL( point )
lat = STTS.round( lat, 4 )
lon = STTS.round( lon, 4 )
alt = math.floor( alt )
cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt )
end
env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" )
os.execute( cmd )
end

View File

@@ -0,0 +1,612 @@
--- **Utils** Templates
--
-- DCS unit templates
--
-- @module Utilities.Templates
-- @image MOOSE.JPG
--- TEMPLATE class.
-- @type TEMPLATE
-- @field #string ClassName Name of the class.
--- *Templates*
--
-- ===
--
-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg)
--
-- Get DCS templates from thin air.
--
-- # Ground Units
--
-- Ground units.
--
-- # Naval Units
--
-- Ships are not implemented yet.
--
-- # Aircraft
--
-- ## Airplanes
--
-- Airplanes are not implemented yet.
--
-- ## Helicopters
--
-- Helicopters are not implemented yet.
--
-- @field #TEMPLATE
TEMPLATE = {
ClassName = "TEMPLATE",
Ground = {},
Naval = {},
Airplane = {},
Helicopter = {},
}
--- Ground unit type names.
-- @type TEMPLATE.TypeGround
-- @param #string InfantryAK
TEMPLATE.TypeGround={
InfantryAK="Infantry AK",
ParatrooperAKS74="Paratrooper AKS-74",
ParatrooperRPG16="Paratrooper RPG-16",
SoldierWWIIUS="soldier_wwii_us",
InfantryM248="Infantry M249",
SoldierM4="Soldier M4",
}
--- Naval unit type names.
-- @type TEMPLATE.TypeNaval
-- @param #string Ticonderoga
TEMPLATE.TypeNaval={
Ticonderoga="TICONDEROG",
}
--- Rotary wing unit type names.
-- @type TEMPLATE.TypeAirplane
-- @param #string A10C
TEMPLATE.TypeAirplane={
A10C="A-10C",
}
--- Rotary wing unit type names.
-- @type TEMPLATE.TypeHelicopter
-- @param #string AH1W
TEMPLATE.TypeHelicopter={
AH1W="AH-1W",
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Ground Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get template for ground units.
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
-- @param #string GroupName Name of the spawned group. **Must be unique!**
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
-- @param #number Nunits Number of units. Default 1.
-- @param #number Radius Spawn radius for additonal units in meters. Default 50 m.
-- @return #table Template Template table.
function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
-- Defaults.
TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4
GroupName=GroupName or "Ground-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or {x=0, y=0, z=0}
Nunits=Nunits or 1
Radius=Radius or 50
-- Get generic template.
local template=UTILS.DeepCopy(TEMPLATE.GenericGround)
-- Set group name.
template.name=GroupName
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
template.CategoryID=Unit.Category.GROUND_UNIT
-- Set first unit.
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
if Vec3 then
TEMPLATE.SetPositionFromVec3(template, Vec3)
end
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
return template
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Naval Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get template for ground units.
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
-- @param #string GroupName Name of the spawned group. **Must be unique!**
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
-- @param #number Nunits Number of units. Default 1.
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
-- @return #table Template Template table.
function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
-- Defaults.
TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga
GroupName=GroupName or "Naval-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or {x=0, y=0, z=0}
Nunits=Nunits or 1
Radius=Radius or 500
-- Get generic template.
local template=UTILS.DeepCopy(TEMPLATE.GenericNaval)
-- Set group name.
template.name=GroupName
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
template.CategoryID=Unit.Category.SHIP
-- Set first unit.
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
if Vec3 then
TEMPLATE.SetPositionFromVec3(template, Vec3)
end
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
return template
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Aircraft Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get template for fixed wing units.
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
-- @param #string GroupName Name of the spawned group. **Must be unique!**
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
-- @param #number Nunits Number of units. Default 1.
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
-- @return #table Template Template table.
function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
-- Defaults.
TypeName=TypeName or TEMPLATE.TypeAirplane.A10C
GroupName=GroupName or "Airplane-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or {x=0, y=1000, z=0}
Nunits=Nunits or 1
Radius=Radius or 100
local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
return template
end
--- Get template for fixed wing units.
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
-- @param #string GroupName Name of the spawned group. **Must be unique!**
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
-- @param #number Nunits Number of units. Default 1.
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
-- @return #table Template Template table.
function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
-- Defaults.
TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W
GroupName=GroupName or "Helicopter-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or {x=0, y=500, z=0}
Nunits=Nunits or 1
Radius=Radius or 100
-- Limit unis to 4.
Nunits=math.min(Nunits, 4)
local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
return template
end
--- Get template for aircraft units.
-- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing.
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
-- @param #string GroupName Name of the spawned group. **Must be unique!**
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
-- @param #number Nunits Number of units. Default 1.
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
-- @return #table Template Template table.
function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
-- Defaults.
TypeName=TypeName
GroupName=GroupName or "Aircraft-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or {x=0, y=0, z=0}
Nunits=Nunits or 1
Radius=Radius or 100
-- Get generic template.
local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft)
-- Set group name.
template.name=GroupName
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
if Airplane then
template.CategoryID=Unit.Category.AIRPLANE
else
template.CategoryID=Unit.Category.HELICOPTER
end
-- Set first unit.
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
-- Set position.
if Vec3 then
TEMPLATE.SetPositionFromVec3(template, Vec3)
end
-- Set number of units.
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
return template
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the position of the template.
-- @param #table Template The template to be modified.
-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group.
function TEMPLATE.SetPositionFromVec2(Template, Vec2)
Template.x=Vec2.x
Template.y=Vec2.y
for _,unit in pairs(Template.units) do
unit.x=Vec2.x
unit.y=Vec2.y
end
Template.route.points[1].x=Vec2.x
Template.route.points[1].y=Vec2.y
Template.route.points[1].alt=0 --TODO: Use land height.
end
--- Set the position of the template.
-- @param #table Template The template to be modified.
-- @param DCS#Vec3 Vec3 Position vector of the group.
function TEMPLATE.SetPositionFromVec3(Template, Vec3)
local Vec2={x=Vec3.x, y=Vec3.z}
TEMPLATE.SetPositionFromVec2(Template, Vec2)
end
--- Set the position of the template.
-- @param #table Template The template to be modified.
-- @param #number N Total number of units in the group.
-- @param Core.Point#COORDINATE Coordinate Position of the first unit.
-- @param #number Radius Radius in meters to randomly place the additional units.
function TEMPLATE.SetUnits(Template, N, Coordinate, Radius)
local units=Template.units
local unit1=units[1]
local Vec3=Coordinate:GetVec3()
unit1.x=Vec3.x
unit1.y=Vec3.z
unit1.alt=Vec3.y
for i=2,N do
units[i]=UTILS.DeepCopy(unit1)
end
for i=1,N do
local unit=units[i]
unit.name=string.format("%s-%d", Template.name, i)
if i>1 then
local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2()
unit.x=vec2.x
unit.y=vec2.y
unit.alt=unit1.alt
end
end
end
--- Set the position of the template.
-- @param #table Template The template to be modified.
-- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned.
-- @param #table ParkingSpots List of parking spot IDs. Every unit needs one!
-- @param #boolean EngineOn If true, aircraft are spawned hot.
function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn)
-- Airbase ID.
local AirbaseID=AirBase:GetID()
-- Spawn point.
local point=Template.route.points[1]
-- Set ID.
if AirBase:IsAirdrome() then
point.airdromeId=AirbaseID
else
point.helipadId=AirbaseID
point.linkUnit=AirbaseID
end
if EngineOn then
point.action=COORDINATE.WaypointAction.FromParkingAreaHot
point.type=COORDINATE.WaypointType.TakeOffParkingHot
else
point.action=COORDINATE.WaypointAction.FromParkingArea
point.type=COORDINATE.WaypointType.TakeOffParking
end
for i,unit in ipairs(Template.units) do
unit.parking_id=ParkingSpots[i]
end
end
--- Add a waypoint.
-- @param #table Template The template to be modified.
-- @param #table Waypoint Waypoint table.
function TEMPLATE.AddWaypoint(Template, Waypoint)
table.insert(Template.route.points, Waypoint)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Generic Ground Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TEMPLATE.GenericGround=
{
["visible"] = false,
["tasks"] = {}, -- end of ["tasks"]
["uncontrollable"] = false,
["task"] = "Ground Nothing",
["route"] =
{
["spans"] = {}, -- end of ["spans"]
["points"] =
{
[1] =
{
["alt"] = 0,
["type"] = "Turning Point",
["ETA"] = 0,
["alt_type"] = "BARO",
["formation_template"] = "",
["y"] = 0,
["x"] = 0,
["ETA_locked"] = true,
["speed"] = 0,
["action"] = "Off Road",
["task"] =
{
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["speed_locked"] = true,
}, -- end of [1]
}, -- end of ["points"]
}, -- end of ["route"]
["groupId"] = nil,
["hidden"] = false,
["units"] =
{
[1] =
{
["transportable"] =
{
["randomTransportable"] = false,
}, -- end of ["transportable"]
["skill"] = "Average",
["type"] = "Infantry AK",
["unitId"] = nil,
["y"] = 0,
["x"] = 0,
["name"] = "Infantry AK-47 Rus",
["heading"] = 0,
["playerCanDrive"] = false,
}, -- end of [1]
}, -- end of ["units"]
["y"] = 0,
["x"] = 0,
["name"] = "Infantry AK-47 Rus",
["start_time"] = 0,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Generic Ship Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TEMPLATE.GenericNaval=
{
["visible"] = false,
["tasks"] = {}, -- end of ["tasks"]
["uncontrollable"] = false,
["route"] =
{
["points"] =
{
[1] =
{
["alt"] = 0,
["type"] = "Turning Point",
["ETA"] = 0,
["alt_type"] = "BARO",
["formation_template"] = "",
["y"] = 0,
["x"] = 0,
["ETA_locked"] = true,
["speed"] = 0,
["action"] = "Turning Point",
["task"] =
{
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["speed_locked"] = true,
}, -- end of [1]
}, -- end of ["points"]
}, -- end of ["route"]
["groupId"] = nil,
["hidden"] = false,
["units"] =
{
[1] =
{
["transportable"] =
{
["randomTransportable"] = false,
}, -- end of ["transportable"]
["skill"] = "Average",
["type"] = "TICONDEROG",
["unitId"] = nil,
["y"] = 0,
["x"] = 0,
["name"] = "Naval-1-1",
["heading"] = 0,
["modulation"] = 0,
["frequency"] = 127500000,
}, -- end of [1]
}, -- end of ["units"]
["y"] = 0,
["x"] = 0,
["name"] = "Naval-1",
["start_time"] = 0,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Generic Aircraft Template
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TEMPLATE.GenericAircraft=
{
["groupId"] = nil,
["name"] = "Rotary-1",
["uncontrolled"] = false,
["hidden"] = false,
["task"] = "Nothing",
["y"] = 0,
["x"] = 0,
["start_time"] = 0,
["communication"] = true,
["radioSet"] = false,
["frequency"] = 127.5,
["modulation"] = 0,
["taskSelected"] = true,
["tasks"] = {}, -- end of ["tasks"]
["route"] =
{
["points"] =
{
[1] =
{
["y"] = 0,
["x"] = 0,
["alt"] = 1000,
["alt_type"] = "BARO",
["action"] = "Turning Point",
["type"] = "Turning Point",
["airdromeId"] = nil,
["task"] =
{
["id"] = "ComboTask",
["params"] =
{
["tasks"] = {}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["ETA"] = 0,
["ETA_locked"] = true,
["speed"] = 100,
["speed_locked"] = true,
["formation_template"] = "",
}, -- end of [1]
}, -- end of ["points"]
}, -- end of ["route"]
["units"] =
{
[1] =
{
["name"] = "Rotary-1-1",
["unitId"] = nil,
["type"] = "AH-1W",
["onboard_num"] = "050",
["livery_id"] = "USA X Black",
["skill"] = "High",
["ropeLength"] = 15,
["speed"] = 0,
["x"] = 0,
["y"] = 0,
["alt"] = 10,
["alt_type"] = "BARO",
["heading"] = 0,
["psi"] = 0,
["parking"] = nil,
["parking_id"] = nil,
["payload"] =
{
["pylons"] = {}, -- end of ["pylons"]
["fuel"] = "1250.0",
["flare"] = 30,
["chaff"] = 30,
["gun"] = 100,
}, -- end of ["payload"]
["callsign"] =
{
[1] = 2,
[2] = 1,
[3] = 1,
["name"] = "Springfield11",
}, -- end of ["callsign"]
}, -- end of [1]
}, -- end of ["units"]
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
-- @field #table CategoryName Names of airbase categories.
-- @field #string AirbaseName Name of the airbase.
-- @field #number AirbaseID Airbase ID.
-- @field Core.Zone#ZONE AirbaseZone Circular zone around the airbase with a radius of 2500 meters. For ships this is a ZONE_UNIT object.
-- @field #number category Airbase category.
-- @field #table descriptors DCS descriptors.
-- @field #boolean isAirdrome Airbase is an airdrome.
@@ -73,7 +74,7 @@ AIRBASE = {
--- Enumeration to identify the airbases in the Caucasus region.
--
-- These are all airbases of Caucasus:
-- Airbases of the Caucasus map:
--
-- * AIRBASE.Caucasus.Gelendzhik
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
@@ -122,7 +123,7 @@ AIRBASE.Caucasus = {
["Beslan"] = "Beslan",
}
--- These are all airbases of Nevada:
--- Airbases of the Nevada map:
--
-- * AIRBASE.Nevada.Creech_AFB
-- * AIRBASE.Nevada.Groom_Lake_AFB
@@ -141,6 +142,7 @@ AIRBASE.Caucasus = {
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
-- * AIRBASE.Nevada.Tonopah_Airport
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
--
-- @field Nevada
AIRBASE.Nevada = {
["Creech_AFB"] = "Creech AFB",
@@ -162,7 +164,7 @@ AIRBASE.Nevada = {
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield",
}
--- These are all airbases of Normandy:
--- Airbases of the Normandy map:
--
-- * AIRBASE.Normandy.Saint_Pierre_du_Mont
-- * AIRBASE.Normandy.Lignerolles
@@ -195,6 +197,7 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Funtington
-- * AIRBASE.Normandy.Tangmere
-- * AIRBASE.Normandy.Ford_AF
--
-- @field Normandy
AIRBASE.Normandy = {
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
@@ -237,7 +240,7 @@ AIRBASE.Normandy = {
["Conches"] = "Conches",
}
--- These are all airbases of the Persion Gulf Map:
--- Airbases of the Persion Gulf Map:
--
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
@@ -268,6 +271,7 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Sirri_Island
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
-- * AIRBASE.PersianGulf.Tunb_Kochak
--
-- @field PersianGulf
AIRBASE.PersianGulf = {
["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl",
@@ -301,7 +305,7 @@ AIRBASE.PersianGulf = {
["Tunb_Kochak"] = "Tunb Kochak",
}
--- These are all airbases of the The Channel Map:
--- Airbases of The Channel Map:
--
-- * AIRBASE.TheChannel.Abbeville_Drucat
-- * AIRBASE.TheChannel.Merville_Calonne
@@ -326,7 +330,7 @@ AIRBASE.TheChannel = {
["High_Halden"] = "High Halden",
}
--- Airbases of Syria
--- Airbases of the Syria map:
--
-- * AIRBASE.Syria.Kuweires
-- * AIRBASE.Syria.Marj_Ruhayyil
@@ -336,33 +340,53 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Incirlik
-- * AIRBASE.Syria.Damascus
-- * AIRBASE.Syria.Bassel_Al_Assad
-- * AIRBASE.Syria.Rosh_Pina
-- * AIRBASE.Syria.Aleppo
-- * AIRBASE.Syria.Qabr_as_Sitt
-- * AIRBASE.Syria.Al_Qusayr
-- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Al_Dumayr
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Ru_Convoy_4
-- * AIRBASE.Syria.Hatay
-- * AIRBASE.Syria.Nicosia
-- * AIRBASE.Syria.Pinarbashi
-- * AIRBASE.Syria.Paphos
-- * AIRBASE.Syria.Kingsfield
-- * AIRBASE.Syria.Thalah
-- * AIRBASE.Syria.Haifa
-- * AIRBASE.Syria.Khalkhalah
-- * AIRBASE.Syria.Megiddo
-- * AIRBASE.Syria.Lakatamia
-- * AIRBASE.Syria.Rayak
-- * AIRBASE.Syria.Larnaca
-- * AIRBASE.Syria.Mezzeh
-- * AIRBASE.Syria.King_Hussein_Air_College
-- * AIRBASE.Syria.Jirah
-- * AIRBASE.Syria.Gecitkale
-- * AIRBASE.Syria.Akrotiri
-- * AIRBASE.Syria.Naqoura
-- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.CVN_71
-- * AIRBASE.Syria.Sayqal
-- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Shayrat
-- * AIRBASE.Syria.Taftanaz
-- * AIRBASE.Syria.H4
-- * AIRBASE.Syria.King_Hussein_Air_College
-- * AIRBASE.Syria.Rene_Mouawad
-- * AIRBASE.Syria.Jirah
-- * AIRBASE.Syria.Ramat_David
-- * AIRBASE.Syria.Qabr_as_Sitt
-- * AIRBASE.Syria.Minakh
-- * AIRBASE.Syria.Adana_Sakirpasa
-- * AIRBASE.Syria.Marj_as_Sultan_South
-- * AIRBASE.Syria.Hama
-- * AIRBASE.Syria.Al_Qusayr
-- * AIRBASE.Syria.Palmyra
-- * AIRBASE.Syria.Hama
-- * AIRBASE.Syria.Ercan
-- * AIRBASE.Syria.Marj_as_Sultan_South
-- * AIRBASE.Syria.Tabqa
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
-- * AIRBASE.Syria.An_Nasiriyah
-- * AIRBASE.Syria.Abu_al_Duhur
--
-- @field Syria
--@field Syria
AIRBASE.Syria={
["Kuweires"]="Kuweires",
["Marj_Ruhayyil"]="Marj Ruhayyil",
@@ -372,27 +396,46 @@ AIRBASE.Syria={
["Incirlik"]="Incirlik",
["Damascus"]="Damascus",
["Bassel_Al_Assad"]="Bassel Al-Assad",
["Rosh_Pina"]="Rosh Pina",
["Aleppo"]="Aleppo",
["Qabr_as_Sitt"]="Qabr as Sitt",
["Al_Qusayr"]="Al Qusayr",
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Al_Dumayr"]="Al-Dumayr",
["Gazipasa"]="Gazipasa",
["Ru_Convoy_4"]="Ru Convoy-4",
["Hatay"]="Hatay",
["Nicosia"]="Nicosia",
["Pinarbashi"]="Pinarbashi",
["Paphos"]="Paphos",
["Kingsfield"]="Kingsfield",
["Thalah"]="Tha'lah",
["Haifa"]="Haifa",
["Khalkhalah"]="Khalkhalah",
["Megiddo"]="Megiddo",
["Lakatamia"]="Lakatamia",
["Rayak"]="Rayak",
["Larnaca"]="Larnaca",
["Mezzeh"]="Mezzeh",
["King_Hussein_Air_College"]="King Hussein Air College",
["Jirah"]="Jirah",
["Gecitkale"]="Gecitkale",
["Akrotiri"]="Akrotiri",
["Naqoura"]="Naqoura",
["Gaziantep"]="Gaziantep",
["Sayqal"]="Sayqal",
["Tiyas"]="Tiyas",
["Shayrat"]="Shayrat",
["Taftanaz"]="Taftanaz",
["H4"]="H4",
["King_Hussein_Air_College"]="King Hussein Air College",
["Rene_Mouawad"]="Rene Mouawad",
["Jirah"]="Jirah",
["Ramat_David"]="Ramat David",
["Qabr_as_Sitt"]="Qabr as Sitt",
["Minakh"]="Minakh",
["Adana_Sakirpasa"]="Adana Sakirpasa",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Hama"]="Hama",
["Al_Qusayr"]="Al Qusayr",
["Palmyra"]="Palmyra",
["Hama"]="Hama",
["Ercan"]="Ercan",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Tabqa"]="Tabqa",
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
["An_Nasiriyah"]="An Nasiriyah",
@@ -400,6 +443,27 @@ AIRBASE.Syria={
}
--- Airbases of the Mariana Islands map:
--
-- * AIRBASE.MarianaIslands.Rota_Intl
-- * AIRBASE.MarianaIslands.Andersen_AFB
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
-- * AIRBASE.MarianaIslands.Saipan_Intl
-- * AIRBASE.MarianaIslands.Tinian_Intl
-- * AIRBASE.MarianaIslands.Olf_Orote
--
--@field MarianaIslands
AIRBASE.MarianaIslands={
["Rota_Intl"]="Rota Intl",
["Andersen_AFB"]="Andersen AFB",
["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl",
["Saipan_Intl"]="Saipan Intl",
["Tinian_Intl"]="Tinian Intl",
["Olf_Orote"]="Olf Orote",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
-- @type AIRBASE.ParkingSpot
-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot.
@@ -463,19 +527,19 @@ function AIRBASE:Register(AirbaseName)
-- Inherit everything from positionable.
local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE
-- Set airbase name.
self.AirbaseName=AirbaseName
-- Set airbase ID.
self.AirbaseID=self:GetID(true)
-- Get descriptors.
self.descriptors=self:GetDesc()
-- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
-- Set category.
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
@@ -483,20 +547,33 @@ function AIRBASE:Register(AirbaseName)
self.isHelipad=true
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else
self:E("ERROR: Unknown airbase category!")
end
self:_InitParkingSpots()
local vec2=self:GetVec2()
-- Init coordinate.
self:GetCoordinate()
if vec2 then
-- TODO: For ships we need a moving zone.
self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 )
if self.isShip then
local unit=UNIT:FindByName(AirbaseName)
if unit then
self.AirbaseZone=ZONE_UNIT:New(AirbaseName, unit, 2500)
end
else
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500)
end
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
end
@@ -592,7 +669,7 @@ end
--- Get all airbase names of the current map. This includes ships and FARPS.
-- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned.
-- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP
-- @param #number category (Optional) Return only airbases of a certain category, e.g. `Airbase.Category.HELIPAD`.
-- @return #table Table containing all airbase names of the current map.
function AIRBASE.GetAllAirbaseNames(coalition, category)
@@ -630,7 +707,7 @@ function AIRBASE:GetID(unique)
local airbaseID=tonumber(DCSAirbase:getID())
local airbaseCategory=self:GetAirbaseCategory()
if AirbaseName==self.AirbaseName then
if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then
-- Ships get a negative sign as their unit number might be the same as the ID of another airbase.
@@ -869,16 +946,16 @@ function AIRBASE:_InitParkingSpots()
-- Init table.
self.parking={}
self.parkingByID={}
self.NparkingTotal=0
self.NparkingTerminal={}
for _,terminalType in pairs(AIRBASE.TerminalType) do
self.NparkingTerminal[terminalType]=0
end
end
-- Put coordinates of parking spots into table.
for _,spot in pairs(parkingdata) do
-- New parking spot.
local park={} --#AIRBASE.ParkingSpot
park.Vec3=spot.vTerminalPos
@@ -889,15 +966,15 @@ function AIRBASE:_InitParkingSpots()
park.TerminalID0=spot.Term_Index_0
park.TerminalType=spot.Term_Type
park.TOAC=spot.TO_AC
self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType) do
if self._CheckTerminalType(terminalType, park.TerminalType) then
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end
end
end
self.parkingByID[park.TerminalID]=park
table.insert(self.parking, park)
end
@@ -921,7 +998,7 @@ function AIRBASE:GetParkingSpotsTable(termtype)
-- Get parking data of all spots (free or occupied)
local parkingdata=self:GetParkingData(false)
-- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true)
@@ -938,17 +1015,26 @@ function AIRBASE:GetParkingSpotsTable(termtype)
-- Put coordinates of parking spots into table.
local spots={}
for _,_spot in pairs(parkingdata) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=_isfree(_spot) -- updated
spot.TOAC=_spot.TO_AC -- updated
table.insert(spots, spot)
if spot then
spot.Free=_isfree(_spot) -- updated
spot.TOAC=_spot.TO_AC -- updated
table.insert(spots, spot)
else
self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index)))
end
end
end
return spots
@@ -969,14 +1055,14 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
for _,_spot in pairs(parkingfree) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then
if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=true -- updated
spot.TOAC=_spot.TO_AC -- updated
table.insert(freespots, spot)
end
end
end
@@ -1021,7 +1107,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
-- Get airbase name.
local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype)))
self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype)))
for _,_spot in pairs(parkingdata) do
@@ -1055,7 +1141,7 @@ end
-- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function.
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata)
-- Init default
scanradius=scanradius or 50
if scanunits==nil then
@@ -1098,14 +1184,25 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
-- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft=group:GetUnit(1)
local _aircraftsize, ax,ay,az=aircraft:GetObjectSize()
local aircraft = nil -- fix local problem below
local _aircraftsize, ax,ay,az
if group and group.ClassName == "GROUP" then
aircraft=group:GetUnit(1)
_aircraftsize, ax,ay,az=aircraft:GetObjectSize()
else
-- SU27 dimensions
_aircraftsize = 23
ax = 23 -- length
ay = 7 -- height
az = 17 -- width
end
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
local _nspots=nspots or group:GetSize()
-- Debug info.
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
-- Table of valid spots.
local validspots={}
@@ -1228,6 +1325,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Retrun spots we found, even if there were not enough.
return validspots
end
--- Check black and white lists.
@@ -1329,7 +1427,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Get spawn points on runway. These can be used to determine the runway heading.
local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
-- Debug: For finding the numbers of the spawn points belonging to each runway.
if false then
for i,_coord in pairs(runwaycoords) do
@@ -1348,7 +1446,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Airbase name.
local name=self:GetName()
-- Exceptions
if name==AIRBASE.Nevada.Jean_Airport or
@@ -1356,40 +1454,41 @@ function AIRBASE:GetRunwayData(magvar, mark)
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport then
name==AIRBASE.PersianGulf.Kish_International_Airport or
name==AIRBASE.MarianaIslands.Andersen_AFB then
-- 1-->4, 2-->3, 3-->2, 4-->1
exception=1
elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and
elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and
name~=AIRBASE.Syria.Minakh and
name~=AIRBASE.Syria.Damascus and
name~=AIRBASE.Syria.Khalkhalah and
name~=AIRBASE.Syria.Marj_Ruhayyil and
name~=AIRBASE.Syria.Beirut_Rafic_Hariri then
-- 1-->3, 2-->4, 3-->1, 4-->2
exception=2
end
--- Function returning the index of the runway coordinate belonding to the given index i.
local function f(i)
local j
if exception==1 then
j=N-(i-1) -- 1-->4, 2-->3
elseif exception==2 then
if i<=N2 then
j=i+N2 -- 1-->3, 2-->4
else
j=i-N2 -- 3-->1, 4-->3
end
else
if i%2==0 then
@@ -1397,9 +1496,9 @@ function AIRBASE:GetRunwayData(magvar, mark)
else
j=i+1 -- odd 1-->2, 3-->4
end
end
-- Special case where there is no obvious order.
if name==AIRBASE.Syria.Beirut_Rafic_Hariri then
if i==1 then
@@ -1432,7 +1531,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
j=2
end
end
return j
end
@@ -1441,7 +1540,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Get the other spawn point coordinate.
local j=f(i)
-- Debug info.
--env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords))

View File

@@ -175,11 +175,8 @@
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
--
-- ## 5.5) Air-2-Air missile attack range:
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets.
--
-- ## 5.6) GROUND units attack range:
-- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically.
--
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets .
--
-- @field #CONTROLLABLE
CONTROLLABLE = {
ClassName = "CONTROLLABLE",
@@ -507,7 +504,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks )
tasks = DCSTasks
}
}
return DCSTaskCombo
end
@@ -805,12 +802,12 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay)
local CommandSetFrequency = {
id = 'SetFrequency',
params = {
frequency = Frequency*1000000,
modulation = Modulation or radio.modulation.AM,
}
local CommandSetFrequency = {
id = 'SetFrequency',
params = {
frequency = Frequency*1000000,
modulation = Modulation or radio.modulation.AM,
}
}
if Delay and Delay>0 then
@@ -884,7 +881,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At
groupId = AttackGroup:GetID(),
weaponType = WeaponType or 1073741822,
expend = WeaponExpend or "Auto",
attackQtyLimit = AttackQty and true or false,
attackQtyLimit = AttackQty and true or false,
attackQty = AttackQty or 1,
directionEnabled = Direction and true or false,
direction = Direction and math.rad(Direction) or 0,
@@ -924,7 +921,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta
weaponType = WeaponType or 1073741822,
}
}
return DCSTask
end
@@ -1088,7 +1085,7 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration,
-- Distribution
--local distribution={}
--distribution[id]=gids
local groupID=self and self:GetID()
local DCSTask = {
@@ -1317,7 +1314,7 @@ function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration)
duration = Duration,
},
}
return DCSTask
end
@@ -1431,16 +1428,22 @@ end
-- @param DCS#Distance Radius The radius of the zone to deploy the fire at.
-- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted).
-- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag
-- @param #number Altitude (Optional) Altitude in meters.
-- @param #number ASL Altitude is above mean sea level. Default is above ground level.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType )
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL )
local DCSTask = {
id = 'FireAtPoint',
params = {
point = Vec2,
x=Vec2.x,
y=Vec2.y,
zoneRadius = Radius,
radius = Radius,
expendQty = 100, -- dummy value
expendQtyEnabled = false,
alt_type = ASL and 0 or 1
}
}
@@ -1449,10 +1452,16 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType )
DCSTask.params.expendQtyEnabled = true
end
if Altitude then
DCSTask.params.altitude=Altitude
end
if WeaponType then
DCSTask.params.weaponType=WeaponType
end
--self:I(DCSTask)
return DCSTask
end
@@ -1470,6 +1479,7 @@ end
--- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction.
-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC.
-- If the task is assigned to the controllable lead unit will be a FAC.
-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators.
-- @param #CONTROLLABLE self
-- @param Wrapper.Group#GROUP AttackGroup Target GROUP object.
-- @param #number WeaponType Bitmask of weapon types, which are allowed to use.
@@ -1477,7 +1487,7 @@ end
-- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default.
-- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz.
-- @param #number Modulation Modulation of radio for communication. Default 0=AM.
-- @param #number CallsignName Callsign enumerator name of the FAC.
-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units)
-- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber )
@@ -1799,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... )
-- DCS task.
local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript )))
return DCSTask
end
@@ -1841,8 +1851,27 @@ do -- Patrol methods
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
local From = FromCoord:WaypointGround( 120 )
-- test for submarine
local depth = 0
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
end
local Waypoint = Waypoints[1]
local Speed = Waypoint.speed or (20 / 3.6)
local From = FromCoord:WaypointGround( Speed )
if IsSub then
From = FromCoord:WaypointNaval( Speed, Waypoint.alt )
end
table.insert( Waypoints, 1, From )
local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" )
@@ -1882,7 +1911,16 @@ do -- Patrol methods
if ToWaypoint then
FromWaypoint = ToWaypoint
end
-- test for submarine
local depth = 0
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
end
-- Loop until a waypoint has been found that is not the same as the current waypoint.
-- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly
-- what it is supposed to do, which is making groups drive around.
@@ -1897,9 +1935,13 @@ do -- Patrol methods
local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
if IsSub then
Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth )
Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt )
else
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
end
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint )
@@ -1940,9 +1982,20 @@ do -- Patrol methods
self:F( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
-- test for submarine
local depth = 0
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
end
-- Select a random Zone and get the Coordinate of the new Zone.
local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
@@ -1950,9 +2003,13 @@ do -- Patrol methods
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
if IsSub then
Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth )
Route[#Route+1] = ToCoord:WaypointNaval( Speed, depth )
else
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
end
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax )
@@ -1976,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points )
id = 'Mission',
params = {
airborne = self:IsAir(),
route = {points = Points},
route = {points = Points},
},
}
@@ -2898,13 +2955,13 @@ end
--- Set option for Rules of Engagement (ROE).
-- @param Wrapper.Controllable#CONTROLLABLE self
-- @param #number ROEvalue ROE value. See ENUMS.ROE.
-- @return Wrapper.Controllable#CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionROE(ROEvalue)
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
@@ -2940,8 +2997,8 @@ function CONTROLLABLE:OptionROEHoldFirePossible()
end
--- Weapons Hold: AI will hold fire under all circumstances.
-- @param Wrapper.Controllable#CONTROLLABLE self
-- @return Wrapper.Controllable#CONTROLLABLE self
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionROEHoldFire()
self:F2( { self.ControllableName } )
@@ -3468,13 +3525,13 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionProhibitAfterburner(Prohibit)
self:F2( { self.ControllableName } )
if Prohibit==nil then
Prohibit=true
end
if self:IsAir() then
self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit)
self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit)
end
return self
@@ -3485,9 +3542,9 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionECM_Never()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption(AI.Option.Air.id.ECM_USING, 0)
self:SetOption(AI.Option.Air.id.ECM_USING, 0)
end
return self
@@ -3498,9 +3555,9 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionECM_OnlyLockByRadar()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption(AI.Option.Air.id.ECM_USING, 1)
self:SetOption(AI.Option.Air.id.ECM_USING, 1)
end
return self
@@ -3512,9 +3569,9 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionECM_DetectedLockByRadar()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption(AI.Option.Air.id.ECM_USING, 2)
self:SetOption(AI.Option.Air.id.ECM_USING, 2)
end
return self
@@ -3525,9 +3582,9 @@ end
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionECM_AlwaysOn()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption(AI.Option.Air.id.ECM_USING, 3)
self:SetOption(AI.Option.Air.id.ECM_USING, 3)
end
return self
@@ -3539,7 +3596,7 @@ end
-- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED!
-- @param #CONTROLLABLE self
-- @param #table WayPoints If WayPoints is given, then use the route.
-- @return #CONTROLLABLE
-- @return #CONTROLLABLE self
function CONTROLLABLE:WayPointInitialize( WayPoints )
self:F( { WayPoints } )
@@ -3570,7 +3627,7 @@ end
-- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1!
-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions.
-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters.
-- @return #CONTROLLABLE
-- @return #CONTROLLABLE self
function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
self:F2( { WayPoint, WayPointIndex, WayPointFunction } )
@@ -3586,7 +3643,7 @@ end
-- @param #CONTROLLABLE self
-- @param #number WayPoint The WayPoint from where to execute the mission.
-- @param #number WaitTime The amount seconds to wait before initiating the mission.
-- @return #CONTROLLABLE
-- @return #CONTROLLABLE self
function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime )
self:F( { WayPoint, WaitTime } )
@@ -3669,21 +3726,22 @@ end
--- Sets Controllable Option for A2A attack range for AIR FIGHTER units.
-- @param #CONTROLLABLE self
-- @param #number range Defines the range
-- @param #number range Defines the range
-- @return #CONTROLLABLE self
-- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack
function CONTROLLABLE:OptionAAAttackRange(range)
self:F2( { self.ControllableName } )
self:F2( { self.ControllableName } )
-- defaults to 3
local range = range or 3
if range < 0 or range > 4 then
range = 3
end
end
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
if Controller then
if self:IsAir() then
self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range)
self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range)
end
end
return self
@@ -3696,7 +3754,7 @@ end
-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100.
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionEngageRange(EngageRange)
self:F2( { self.ControllableName } )
self:F2( { self.ControllableName } )
-- Set default if not specified.
EngageRange=EngageRange or 100
if EngageRange < 0 or EngageRange > 100 then
@@ -3705,9 +3763,9 @@ function CONTROLLABLE:OptionEngageRange(EngageRange)
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
if Controller then
if self:IsGround() then
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange)
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange)
end
end
return self
@@ -3721,10 +3779,12 @@ end
-- @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
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut)
self:F2( { self.ControllableName } )
-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road"
-- @return #CONTROLLABLE self
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation)
self:F2( { self.ControllableName } )
local _coord = self:GetCoordinate()
local _coord = self:GetCoordinate()
local _radius = radius or 500
local _speed = speed or 20
local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100)
@@ -3732,15 +3792,56 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc
local _grptsk = {}
local _candoroad = false
local _shortcut = shortcut or false
local _formation = formation or "Off Road"
-- create a DCS Task an push it on the group
-- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
if onroad then
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut)
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut)
self:Route(_grptsk,5)
else
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road")
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation)
end
return self
return self
end
--- Defines how long a GROUND unit/group will move to avoid an ongoing attack.
-- @param #CONTROLLABLE self
-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse.
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
self:F2( { self.ControllableName } )
-- Set default if not specified.
local seconds = Seconds or 0
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
if self:IsGround() then
self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds)
end
end
return self
end
return nil
end
--- Returns if the unit is a submarine.
-- @param #POSITIONABLE self
-- @return #boolean Submarines attributes result.
function POSITIONABLE:IsSubmarine()
self:F2()
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local UnitDescriptor = DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"] == true then
return true
else
return false
end
end
return nil
end

View File

@@ -310,8 +310,7 @@ end
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive.
function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3()
self:F2( self.PositionableName )
@@ -339,9 +338,7 @@ end
-- If the first @{Wrapper.Unit} of the group is inactive, it will return false.
--
-- @param #GROUP self
-- @return #boolean true if the group is alive and active.
-- @return #boolean false if the group is alive but inactive.
-- @return #nil if the group does not exist anymore.
-- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore.
function GROUP:IsAlive()
self:F2( self.GroupName )
@@ -363,8 +360,7 @@ end
--- Returns if the group is activated.
-- @param #GROUP self
-- @return #boolean true if group is activated.
-- @return #nil The group is not existing or alive.
-- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive.
function GROUP:IsActive()
self:F2( self.GroupName )
@@ -412,7 +408,6 @@ function GROUP:Destroy( GenerateEvent, delay )
self:F2( self.GroupName )
if delay and delay>0 then
--SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay)
self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent)
else
@@ -568,12 +563,12 @@ function GROUP:GetSpeedMax()
local Units=self:GetUnits()
local speedmax=nil
local speedmax=0
for _,unit in pairs(Units) do
local unit=unit --Wrapper.Unit#UNIT
local speed=unit:GetSpeedMax()
if speedmax==nil then
if speedmax==0 then
speedmax=speed
elseif speed<speedmax then
speedmax=speed
@@ -1137,7 +1132,7 @@ end
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
-- @return #number Number of missiles left.
function GROUP:GetAmmunition()
self:F( self.ControllableName )
@@ -1147,6 +1142,7 @@ function GROUP:GetAmmunition()
local Nshells=0
local Nrockets=0
local Nmissiles=0
local Nbombs=0
if DCSControllable then
@@ -1155,18 +1151,19 @@ function GROUP:GetAmmunition()
local Unit = UnitData -- Wrapper.Unit#UNIT
-- Get ammo of the unit
local ntot, nshells, nrockets, nmissiles = Unit:GetAmmunition()
local ntot, nshells, nrockets, nbombs, nmissiles = Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
end
end
return Ntot, Nshells, Nrockets, Nmissiles
return Ntot, Nshells, Nrockets, Nbombs, Nmissiles
end
@@ -2324,41 +2321,44 @@ function GROUP:GetAttribute()
local unarmedship=self:HasAttribute("Unarmed ships")
-- Define attribute. Order is important.
if transportplane then
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
elseif awacs then
attribute=GROUP.Attribute.AIR_AWACS
elseif fighter then
-- Define attribute. Order of attack is important.
if fighter then
attribute=GROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=GROUP.Attribute.AIR_BOMBER
elseif awacs then
attribute=GROUP.Attribute.AIR_AWACS
elseif transportplane then
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
elseif tanker then
attribute=GROUP.Attribute.AIR_TANKER
-- helos
elseif attackhelicopter then
attribute=GROUP.Attribute.AIR_ATTACKHELO
elseif transporthelo then
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
elseif attackhelicopter then
attribute=GROUP.Attribute.AIR_ATTACKHELO
elseif uav then
attribute=GROUP.Attribute.AIR_UAV
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
-- ground - order of attack
elseif ewr then
attribute=GROUP.Attribute.GROUND_EWR
elseif sam then
attribute=GROUP.Attribute.GROUND_SAM
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then
attribute=GROUP.Attribute.GROUND_TRAIN
-- ships
elseif aircraftcarrier then
attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
@@ -2551,6 +2551,64 @@ do -- Players
end
--- GROUND - Switch on/off radar emissions for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:EnableEmission(switch)
self:F2( self.GroupName )
local switch = switch or false
local DCSUnit = self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
--- Switch on/off invisible flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:SetCommandInvisible(switch)
self:F2( self.GroupName )
if switch==nil then
switch=false
end
local SetInvisible = {id = 'SetInvisible', params = {value = switch}}
self:SetCommand(SetInvisible)
return self
end
--- Switch on/off immortal flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #GROUP self
function GROUP:SetCommandImmortal(switch)
self:F2( self.GroupName )
if switch==nil then
switch=false
end
local SetImmortal = {id = 'SetImmortal', params = {value = switch}}
self:SetCommand(SetImmortal)
return self
end
--- Get skill from Group. Effectively gets the skill from Unit 1 as the group holds no skill value.
-- @param #GROUP self
-- @return #string Skill String of skill name.
function GROUP:GetSkill()
self:F2( self.GroupName )
local unit = self:GetUnit(1)
local name = unit:GetName()
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end
--do -- Smoke
--
----- Signal a flare at the position of the GROUP.
@@ -2641,4 +2699,4 @@ end
--
--
--
--end
--end

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,46 @@
--- **Wrapper** -- STATIC wraps the DCS StaticObject class.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
--
--
-- ### Contributions: **funkyfranky**
--
--
-- ===
--
--
-- @module Wrapper.Static
-- @image Wrapper_Static.JPG
--- @type STATIC
-- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle Static objects.
--
--
-- Note that Statics are almost the same as Units, but they don't have a controller.
-- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects:
--
--
-- * Wraps the DCS Static objects.
-- * Support all DCS Static APIs.
-- * Enhance with Static specific APIs not in the DCS API set.
--
--
-- ## STATIC reference methods
--
--
-- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object.
-- This is done at the beginning of the mission (when the mission starts).
--
--
-- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference
-- using the Static Name.
--
--
-- Another thing to know is that STATIC objects do not "contain" the DCS Static object.
-- The STATIc methods will reference the DCS Static object by name when it is needed during API execution.
-- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file.
--
--
-- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance:
--
--
-- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name.
--
-- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil).
--
--
-- IMPORTANT: ONE SHOULD NEVER SANITIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil).
--
-- @field #STATIC
STATIC = {
ClassName = "STATIC",
}
STATIC = { ClassName = "STATIC" }
--- Register a static object.
-- @param #STATIC self
@@ -58,7 +52,6 @@ function STATIC:Register( StaticName )
return self
end
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
-- @param #STATIC self
-- @param DCS#StaticObject DCSStatic An existing DCS Static object reference.
@@ -83,13 +76,13 @@ function STATIC:FindByName( StaticName, RaiseError )
-- Set static name.
self.StaticName = StaticName
if StaticFound then
return StaticFound
return StaticFound
end
if RaiseError == nil or RaiseError == true then
error( "STATIC not found for: " .. StaticName )
if RaiseError == nil or RaiseError == true then
error( "STATIC not found for: " .. StaticName )
end
return nil
@@ -97,38 +90,39 @@ end
--- Destroys the STATIC.
-- @param #STATIC self
-- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the static.
-- @return #nil The DCS StaticObject is not existing or alive.
-- @param #boolean GenerateEvent (Optional) true to generate a crash or dead event, false to not generate any event. `nil` (default) creates a remove event.
-- @return #nil The DCS StaticObject is not existing or alive.
--
-- @usage
-- -- Air static example: destroy the static Helicopter and generate a S_EVENT_CRASH.
-- Helicopter = STATIC:FindByName( "Helicopter" )
-- Helicopter:Destroy( true )
--
--
-- @usage
-- -- Ground static example: destroy the static Tank and generate a S_EVENT_DEAD.
-- Tanks = UNIT:FindByName( "Tank" )
-- Tanks:Destroy( true )
--
--
-- @usage
-- -- Ship static example: destroy the Ship silently.
-- Ship = STATIC:FindByName( "Ship" )
-- Ship:Destroy()
--
--
-- @usage
-- -- Destroy without event generation example.
-- Ship = STATIC:FindByName( "Boat" )
-- Ship:Destroy( false ) -- Don't generate an event upon destruction.
--
-- Ship:Destroy( false ) -- Don't generate any event upon destruction.
--
function STATIC:Destroy( GenerateEvent )
self:F2( self.ObjectName )
local DCSObject = self:GetDCSObject()
if DCSObject then
local StaticName = DCSObject:getName()
self:F( { StaticName = StaticName } )
if GenerateEvent and GenerateEvent == true then
if self:IsAir() then
self:CreateEventCrash( timer.getTime(), DCSObject )
@@ -140,7 +134,7 @@ function STATIC:Destroy( GenerateEvent )
else
self:CreateEventRemoveUnit( timer.getTime(), DCSObject )
end
DCSObject:destroy()
return true
end
@@ -148,17 +142,16 @@ function STATIC:Destroy( GenerateEvent )
return nil
end
--- Get DCS object of static of static.
-- @param #STATIC self
-- @return DCS static object
function STATIC:GetDCSObject()
local DCSStatic = StaticObject.getByName( self.StaticName )
if DCSStatic then
return DCSStatic
end
return nil
end
@@ -170,7 +163,7 @@ function STATIC:GetUnits()
local DCSStatic = self:GetDCSObject()
local Statics = {}
if DCSStatic then
Statics[1] = STATIC:Find( DCSStatic )
self:T3( Statics )
@@ -180,7 +173,6 @@ function STATIC:GetUnits()
return nil
end
--- Get threat level of static.
-- @param #STATIC self
-- @return #number Threat level 1.
@@ -194,66 +186,62 @@ end
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
-- @param #number Heading The heading of the static respawn in degrees. Default is 0 deg.
-- @param #number Delay Delay in seconds before the static is spawned.
function STATIC:SpawnAt(Coordinate, Heading, Delay)
function STATIC:SpawnAt( Coordinate, Heading, Delay )
Heading=Heading or 0
Heading = Heading or 0
if Delay and Delay>0 then
SCHEDULER:New(nil, self.SpawnAt, {self, Coordinate, Heading}, Delay)
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.SpawnAt, { self, Coordinate, Heading }, Delay )
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName )
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
end
return self
end
--- Respawn the @{Wrapper.Unit} at the same location with the same properties.
-- This is useful to respawn a cargo after it has been destroyed.
-- @param #STATIC self
-- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently.
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now.
function STATIC:ReSpawn(CountryID, Delay)
function STATIC:ReSpawn( CountryID, Delay )
if Delay and Delay>0 then
SCHEDULER:New(nil, self.ReSpawn, {self, CountryID}, Delay)
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.ReSpawn, { self, CountryID }, Delay )
else
CountryID=CountryID or self:GetCountry()
CountryID = CountryID or self:GetCountry()
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, CountryID )
SpawnStatic:Spawn( nil, self.StaticName )
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID)
SpawnStatic:Spawn(nil, self.StaticName)
end
return self
end
--- Respawn the @{Wrapper.Unit} at a defined Coordinate with an optional heading.
-- @param #STATIC self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default the current heading.
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now.
function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default is the current heading.
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default is now.
function STATIC:ReSpawnAt( Coordinate, Heading, Delay )
--Heading=Heading or 0
-- Heading=Heading or 0
if Delay and Delay>0 then
SCHEDULER:New(nil, self.ReSpawnAt, {self, Coordinate, Heading}, Delay)
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.ReSpawnAt, { self, Coordinate, Heading }, Delay )
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry())
SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName)
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, self:GetCountry() )
SpawnStatic:SpawnFromCoordinate( Coordinate, Heading, self.StaticName )
end
return self
end

View File

@@ -63,7 +63,7 @@
--
-- The UNIT class provides methods to obtain the current point or position of the DCS Unit.
-- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively.
-- If you want to obtain the complete **3D position** including ori<EFBFBD>ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively.
-- If you want to obtain the complete **3D position** including orientation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively.
--
-- ## Test if alive
--
@@ -154,8 +154,8 @@ function UNIT:Name()
return self.UnitName
end
--- @param #UNIT self
--- Get the DCS unit object.
-- @param #UNIT self
-- @return DCS#Unit
function UNIT:GetDCSObject()
@@ -168,9 +168,6 @@ function UNIT:GetDCSObject()
return nil
end
--- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group.
--
-- This function will:
@@ -263,8 +260,6 @@ function UNIT:ReSpawnAt( Coordinate, Heading )
_DATABASE:Spawn( SpawnGroupTemplate )
end
--- Returns if the unit is activated.
-- @param #UNIT self
-- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive.
@@ -301,8 +296,6 @@ function UNIT:IsAlive()
return nil
end
--- Returns the Unit's callsign - the localized string.
-- @param #UNIT self
-- @return #string The Callsign of the Unit.
@@ -442,7 +435,7 @@ function UNIT:GetSpeedMax()
return SpeedMax*3.6
end
return nil
return 0
end
--- Returns the unit's max range in meters derived from the DCS descriptors.
@@ -527,8 +520,7 @@ end
--- Returns the unit's group if it exist and nil otherwise.
-- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit.
-- @return #nil The DCS Unit is not existing or alive.
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
function UNIT:GetGroup()
self:F2( self.UnitName )
@@ -581,6 +573,18 @@ function UNIT:GetAmmo()
return nil
end
--- Sets the Unit's Internal Cargo Mass, in kg
-- @param #UNIT self
-- @param #number mass to set cargo to
-- @return #UNIT self
function UNIT:SetUnitInternalCargo(mass)
local DCSUnit = self:GetDCSObject()
if DCSUnit then
trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass)
end
return self
end
--- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has.
-- @param #UNIT self
-- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles.
@@ -615,8 +619,8 @@ function UNIT:GetAmmunition()
-- Type name of current weapon.
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString = UTILS.Split(Tammo,"%.")
local _weaponName = _weaponString[#_weaponString]
--local _weaponString = UTILS.Split(Tammo,"%.")
--local _weaponName = _weaponString[#_weaponString]
-- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3
local Category=ammotable[w].desc.category
@@ -644,8 +648,9 @@ function UNIT:GetAmmunition()
nbombs=nbombs+Nammo
elseif Category==Weapon.Category.MISSILE then
-- Add up all cruise missiles (category 5)
-- Add up all missiles (category 5)
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
@@ -654,6 +659,10 @@ function UNIT:GetAmmunition()
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.SAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
end
end
@@ -762,6 +771,7 @@ function UNIT:GetFuel()
return nil
end
--- Returns a list of one @{Wrapper.Unit}.
-- @param #UNIT self
-- @return #list<Wrapper.Unit#UNIT> A list of one @{Wrapper.Unit}.
@@ -944,7 +954,6 @@ end
-- @return #string Some text.
function UNIT:GetThreatLevel()
local ThreatLevel = 0
local ThreatText = ""
@@ -970,7 +979,6 @@ function UNIT:GetThreatLevel()
"LR SAMs"
}
if Attributes["LR SAM"] then ThreatLevel = 10
elseif Attributes["MR SAM"] then ThreatLevel = 9
elseif Attributes["SR SAM"] and
@@ -1006,7 +1014,6 @@ function UNIT:GetThreatLevel()
"Fighter"
}
if Attributes["Fighters"] then ThreatLevel = 10
elseif Attributes["Multirole fighters"] then ThreatLevel = 9
elseif Attributes["Battleplanes"] then ThreatLevel = 8
@@ -1124,12 +1131,6 @@ function UNIT:OtherUnitInRadius( AwaitUnit, Radius )
return nil
end
--- Returns if the unit is a friendly unit.
-- @param #UNIT self
-- @return #boolean IsFriendly evaluation result.
@@ -1175,8 +1176,9 @@ end
--- Returns true if the UNIT is in the air.
-- @param #UNIT self
-- @param #boolean NoHeloCheck If true, no additonal checks for helos are performed.
-- @return #boolean Return true if in the air or #nil if the UNIT is not existing or alive.
function UNIT:InAir()
function UNIT:InAir(NoHeloCheck)
self:F2( self.UnitName )
-- Get DCS unit object.
@@ -1186,14 +1188,14 @@ function UNIT:InAir()
-- Get DCS result of whether unit is in air or not.
local UnitInAir = DCSUnit:inAir()
-- Get unit category.
local UnitCategory = DCSUnit:getDesc().category
-- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not.
-- This is a workaround since DCS currently does not acknoledge that helos land on buildings.
-- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier!
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then
local VelocityVec3 = DCSUnit:getVelocity()
local Velocity = UTILS.VecNorm(VelocityVec3)
local Coordinate = DCSUnit:getPoint()
@@ -1393,3 +1395,33 @@ function UNIT:GetTemplateFuel()
return nil
end
--- GROUND - Switch on/off radar emissions of a unit.
-- @param #UNIT self
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #UNIT self
function UNIT:EnableEmission(switch)
self:F2( self.UnitName )
local switch = switch or false
local DCSUnit = self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
--- Get skill from Unit.
-- @param #UNIT self
-- @return #string Skill String of skill name.
function UNIT:GetSkill()
self:F2( self.UnitName )
local name = self.UnitName
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end

View File

@@ -3,10 +3,12 @@ Utilities/Routines.lua
Utilities/Utils.lua
Utilities/Enums.lua
Utilities/Profiler.lua
Utilities/Templates.lua
Utilities/STTS.lua
Core/Base.lua
Core/Beacon.lua
Core/UserFlag.lua
Core/UserSound.lua
Core/Report.lua
Core/Scheduler.lua
Core/ScheduleDispatcher.lua
@@ -21,9 +23,6 @@ Core/Point.lua
Core/Velocity.lua
Core/Message.lua
Core/Fsm.lua
Core/Radio.lua
Core/RadioQueue.lua
Core/RadioSpeech.lua
Core/Spawn.lua
Core/SpawnStatic.lua
Core/Timer.lua
@@ -68,11 +67,15 @@ Functional/Suppression.lua
Functional/PseudoATC.lua
Functional/Warehouse.lua
Functional/Fox.lua
Functional/Mantis.lua
Functional/Shorad.lua
Ops/Airboss.lua
Ops/RecoveryTanker.lua
Ops/RescueHelo.lua
Ops/ATIS.lua
Ops/CTLD.lua
Ops/CSAR.lua
AI/AI_Balancer.lua
AI/AI_Air.lua
@@ -111,6 +114,13 @@ Actions/Act_Route.lua
Actions/Act_Account.lua
Actions/Act_Assist.lua
Sound/UserSound.lua
Sound/SoundOutput.lua
Sound/Radio.lua
Sound/RadioQueue.lua
Sound/RadioSpeech.lua
Sound/SRS.lua
Tasking/CommandCenter.lua
Tasking/Mission.lua
Tasking/Task.lua

View File

@@ -1,3 +1,5 @@
[![Build status](https://ci.appveyor.com/api/projects/status/1y8nfmx7lwsn33tt?svg=true)](https://ci.appveyor.com/project/Applevangelist/MOOSE)
# MOOSE framework
MOOSE is a **M**ission **O**bject **O**riented **S**cripting **E**nvironment, and is meant for mission designers in DCS World.
@@ -28,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework.
### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated
This repository contains the Moose.lua file to be included within your missions.
This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission.
### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use
@@ -50,9 +52,7 @@ This repository contains all the demonstration missions in packed format (*.miz)
This repository contains all the demonstration missions in unpacked format. That means that there is no .miz file included, but all the .miz contents are unpacked.
## [MOOSE Web Site](https://flightcontrol-master.github.io/MOOSE_DOCS/)
Documentation on the MOOSE class hierarchy, usage guides and background information can be found here for normal users, beta testers and contributors.
@@ -74,6 +74,6 @@ MOOSE has a living (chat and video) community of users, beta testers and contrib
Kind regards,
FlightControl (FC)
The Moose Team