Compare commits

..

147 Commits

Author SHA1 Message Date
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
ebb4623bb5 Merge branch 'master' into FF/MasterDevel 2021-05-07 17:42:07 +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
Frank
2bce305451 Merge branch 'master' into FF/MasterDevel 2021-05-01 17:07:56 +02:00
Frank
2e66a854b1 Events and Templates 2021-05-01 00:55:43 +02:00
46 changed files with 13618 additions and 2813 deletions

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

@@ -64,20 +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( "*", "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
@@ -207,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
@@ -255,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
@@ -271,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() < 15 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
@@ -285,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() < 15 then
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
self:Unload( self.DeployZone )
self.RouteDeploy = false
end

View File

@@ -0,0 +1,442 @@
--- **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 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

@@ -136,7 +136,7 @@ function DATABASE:New()
self:_RegisterGroupsAndUnits()
self:_RegisterClients()
self:_RegisterStatics()
self:_RegisterAirbases()
--self:_RegisterAirbases()
--self:_RegisterPlayers()
self.UNITS_Position = 0
@@ -298,24 +298,78 @@ do -- Zones
-- @return #DATABASE self
function DATABASE:_RegisterZones()
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
local ZoneName = ZoneData.name
-- Color
local color=ZoneData.color or {1, 0, 0, 0.15}
-- Create new Zone
local Zone=nil --Core.Zone#ZONE_BASE
if ZoneData.type==0 then
---
-- Circular zone
---
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
Zone=ZONE:New(ZoneName)
else
self:I( { "Register ZONE:", Name = ZoneName } )
local Zone = ZONE:New( ZoneName )
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone )
---
-- Quad-point zone
---
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
--for i,vec2 in pairs(ZoneData.verticies) do
-- local coord=COORDINATE:NewFromVec2(vec2)
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
--end
end
if Zone then
-- Store color of zone.
Zone.Color=color
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
end
end
-- Polygon zones defined by late activated groups.
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("#ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } )
-- Debug output
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
-- Create a new polygon zone.
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
-- Set color.
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
-- Store name in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -540,7 +540,7 @@ do -- FSM
--- Returns a table with the scores defined.
-- @param #FSM self
-- @param #table Scores.
-- @return #table Scores.
function FSM:GetScores()
return self._Scores or {}
end

View File

@@ -164,6 +164,7 @@ do -- COORDINATE
--
-- * @{#COORDINATE.WaypointAir}(): Build an air route point.
-- * @{#COORDINATE.WaypointGround}(): Build a ground route point.
-- * @{#COORDINATE.WaypointNaval}(): Build a naval route point.
--
-- Route points can be used in the Route methods of the @{Wrapper.Group#GROUP} class.
--
@@ -183,10 +184,18 @@ do -- COORDINATE
--
-- ## 9) Coordinate text generation
--
--
-- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance.
-- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text.
--
-- ## 10) Drawings on F10 map
--
-- * @{#COORDINATE.CircleToAll}(): Draw a circle on the F10 map.
-- * @{#COORDINATE.LineToAll}(): Draw a line on the F10 map.
-- * @{#COORDINATE.RectToAll}(): Draw a rectangle on the F10 map.
-- * @{#COORDINATE.QuadToAll}(): Draw a shape with four points on the F10 map.
-- * @{#COORDINATE.TextToAll}(): Write some text on the F10 map.
-- * @{#COORDINATE.ArrowToAll}(): Draw an arrow on the F10 map.
--
-- @field #COORDINATE
COORDINATE = {
ClassName = "COORDINATE",
@@ -625,9 +634,9 @@ do -- COORDINATE
--- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self
-- @param DCS#Distance OuterRadius
-- @param DCS#Distance InnerRadius
-- @return #COORDINATE
-- @param DCS#Distance OuterRadius Outer radius in meters.
-- @param DCS#Distance InnerRadius Inner radius in meters.
-- @return #COORDINATE self
function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius )
self:F2( { OuterRadius, InnerRadius } )
@@ -1984,13 +1993,13 @@ do -- COORDINATE
-- @param #COORDINATE self
-- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID which is a number.
function COORDINATE:LineToAll(Endpoint, Coalition, LineType, Color, Alpha, ReadOnly, Text)
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
@@ -2007,18 +2016,17 @@ do -- COORDINATE
--- Circle to all.
-- Creates a circle on the map with a given radius, color, fill color, and outline.
-- @param #COORDINATE self
-- @param #COORDINATE Center COORDIANTE of the center of the circle.
-- @param #numberr Radius Radius in meters. Default 1000 m.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number FillAlpha Transparency [0,1]. Default 0.5.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID which is a number.
function COORDINATE:CircleToAll(Radius, Coalition, LineType, Color, Alpha, FillColor, FillAlpha, ReadOnly, Text)
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
@@ -2029,14 +2037,193 @@ do -- COORDINATE
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or {1,0,0}
FillColor[4]=FillAlpha or 0.5
FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.15
trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "")
return MarkID
end
end -- Markings
--- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner.
-- Creates a line on the F10 map from one point to another.
-- @param #COORDINATE self
-- @param #COORDINATE Endpoint COORDIANTE in the opposite corner.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:RectToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or -1
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.15
trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "")
return MarkID
end
--- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified.
-- @param #COORDINATE self
-- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape.
-- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape.
-- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local point1=self:GetVec3()
local point2=Coord2:GetVec3()
local point3=Coord3:GetVec3()
local point4=Coord4:GetVec3()
Coalition=Coalition or -1
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.15
trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "")
return MarkID
end
--- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified.
-- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported.
-- @param #COORDINATE self
-- @param #table Coordinates Table of coordinates of the remaining points of the shape.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or -1
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
local vecs={}
vecs[1]=self:GetVec3()
for i,coord in ipairs(Coordinates) do
vecs[i+1]=coord:GetVec3()
end
if #vecs<3 then
self:E("ERROR: A free form polygon needs at least three points!")
elseif #vecs==3 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==4 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==5 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==6 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "")
elseif #vecs==7 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==8 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==9 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==10 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "")
else
self:E("ERROR: Currently a free form polygon can only have 10 points in total!")
-- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :(
trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "")
end
return MarkID
end
--- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map.
-- @param #COORDINATE self
-- @param #string Text Text displayed on the F10 map.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.3.
-- @param #number FontSize Font size. Default 14.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or -1
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.3
FontSize=FontSize or 14
trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World")
return MarkID
end
--- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow.
-- @param #COORDINATE self
-- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default).
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:ArrowToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or -1
Color=Color or {1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.15
--trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World")
trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "")
return MarkID
end
--- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.
-- @param #COORDINATE self

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

@@ -3998,7 +3998,25 @@ do -- SET_CLIENT
return self
end
--- Iterate the SET_CLIENT and count alive units.
-- @param #SET_CLIENT self
-- @return #number count
function SET_CLIENT:CountAlive()
local Set = self:GetSet()
local CountU = 0
for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP
if UnitData and UnitData:IsAlive() then
CountU = CountU + 1
end
end
return CountU
end
---
-- @param #SET_CLIENT self
-- @param Wrapper.Client#CLIENT MClient
@@ -4746,7 +4764,7 @@ do -- SET_AIRBASE
local airbaseName, airbase=self:FindInDatabase(EventData)
if airbase and airbase:IsShip() or airbase:IsHelipad() then
if airbase and (airbase:IsShip() or airbase:IsHelipad()) then
self:RemoveAirbasesByName(airbaseName)
end

View File

@@ -236,6 +236,7 @@ do -- SETTINGS
--- SETTINGS constructor.
-- @param #SETTINGS self
-- @param #string PlayerName (Optional) Set settings for this player.
-- @return #SETTINGS
function SETTINGS:Set( PlayerName )

View File

@@ -3408,8 +3408,8 @@ function SPAWN:_SpawnCleanUpScheduler()
if Stamp.Vec2 then
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
local NewVec2 = SpawnUnit:GetVec2()
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
-- If the plane is not moving, and is on the ground, assign it with a timestamp...
if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then
-- If the plane is not moving or dead , and is on the ground, assign it with a timestamp...
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor )
@@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler()
else
if SpawnUnit:InAir() == false then
Stamp.Vec2 = SpawnUnit:GetVec2()
if SpawnUnit:GetVelocityKMH() < 1 then
if (SpawnUnit:GetVelocityKMH() < 1) then
Stamp.Time = timer.getTime()
end
else

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

View File

@@ -1707,8 +1707,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

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

@@ -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.

View File

@@ -32,11 +32,13 @@
--
-- ===
--
-- ## Missions: Example missions will be added later.
-- ## Missions:
--
-- * [MAR - On the Range - MOOSE - SC](https://www.digitalcombatsimulator.com/en/files/3317765/) by shagrat
--
-- ===
--
-- ## Sound files: Check out the pinned messages in the Moose discord *#func-range* channel.
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
--
-- ===
--
@@ -91,9 +93,9 @@
-- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb.
-- @field #boolean autosave If true, automatically save results every X seconds.
-- @field #number instructorfreq Frequency on which the range control transmitts.
-- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
-- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
-- @field #number rangecontrolfreq Frequency on which the range control transmitts.
-- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
-- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
-- @field #string rangecontrolrelayname Name of relay unit.
-- @field #string instructorrelayname Name of relay unit.
-- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/".
@@ -2558,7 +2560,7 @@ function RANGE:_DisplayBombTargets(_unitname)
-- Get elevation
local elevation=coord:GetLandHeight()
local eltxt=string.format("%d m", elevation)
if _settings:IsImperial() then
if not _settings:IsMetric() then
elevation=UTILS.MetersToFeet(elevation)
eltxt=string.format("%d ft", elevation)
end
@@ -2829,6 +2831,7 @@ function RANGE:_CheckInZone(_unitName)
local accur=0
if shots>0 then
accur=_result.hits/shots*100
if accur > 100 then accur = 100 end
end
-- Message text.

View File

@@ -17,14 +17,15 @@
--
-- ### Authors: **FlightControl**, **applevangelist**
--
-- Last Update: April 2021
--
-- Last Update: Aug 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.
@@ -39,44 +40,28 @@
--
-- @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,
}
-- TODO Complete list?
--- Missile enumerators
-- @field Harms
SEAD.Harms = {
--[[
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
--]]
["AGM_88"] = "AGM_88",
["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45",
["ALARN"] = "ALARM",
["ALARM"] = "ALARM",
["LD-10"] = "LD-10",
["X_58"] = "X_58",
["X_28"] = "X_28",
@@ -85,32 +70,55 @@ SEAD = {
["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 #SEAD self
-- @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.
-- @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
-- @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.7")
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:HandleEvent( EVENTS.Shot, self.HandleEventShot )
self:I("*** SEAD - Started Version 0.3.1")
return self
end
--- Update the active SEAD Set
@@ -119,7 +127,7 @@ end
-- @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
@@ -137,7 +145,7 @@ end
-- @param #number range Set the engagement range in percent, e.g. 50
-- @return self
function SEAD:SetEngagementRange(range)
self:F( { range } )
self:T( { range } )
range = range or 75
if range < 0 or range > 100 then
range = 75
@@ -147,129 +155,172 @@ function SEAD:SetEngagementRange(range)
return self
end
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
-- @param #SEAD self
-- @param #number Padding Extra number of seconds to add for the switch-on
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
--- 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:F( { 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 end
if string.find(WeaponName,_name,1) then
hit = true
name = _name
break
end
end
return hit
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
--- 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.
-- @see SEAD
-- @param #SEAD
-- @param Core.Event#EVENTDATA EventData
function SEAD:OnEventShot( EventData )
self:T( { EventData } )
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
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
--]]
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
--self:T({ SEADWeapon })
if self:_CheckHarms(SEADWeaponName) then
self:T( '*** SEAD - Weapon Match' )
local _targetskill = "Random"
local _targetMimgroupName = "none"
local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = EventData.Weapon:getTarget() -- Identify target
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
if _targetUnit and _targetUnit:IsAlive() then
local _targetMimgroup = _targetUnit:GetGroup()
local _targetMimgroupName = _targetMimgroup:GetName() -- group name
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
self:T( self.SEADGroupPrefixes )
self:T( _targetMimgroupName )
end
-- 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.groupName:enableEmission(true)
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)
--_targetMimgroup:enableEmission(false)
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
end
end
end
end
end
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( 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
grp:OptionAlarmStateGreen()
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[args[2]] = false
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
end
end
end
end
end
end
end

View File

@@ -18,7 +18,7 @@
-- @module Functional.Shorad
-- @image Functional.Shorad.jpg
--
-- Date: May 2021
-- Date: July 2021
-------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE
@@ -108,28 +108,12 @@ do
--- Missile enumerators
-- @field Harms
SHORAD.Harms = {
--[[
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
--]]
["AGM_88"] = "AGM_88",
["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45",
["ALARN"] = "ALARM",
["ALARM"] = "ALARM",
["LD-10"] = "LD-10",
["X_58"] = "X_58",
["X_28"] = "X_28",
@@ -157,7 +141,9 @@ do
-- @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"
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition)
-- @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})
@@ -171,22 +157,23 @@ do
self.ActiveTimer = ActiveTimer or 600
self.ActiveGroups = {}
self.Groupset = GroupSet
self:HandleEvent( EVENTS.Shot )
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 = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
self:I("*** SHORAD - Started Version 0.2.5")
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})
@@ -195,31 +182,48 @@ 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,10 do
for i=1,100 do
math.random()
end
return self
end
--- Switch debug state
--- Switch debug state on
-- @param #SHORAD self
-- @param #boolean debug Switch debug on (true) or off (false)
function SHORAD:SwitchDebug(debug)
self:T( { debug } )
local onoff = debug or false
if debug then
self.debug = true
--tracing
BASE:TraceOn()
BASE:TraceClass("SHORAD")
function SHORAD:SwitchDebug(onoff)
self:T( { onoff } )
if onoff then
self:SwitchDebugOn()
else
self.debug = false
BASE:TraceOff()
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
@@ -229,6 +233,7 @@ do
self:T( { onoff } )
local onoff = onoff or true
self.DefendHarms = onoff
return self
end
--- Switch defense for AGMs
@@ -238,6 +243,7 @@ do
self:T( { onoff } )
local onoff = onoff or true
self.DefendMavs = onoff
return self
end
--- Set defense probability limits
@@ -256,35 +262,42 @@ do
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
@@ -292,6 +305,7 @@ do
-- @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
@@ -307,6 +321,7 @@ do
-- @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
@@ -322,6 +337,7 @@ do
-- @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
@@ -344,6 +360,7 @@ do
-- @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
@@ -352,7 +369,7 @@ do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then
returnname = true
_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
end
end
return returnname
@@ -363,6 +380,7 @@ do
-- @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
@@ -381,6 +399,7 @@ do
-- @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
@@ -405,6 +424,7 @@ do
-- 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
@@ -442,7 +462,7 @@ do
self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then
_group:SetAIOn()
--_group:SetAIOn()
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
@@ -454,14 +474,15 @@ do
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:OnEventShot( EventData )
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
@@ -524,4 +545,4 @@ do
end
-----------------------------------------------------------------------
-- SHORAD end
-----------------------------------------------------------------------
-----------------------------------------------------------------------

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' )
@@ -74,6 +73,8 @@ __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' )
@@ -112,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' )

View File

@@ -18,6 +18,7 @@
-- * Option to present information in imperial or metric units
-- * Runway length and airfield elevation (optional)
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional)
-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary)
--
-- ===
--
@@ -34,7 +35,7 @@
--
-- ===
--
-- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel.
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
--
-- ===
--
@@ -59,7 +60,7 @@
-- @field #number frequency Radio frequency in MHz.
-- @field #number modulation Radio modulation 0=AM or 1=FM.
-- @field #number power Radio power in Watts. Default 100 W.
-- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages.
-- @field Sound.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages.
-- @field #string soundpath Path to sound files.
-- @field #string relayunitname Name of the radio relay unit.
-- @field #table towerfrequency Table with tower frequencies.
@@ -88,6 +89,9 @@
-- @field #boolean usemarker Use mark on the F10 map.
-- @field #number markerid Numerical ID of the F10 map mark point.
-- @field #number relHumidity Relative humidity (used to approximately calculate the dew point).
-- @field #boolean useSRS If true, use SRS for transmission.
-- @field Sound.SRS#MSRS msrs Moose SRS object.
-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used.
-- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -252,6 +256,16 @@
-- # Marks on the F10 Map
--
-- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature).
--
-- # Text-To-Speech
--
-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing.
-- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented.
--
-- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file.
--
-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before
-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds.
--
-- # Examples
--
@@ -283,7 +297,14 @@
-- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2})
-- atisAbuDhabi:SetVOR(114.25)
-- atisAbuDhabi:Start()
--
-- ## SRS
--
-- atis=ATIS:New("Batumi", 305, radio.modulation.AM)
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US")
-- atis:Start()
--
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
--
-- @field #ATIS
ATIS = {
@@ -368,6 +389,7 @@ ATIS.Alphabet = {
-- @field #number PersianGulf +2° (East).
-- @field #number TheChannel -10° (West).
-- @field #number Syria +5° (East).
-- @field #number MarianaIslands +2° (East).
ATIS.RunwayM2T={
Caucasus=0,
Nevada=12,
@@ -375,6 +397,7 @@ ATIS.RunwayM2T={
PersianGulf=2,
TheChannel=-10,
Syria=5,
MarianaIslands=2,
}
--- Whether ICAO phraseology is used for ATIS broadcasts.
@@ -385,6 +408,7 @@ ATIS.RunwayM2T={
-- @field #boolean PersianGulf true.
-- @field #boolean TheChannel true.
-- @field #boolean Syria true.
-- @field #boolean MarianaIslands true.
ATIS.ICAOPhraseology={
Caucasus=true,
Nevada=false,
@@ -392,6 +416,7 @@ ATIS.ICAOPhraseology={
PersianGulf=true,
TheChannel=true,
Syria=true,
MarianaIslands=true,
}
--- Nav point data.
@@ -564,7 +589,7 @@ _ATIS={}
--- ATIS class version.
-- @field #string version
ATIS.version="0.9.1"
ATIS.version="0.9.6"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -628,6 +653,7 @@ function ATIS:New(airbasename, frequency, modulation)
self:SetAltimeterQNH(true)
self:SetMapMarks(false)
self:SetRelativeHumidity()
self:SetQueueUpdateTime()
-- Start State.
self:SetStartState("Stopped")
@@ -955,7 +981,9 @@ end
-- * 170° on the Normany map
-- * 182° on the Persian Gulf map
--
-- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation.
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
--
-- Or you make your life simple and just include the sign so you don't have to bother about East/West.
--
-- @param #ATIS self
-- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}.
@@ -1100,6 +1128,44 @@ function ATIS:MarkRunways(markall)
end
end
--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary.
-- @param #ATIS self
-- @param #string PathToSRS Path to SRS directory.
-- @param #string Gender Gender: "male" or "female" (default).
-- @param #string Culture Culture, e.g. "en-GB" (default).
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`.
-- @param #number Port SRS port. Default 5002.
-- @return #ATIS self
function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port)
self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port)
self.msrs:SetCoalition(self:GetCoalition())
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
end
return self
end
--- Set the time interval between radio queue updates.
-- @param #ATIS self
-- @param #number TimeInterval Interval in seconds. Default 5 sec.
-- @return #ATIS self
function ATIS:SetQueueUpdateTime(TimeInterval)
self.dTQueueCheck=TimeInterval or 5
end
--- Get the coalition of the associated airbase.
-- @param #ATIS self
-- @return #number Coalition of the associcated airbase.
function ATIS:GetCoalition()
local coal=self.airbase and self.airbase:GetCoalition() or nil
return coal
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1146,6 +1212,10 @@ function ATIS:onafterStart(From, Event, To)
-- Start radio queue.
self.radioqueue:Start(1, 0.1)
-- Handle airbase capture
-- Handle events.
self:HandleEvent(EVENTS.BaseCaptured)
-- Init status updates.
self:__Status(-2)
@@ -1171,7 +1241,13 @@ function ATIS:onafterStatus(From, Event, To)
end
-- Info text.
local text=string.format("State %s: Freq=%.3f MHz %s, Relay unit=%s (alive=%s)", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation), tostring(self.relayunitname), relayunitstatus)
local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation))
if self.useSRS then
text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s",
tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice))
else
text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus)
end
self:I(self.lid..text)
self:__Status(-60)
@@ -1188,15 +1264,25 @@ end
-- @param #string To To state.
function ATIS:onafterCheckQueue(From, Event, To)
if #self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
if self.useSRS then
self:Broadcast()
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
if #self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
self:Broadcast()
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
end
end
-- Check back in 5 seconds.
self:__CheckQueue(-5)
-- Check back in 5 seconds.
self:__CheckQueue(-math.abs(self.dTQueueCheck))
end
--- Broadcast ATIS radio message.
@@ -1322,11 +1408,14 @@ function ATIS:onafterBroadcast(From, Event, To)
if time < 0 then
time = 24*60*60 + time --avoid negative time around midnight
end
end
local clock=UTILS.SecondsToClock(time)
local zulu=UTILS.Split(clock, ":")
local ZULU=string.format("%s%s", zulu[1], zulu[2])
if self.useSRS then
ZULU=string.format("%s hours", zulu[1])
end
-- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc.
@@ -1346,10 +1435,17 @@ function ATIS:onafterBroadcast(From, Event, To)
local sunrise=coord:GetSunrise()
sunrise=UTILS.Split(sunrise, ":")
local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2])
if self.useSRS then
SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2])
end
local sunset=coord:GetSunset()
sunset=UTILS.Split(sunset, ":")
local SUNSET=string.format("%s%s", sunset[1], sunset[2])
if self.useSRS then
SUNSET=string.format("%s %s hours", sunset[1], sunset[2])
end
---------------------------------
--- Temperature and Dew Point ---
@@ -1521,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Scattered 4
clouddens=4
elseif cloudspreset:find("RainyPreset") then
-- Overcast + Rain
clouddens=9
if temperature>5 then
precepitation=1 -- rain
else
precepitation=3 -- snow
end
elseif cloudspreset:find("RainyPreset1") then
-- Overcast + Rain
clouddens=9
if temperature>5 then
precepitation=1 -- rain
else
precepitation=3 -- snow
end
elseif cloudspreset:find("RainyPreset2") then
-- Overcast + Rain
clouddens=9
if temperature>5 then
precepitation=1 -- rain
else
precepitation=3 -- snow
end
elseif cloudspreset:find("RainyPreset3") then
-- Overcast + Rain
clouddens=9
if temperature>5 then
@@ -1591,36 +1711,46 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then
subtitle=subtitle.." Airport"
end
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
if not self.useSRS then
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
end
local alltext=subtitle
-- Information tag
subtitle=string.format("Information %s", NATO)
local _INFORMATION=subtitle
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
if not self.useSRS then
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle
-- Zulu Time
subtitle=string.format("%s Zulu", ZULU)
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
if not self.useSRS then
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
end
alltext=alltext..";\n"..subtitle
if not self.zulutimeonly then
-- Sunrise Time
subtitle=string.format("Sunrise at %s local time", SUNRISE)
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle
-- Sunset Time
subtitle=string.format("Sunset at %s local time", SUNSET)
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle
end
@@ -1634,17 +1764,19 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle..", gusting"
end
local _WIND=subtitle
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At, 0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
else
self:Transmission(ATIS.Sound.Knots, 0.2)
end
if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At, 0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
else
self:Transmission(ATIS.Sound.Knots, 0.2)
end
if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@@ -1654,12 +1786,14 @@ function ATIS:onafterBroadcast(From, Event, To)
else
subtitle=string.format("Visibility %s SM", VISIBILITY)
end
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(ATIS.Sound.Kilometers, 0.2)
else
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(ATIS.Sound.Kilometers, 0.2)
else
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@@ -1699,57 +1833,67 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Actual output
if wp then
subtitle=string.format("Weather phenomena:%s", wpsub)
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
if precepitation==1 then
self:Transmission(ATIS.Sound.Rain, 0.5)
elseif precepitation==2 then
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
elseif precepitation==3 then
self:Transmission(ATIS.Sound.Snow, 0.5)
elseif precepitation==4 then
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
end
if fog then
self:Transmission(ATIS.Sound.Fog, 0.5)
end
if dust then
self:Transmission(ATIS.Sound.Dust, 0.5)
if not self.useSRS then
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
if precepitation==1 then
self:Transmission(ATIS.Sound.Rain, 0.5)
elseif precepitation==2 then
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
elseif precepitation==3 then
self:Transmission(ATIS.Sound.Snow, 0.5)
elseif precepitation==4 then
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
end
if fog then
self:Transmission(ATIS.Sound.Fog, 0.5)
end
if dust then
self:Transmission(ATIS.Sound.Dust, 0.5)
end
end
alltext=alltext..";\n"..subtitle
end
-- Cloud base
self:Transmission(CloudCover, 1.0, CLOUDSsub)
if not self.useSRS then
self:Transmission(CloudCover, 1.0, CLOUDSsub)
end
if CLOUDBASE and static then
-- Base
local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100)
local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100)
if self.metric then
subtitle=string.format("Cloudbase %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
--subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil)
else
subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL)
--subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL)
subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil)
end
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
-- Ceiling
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
if not self.useSRS then
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
-- Ceiling
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
end
alltext=alltext..";\n"..subtitle
@@ -1769,15 +1913,17 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
local _TEMPERATURE=subtitle
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
if temperature<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
if temperature<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@@ -1796,15 +1942,17 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
local _DEWPOINT=subtitle
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@@ -1813,51 +1961,53 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
end
else
if self.metric then
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
end
else
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
end
end
end
local _ALTIMETER=subtitle
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH, 0.5)
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if not self.qnhonly then
self:Transmission(ATIS.Sound.QFE, 0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH, 0.5)
end
self.radioqueue:Number2Transmission(QFE[2])
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if self.PmmHg then
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
else
if self.metric then
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QFE, 0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QFE[2])
end
if self.PmmHg then
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
else
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
if self.metric then
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
else
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
end
end
end
alltext=alltext..";\n"..subtitle
@@ -1870,12 +2020,14 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." Right"
end
local _RUNACT=subtitle
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runway)
if rwyLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runway)
if rwyLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@@ -1900,21 +2052,22 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- Transmit.
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
if not self.useSRS then
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
alltext=alltext..";\n"..subtitle
end
@@ -1937,22 +2090,23 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." feet"
end
-- Transmitt.
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
-- Transmit.
if not self.useSRS then
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
alltext=alltext..";\n"..subtitle
end
@@ -1966,9 +2120,47 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
subtitle=string.format("Tower frequency %s", freqs)
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq)
if not self.useSRS then
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
end
alltext=alltext..";\n"..subtitle
end
-- ILS
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
local f=string.format("%.2f", ils.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- Outer NDB
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
@@ -1977,41 +2169,6 @@ function ATIS:onafterBroadcast(From, Event, To)
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- ILS
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
local f=string.format("%.2f", ils.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
-- Outer NDB
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
@@ -2019,51 +2176,58 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft)
if ndb then
subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency)
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- VOR
if self.vor then
subtitle=string.format("VOR frequency %.2f MHz", self.vor)
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
local f=string.format("%.2f", self.vor)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
if self.useSRS then
subtitle=string.format("V O R frequency %.2f MHz", self.vor)
end
if not self.useSRS then
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
local f=string.format("%.2f", self.vor)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
-- TACAN
if self.tacan then
subtitle=string.format("TACAN channel %dX", self.tacan)
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- RSBN
if self.rsbn then
subtitle=string.format("RSBN channel %d", self.rsbn)
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
end
alltext=alltext..";\n"..subtitle
end
@@ -2071,17 +2235,19 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft)
if ndb then
subtitle=string.format("PRMG channel %d", ndb.frequency)
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
if not self.useSRS then
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
end
alltext=alltext..";\n"..subtitle
end
-- Advice on initial...
subtitle=string.format("Advise on initial contact, you have information %s", NATO)
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
if not self.useSRS then
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle
-- Report ATIS text.
@@ -2102,6 +2268,62 @@ end
-- @param #string Text Report text.
function ATIS:onafterReport(From, Event, To, Text)
self:T(self.lid..string.format("Report:\n%s", Text))
if self.useSRS and self.msrs then
-- Remove line breaks
local text=string.gsub(Text, "[\r\n]", "")
-- Replace other stuff.
local text=string.gsub(text, "SM", "statute miles")
local text=string.gsub(text, "°C", "degrees Celsius")
local text=string.gsub(text, "°F", "degrees Fahrenheit")
local text=string.gsub(text, "inHg", "inches of Mercury")
local text=string.gsub(text, "mmHg", "millimeters of Mercury")
local text=string.gsub(text, "hPa", "hecto Pascals")
local text=string.gsub(text, "m/s", "meters per second")
-- Replace ";" by "."
local text=string.gsub(text, ";", " . ")
--Debug output.
self:T("SRS TTS: "..text)
-- Play text-to-speech report.
self.msrs:PlayText(text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Base captured
-- @param #ATIS self
-- @param Core.Event#EVENTDATA EventData Event data.
function ATIS:OnEventBaseCaptured(EventData)
if EventData and EventData.Place then
-- Place is the airbase that was captured.
local airbase=EventData.Place --Wrapper.Airbase#AIRBASE
-- Check that this airbase belongs or did belong to this warehouse.
if EventData.PlaceName==self.airbasename then
-- New coalition of airbase after it was captured.
local NewCoalitionAirbase=airbase:GetCoalition()
if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then
self.msrs:SetCoalition(NewCoalitionAirbase)
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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,692 @@
--- **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 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.
--
-- ## 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(' -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

@@ -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 sanitisation.
-- 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 initialise 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 specfic 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"
--- DONT 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, 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
--
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,41 +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
-- * AIRBASE.Syria.H4
-- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.Rosh_Pina
-- * AIRBASE.Syria.Sayqal
-- * AIRBASE.Syria.Shayrat
-- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Tha_lah
-- * AIRBASE.Syria.Naqoura
--
-- @field Syria
--@field Syria
AIRBASE.Syria={
["Kuweires"]="Kuweires",
["Marj_Ruhayyil"]="Marj Ruhayyil",
@@ -380,39 +396,71 @@ 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",
["Abu_al_Duhur"]="Abu al-Duhur",
["H4"]="H4",
["Gaziantep"]="Gaziantep",
["Rosh_Pina"]="Rosh Pina",
["Sayqal"]="Sayqal",
["Shayrat"]="Shayrat",
["Tiyas"]="Tiyas",
["Tha_lah"]="Tha'lah",
["Naqoura"]="Naqoura",
}
--- 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",
}
@@ -479,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
@@ -499,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
@@ -646,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.
@@ -885,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
@@ -905,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
@@ -937,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)
@@ -954,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
@@ -985,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
@@ -1037,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
@@ -1071,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
@@ -1114,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={}
@@ -1244,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.
@@ -1345,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
@@ -1364,7 +1446,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Airbase name.
local name=self:GetName()
-- Exceptions
if name==AIRBASE.Nevada.Jean_Airport or
@@ -1372,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
@@ -1413,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
@@ -1448,7 +1531,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
j=2
end
end
return j
end
@@ -1457,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
@@ -1432,7 +1429,7 @@ end
-- @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.
-- @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, Altitude, ASL )
@@ -1454,7 +1451,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
DCSTask.params.expendQty = AmmoCount
DCSTask.params.expendQtyEnabled = true
end
if Altitude then
DCSTask.params.altitude=Altitude
end
@@ -1462,7 +1459,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
if WeaponType then
DCSTask.params.weaponType=WeaponType
end
--self:I(DCSTask)
return DCSTask
@@ -1482,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.
@@ -1489,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 )
@@ -1811,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... )
-- DCS task.
local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript )))
return DCSTask
end
@@ -1853,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" )
@@ -1894,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.
@@ -1909,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 )
@@ -1952,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
@@ -1962,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 )
@@ -1988,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points )
id = 'Mission',
params = {
airborne = self:IsAir(),
route = {points = Points},
route = {points = Points},
},
}
@@ -2914,9 +2959,9 @@ end
function CONTROLLABLE:OptionROE(ROEvalue)
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
@@ -3480,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
@@ -3497,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
@@ -3510,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
@@ -3524,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
@@ -3537,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
@@ -3681,22 +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
@@ -3709,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
@@ -3718,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
@@ -3734,11 +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
-- @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)
self:F2( { self.ControllableName } )
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)
@@ -3746,36 +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.
-- @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 } )
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 Controller then
if self:IsGround() then
self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds)
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

@@ -1137,7 +1137,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 +1147,7 @@ function GROUP:GetAmmunition()
local Nshells=0
local Nrockets=0
local Nmissiles=0
local Nbombs=0
if DCSControllable then
@@ -1155,18 +1156,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
@@ -2551,9 +2553,10 @@ do -- Players
end
--- GROUND - Switch on/off radar emissions
--- GROUND - Switch on/off radar emissions for the group.
-- @param #GROUP self
-- @param #boolean switch
-- @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
@@ -2566,6 +2569,46 @@ function GROUP: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

View File

@@ -684,6 +684,27 @@ function POSITIONABLE:IsShip()
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
--- Returns true if the POSITIONABLE is in the air.
-- Polymorphic, is overridden in GROUP and UNIT.
-- @param Wrapper.Positionable#POSITIONABLE self
@@ -1513,6 +1534,7 @@ do -- Cargo
["Ural-4320 APA-5D"] = 10,
["Ural-4320T"] = 14,
["ZBD04A"] = 7, -- new by kappa
["VAB_Mephisto"] = 8, -- new by Apple
}
local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95

View File

@@ -580,6 +580,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.
@@ -761,40 +773,6 @@ function UNIT:GetFuel()
return nil
end
--- Sets the passed group or unit objects radar emitters on or off. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
-- @param #UNIT self
-- @param #boolean Switch If `true` or `nil`, emission is enabled. If `false`, emission is turned off.
-- @return #UNIT self
function UNIT:SetEmission(Switch)
if Switch==nil then
Switch=true
end
local DCSUnit = self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(Switch)
end
return self
end
--- Sets the passed group or unit objects radar emitters ON. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
-- @param #UNIT self
-- @return #UNIT self
function UNIT:EnableEmission()
self:SetEmission(true)
return self
end
--- Sets the passed group or unit objects radar emitters OFF. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
-- @param #UNIT self
-- @return #UNIT self
function UNIT:DisableEmission()
self:SetEmission(false)
return self
end
--- Returns a list of one @{Wrapper.Unit}.
-- @param #UNIT self
@@ -1429,9 +1407,10 @@ function UNIT:GetTemplateFuel()
return nil
end
--- GROUND - Switch on/off radar emissions.
--- GROUND - Switch on/off radar emissions of a unit.
-- @param #UNIT self
-- @param #boolean switch
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
-- @return #UNIT self
function UNIT:EnableEmission(switch)
self:F2( self.UnitName )
@@ -1445,4 +1424,15 @@ function UNIT: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
@@ -75,6 +74,8 @@ 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
@@ -113,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

@@ -30,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
@@ -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