Compare commits

...

85 Commits
2.7.3 ... 2.7.6

Author SHA1 Message Date
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
41 changed files with 10863 additions and 2075 deletions

View File

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

View File

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

View File

@@ -64,20 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" ) self:SetStartState( "Unloaded" )
-- Boarding
self:AddTransition( "Unloaded", "Pickup", "*" ) self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
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( "*", "Landed", "*" )
self:AddTransition( "*", "Load", "*" )
self:AddTransition( "*", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
-- Unboarding
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" ) self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Home" , "*" ) self:AddTransition( "*", "Destroyed", "*" )
self:AddTransition( "*", "Unload", "*" )
self:AddTransition( "*", "Unloaded", "Unloaded" )
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
self:AddTransition( "*", "Destroyed", "Destroyed" ) -- RTB
self:AddTransition( "*", "Home" , "*" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER --- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
@@ -207,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
self:SetCarrier( Helicopter ) self:SetCarrier( Helicopter )
self.landingspeed = 15 -- kph
self.landingheight = 5.5 -- meter
return self return self
end end
@@ -255,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
return self return self
end end
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
-- @param #AI_CARGO_HELICOPTER self
-- @param #number speed Landing speed in kph(!), e.g. 15
-- @param #number height Landing height in meters(!), e.g. 5.5
-- @return #AI_CARGO_HELICOPTER self
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
--
-- BASE:TraceOn()
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
--
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
-- Adjust if necessary.
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
local _speed = speed or 15
local _height = height or 5.5
self.landingheight = _height
self.landingspeed = _speed
return self
end
--- @param #AI_CARGO_HELICOPTER self --- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter -- @param Wrapper.Group#GROUP Helicopter
@@ -271,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
-- 1 - When the helo lands normally on the ground. -- 1 - When the helo lands normally on the ground.
-- 2 - when the helo is hit and goes RTB or even when it is destroyed. -- 2 - when the helo is hit and goes RTB or even when it is destroyed.
-- For point 2, this is an issue, the infantry may not unload in this case! -- For point 2, this is an issue, the infantry may not unload in this case!
-- So we check if the helo is on the ground, and velocity< 5. -- So we check if the helo is on the ground, and velocity< 15.
-- Only then the infantry can unload (and load too, for consistency)! -- Only then the infantry can unload (and load too, for consistency)!
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
if self.RoutePickup == true then if self.RoutePickup == true then
if Helicopter:GetHeight( true ) <= 5.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( Helicopter:GetPointVec2() )
self:Load( self.PickupZone ) self:Load( self.PickupZone )
self.RoutePickup = false self.RoutePickup = false
@@ -285,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
end end
if self.RouteDeploy == true then if self.RouteDeploy == true then
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
self:Unload( self.DeployZone ) self:Unload( self.DeployZone )
self.RouteDeploy = false self.RouteDeploy = false
end end

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

@@ -301,21 +301,75 @@ do -- Zones
for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
local ZoneName = ZoneData.name local ZoneName = ZoneData.name
self:I( { "Register ZONE:", Name = ZoneName } ) -- Color
local Zone = ZONE:New( ZoneName ) local color=ZoneData.color or {1, 0, 0, 0.15}
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone ) -- Create new Zone
local Zone=nil --Core.Zone#ZONE_BASE
if ZoneData.type==0 then
---
-- Circular zone
---
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
Zone=ZONE:New(ZoneName)
else
---
-- Quad-point zone
---
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
--for i,vec2 in pairs(ZoneData.verticies) do
-- local coord=COORDINATE:NewFromVec2(vec2)
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
--end
end 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 for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("#ZONE_POLYGON") then if ZoneGroupName:match("#ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) -- Debug output
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
-- Create a new polygon zone.
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
-- Set color.
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
-- Store name in DB.
self.ZONENAMES[ZoneName] = ZoneName self.ZONENAMES[ZoneName] = ZoneName
-- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon ) self:AddZone( ZoneName, Zone_Polygon )
end end
end end

View File

@@ -1000,11 +1000,7 @@ function EVENT:onEvent( Event )
-- Check if this is a known event? -- Check if this is a known event?
if EventMeta then if EventMeta then
if self and if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit)) then
self.Events and
self.Events[Event.id] and
self.MissionEnd == false and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.id and Event.id == EVENTS.MissionEnd then if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true self.MissionEnd = true
@@ -1039,9 +1035,10 @@ function EVENT:onEvent( Event )
end end
if Event.IniObjectCategory == Object.Category.STATIC then if Event.IniObjectCategory == Object.Category.STATIC then
if Event.id==31 then if Event.id==31 then
--env.info("FF event 31")
-- Event.initiator is a Static object representing the pilot. But getName() error due to DCS bug. -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug.
Event.IniDCSUnit = Event.initiator Event.IniDCSUnit = Event.initiator
local ID=Event.initiator.id_ local ID=Event.initiator.id_
Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID))
@@ -1123,7 +1120,6 @@ function EVENT:onEvent( Event )
end end
if Event.TgtObjectCategory == Object.Category.STATIC then if Event.TgtObjectCategory == Object.Category.STATIC then
BASE:T({StaticTgtEvent = Event.id})
-- get base data -- get base data
Event.TgtDCSUnit = Event.target Event.TgtDCSUnit = Event.target
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object

View File

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

View File

@@ -164,6 +164,7 @@ do -- COORDINATE
-- --
-- * @{#COORDINATE.WaypointAir}(): Build an air route point. -- * @{#COORDINATE.WaypointAir}(): Build an air route point.
-- * @{#COORDINATE.WaypointGround}(): Build a ground 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. -- 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 -- ## 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.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. -- * @{#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 -- @field #COORDINATE
COORDINATE = { COORDINATE = {
ClassName = "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. --- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Distance OuterRadius -- @param DCS#Distance OuterRadius Outer radius in meters.
-- @param DCS#Distance InnerRadius -- @param DCS#Distance InnerRadius Inner radius in meters.
-- @return #COORDINATE -- @return #COORDINATE self
function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius )
self:F2( { OuterRadius, InnerRadius } ) self:F2( { OuterRadius, InnerRadius } )
@@ -1984,13 +1993,13 @@ do -- COORDINATE
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. -- @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 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 #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 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 #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. -- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID which is a number. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:LineToAll(Endpoint, Coalition, LineType, Color, Alpha, ReadOnly, Text) function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID() local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then if ReadOnly==nil then
ReadOnly=false ReadOnly=false
@@ -2007,18 +2016,17 @@ do -- COORDINATE
--- Circle to all. --- Circle to all.
-- Creates a circle on the map with a given radius, color, fill color, and outline. -- Creates a circle on the map with a given radius, color, fill color, and outline.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #COORDINATE Center COORDIANTE of the center of the circle.
-- @param #numberr Radius Radius in meters. Default 1000 m. -- @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 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 #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 Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @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.5. -- @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 #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. -- @param #string Text (Optional) Text displayed when mark is added. Default none.
-- @return #number The resulting Mark ID which is a number. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again.
function COORDINATE:CircleToAll(Radius, Coalition, LineType, Color, Alpha, FillColor, FillAlpha, ReadOnly, Text) function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text)
local MarkID = UTILS.GetMarkID() local MarkID = UTILS.GetMarkID()
if ReadOnly==nil then if ReadOnly==nil then
ReadOnly=false ReadOnly=false
@@ -2029,14 +2037,193 @@ do -- COORDINATE
Color=Color or {1,0,0} Color=Color or {1,0,0}
Color[4]=Alpha or 1.0 Color[4]=Alpha or 1.0
LineType=LineType or 1 LineType=LineType or 1
FillColor=FillColor or {1,0,0} FillColor=FillColor or Color
FillColor[4]=FillAlpha or 0.5 FillColor[4]=FillAlpha or 0.15
trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "")
return MarkID return MarkID
end end
end -- Markings 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. --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.
-- @param #COORDINATE self -- @param #COORDINATE self

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
-- * Get zone properties. -- * Get zone properties.
-- * Get zone bounding box. -- * Get zone bounding box.
-- * Set/get zone name. -- * Set/get zone name.
-- * Draw zones (circular and polygon) on the F10 map.
-- --
-- --
-- There are essentially two core functions that zones accomodate: -- There are essentially two core functions that zones accomodate:
@@ -56,6 +57,8 @@
--- @type ZONE_BASE --- @type ZONE_BASE
-- @field #string ZoneName Name of the zone. -- @field #string ZoneName Name of the zone.
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -104,6 +107,8 @@ ZONE_BASE = {
ClassName = "ZONE_BASE", ClassName = "ZONE_BASE",
ZoneName = "", ZoneName = "",
ZoneProbability = 1, ZoneProbability = 1,
DrawID=nil,
Color={}
} }
@@ -221,22 +226,6 @@ function ZONE_BASE:GetPointVec2()
end end
--- Returns a @{Core.Point#COORDINATE} of the zone.
-- @param #ZONE_BASE self
-- @return Core.Point#COORDINATE The Coordinate of the zone.
function ZONE_BASE:GetCoordinate()
self:F2( self.ZoneName )
local Vec2 = self:GetVec2()
local Coordinate = COORDINATE:NewFromVec2( Vec2 )
self:T2( { Coordinate } )
return Coordinate
end
--- Returns the @{DCS#Vec3} of the zone. --- Returns the @{DCS#Vec3} of the zone.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
@@ -280,11 +269,23 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1
local Vec3 = self:GetVec3( Height ) local Vec3 = self:GetVec3( Height )
local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) if self.Coordinate then
self:T2( { PointVec3 } ) -- Update coordinates.
self.Coordinate.x=Vec3.x
self.Coordinate.y=Vec3.y
self.Coordinate.z=Vec3.z
return PointVec3 --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName))
else
-- Create a new coordinate object.
self.Coordinate=COORDINATE:NewFromVec3(Vec3)
--env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName))
end
return self.Coordinate
end end
@@ -324,6 +325,76 @@ function ZONE_BASE:BoundZone()
end end
--- Set color of zone.
-- @param #ZONE_BASE self
-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`.
-- @param #number Alpha Transparacy between 0 and 1. Default 0.15.
-- @return #ZONE_BASE self
function ZONE_BASE:SetColor(RGBcolor, Alpha)
RGBcolor=RGBcolor or {1, 0, 0}
Alpha=Alpha or 0.15
self.Color={}
self.Color[1]=RGBcolor[1]
self.Color[2]=RGBcolor[2]
self.Color[3]=RGBcolor[3]
self.Color[4]=Alpha
return self
end
--- Get color table of the zone.
-- @param #ZONE_BASE self
-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
function ZONE_BASE:GetColor()
return self.Color
end
--- Get RGB color of zone.
-- @param #ZONE_BASE self
-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code.
function ZONE_BASE:GetColorRGB()
local rgb={}
rgb[1]=self.Color[1]
rgb[2]=self.Color[2]
rgb[3]=self.Color[3]
return rgb
end
--- Get transperency Alpha value of zone.
-- @param #ZONE_BASE self
-- @return #number Alpha value.
function ZONE_BASE:GetColorAlpha()
local alpha=self.Color[4]
return alpha
end
--- Remove the drawing of the zone from the F10 map.
-- @param #ZONE_BASE self
-- @param #number Delay (Optional) Delay before the drawing is removed.
-- @return #ZONE_BASE self
function ZONE_BASE:UndrawZone(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self)
else
if self.DrawID then
UTILS.RemoveMark(self.DrawID)
end
end
return self
end
--- Get ID of the zone object drawn on the F10 map.
-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`.
-- @param #ZONE_BASE self
-- @return #number Unique ID of the
function ZONE_BASE:GetDrawID()
return self.DrawID
end
--- Smokes the zone boundaries in a color. --- Smokes the zone boundaries in a color.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
@@ -421,6 +492,10 @@ end
-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone.
-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight.
-- --
-- ## Draw zone
--
-- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map.
--
-- @field #ZONE_RADIUS -- @field #ZONE_RADIUS
ZONE_RADIUS = { ZONE_RADIUS = {
ClassName="ZONE_RADIUS", ClassName="ZONE_RADIUS",
@@ -433,12 +508,51 @@ ZONE_RADIUS = {
-- @param DCS#Distance Radius The radius of the zone. -- @param DCS#Distance Radius The radius of the zone.
-- @return #ZONE_RADIUS self -- @return #ZONE_RADIUS self
function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
-- Inherit ZONE_BASE.
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
self:F( { ZoneName, Vec2, Radius } ) self:F( { ZoneName, Vec2, Radius } )
self.Radius = Radius self.Radius = Radius
self.Vec2 = Vec2 self.Vec2 = Vec2
--self.Coordinate=COORDINATE:NewFromVec2(Vec2)
return self
end
--- Update zone from a 2D vector.
-- @param #ZONE_RADIUS self
-- @param DCS#Vec2 Vec2 The location of the zone.
-- @param DCS#Distance Radius The radius of the zone.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius)
-- New center of the zone.
self.Vec2=Vec2
if Radius then
self.Radius=Radius
end
return self
end
--- Update zone from a 2D vector.
-- @param #ZONE_RADIUS self
-- @param DCS#Vec3 Vec3 The location of the zone.
-- @param DCS#Distance Radius The radius of the zone.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius)
-- New center of the zone.
self.Vec2.x=Vec3.x
self.Vec2.y=Vec3.z
if Radius then
self.Radius=Radius
end
return self return self
end end
@@ -469,6 +583,32 @@ function ZONE_RADIUS:MarkZone(Points)
end end
--- Draw the zone circle on the F10 map.
-- @param #ZONE_RADIUS self
-- @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.
-- @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.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
local coordinate=self:GetCoordinate()
local Radius=self:GetRadius()
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or Color
FillAlpha=FillAlpha or self:GetColorAlpha()
self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
return self
end
--- Bounds the zone with tires. --- Bounds the zone with tires.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number Points (optional) The amount of points in the circle. Default 360. -- @param #number Points (optional) The amount of points in the circle. Default 360.
@@ -781,6 +921,32 @@ function ZONE_RADIUS:GetScannedSetUnit()
return SetUnit return SetUnit
end end
--- Get a set of scanned units.
-- @param #ZONE_RADIUS self
-- @return Core.Set#SET_GROUP Set of groups.
function ZONE_RADIUS:GetScannedSetGroup()
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
self.ScanSetGroup.Set={}
if self.ScanData then
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
local UnitObject = UnitObject -- DCS#Unit
if UnitObject:isExist() then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group)
end
end
end
end
return self.ScanSetGroup
end
--- Count the number of different coalitions inside the zone. --- Count the number of different coalitions inside the zone.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
@@ -1116,22 +1282,37 @@ ZONE = {
} }
--- Constructor of ZONE, taking the zone name. --- Constructor of ZONE taking the zone name.
-- @param #ZONE self -- @param #ZONE self
-- @param #string ZoneName The name of the zone as defined within the mission editor. -- @param #string ZoneName The name of the zone as defined within the mission editor.
-- @return #ZONE -- @return #ZONE self
function ZONE:New( ZoneName ) function ZONE:New( ZoneName )
-- First try to find the zone in the DB.
local zone=_DATABASE:FindZone(ZoneName)
if zone then
--env.info("FF found zone in DB")
return zone
end
-- Get zone from DCS trigger function.
local Zone = trigger.misc.getZone( ZoneName ) local Zone = trigger.misc.getZone( ZoneName )
-- Error!
if not Zone then if not Zone then
error( "Zone " .. ZoneName .. " does not exist." ) error( "Zone " .. ZoneName .. " does not exist." )
return nil return nil
end end
-- Create a new ZONE_RADIUS.
local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius))
self:F(ZoneName) self:F(ZoneName)
-- Color of zone.
self.Color={1, 0, 0, 0.15}
-- DCS zone.
self.Zone = Zone self.Zone = Zone
return self return self
@@ -1392,26 +1573,38 @@ end
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone.
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone.
-- --
-- ## Draw zone
--
-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map.
-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles.
--
--
-- @field #ZONE_POLYGON_BASE -- @field #ZONE_POLYGON_BASE
ZONE_POLYGON_BASE = { ZONE_POLYGON_BASE = {
ClassName="ZONE_POLYGON_BASE", ClassName="ZONE_POLYGON_BASE",
} }
--- A points array. --- A 2D points array.
-- @type ZONE_POLYGON_BASE.ListVec2 -- @type ZONE_POLYGON_BASE.ListVec2
-- @list <DCS#Vec2> -- @list <DCS#Vec2> Table of 2D vectors.
--- A 3D points array.
-- @type ZONE_POLYGON_BASE.ListVec3
-- @list <DCS#Vec3> Table of 3D vectors.
--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. --- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon.
-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName Name of the zone. -- @param #string ZoneName Name of the zone.
-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. -- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.
-- @return #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
-- Inherit ZONE_BASE.
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) )
self:F( { ZoneName, PointsArray } ) self:F( { ZoneName, PointsArray } )
local i = 0 if PointsArray then
self._.Polygon = {} self._.Polygon = {}
@@ -1421,11 +1614,47 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
self._.Polygon[i].y = PointsArray[i].y self._.Polygon[i].y = PointsArray[i].y
end end
end
return self
end
--- Update polygon points with an array of @{DCS#Vec2}.
-- @param #ZONE_POLYGON_BASE self
-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
self._.Polygon = {}
for i=1,#Vec2Array do
self._.Polygon[i] = {}
self._.Polygon[i].x=Vec2Array[i].x
self._.Polygon[i].y=Vec2Array[i].y
end
return self
end
--- Update polygon points with an array of @{DCS#Vec3}.
-- @param #ZONE_POLYGON_BASE self
-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
self._.Polygon = {}
for i=1,#Vec3Array do
self._.Polygon[i] = {}
self._.Polygon[i].x=Vec3Array[i].x
self._.Polygon[i].y=Vec3Array[i].z
end
return self return self
end end
--- Returns the center location of the polygon. --- Returns the center location of the polygon.
-- @param #ZONE_GROUP self -- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
function ZONE_POLYGON_BASE:GetVec2() function ZONE_POLYGON_BASE:GetVec2()
self:F( self.ZoneName ) self:F( self.ZoneName )
@@ -1435,6 +1664,78 @@ function ZONE_POLYGON_BASE:GetVec2()
return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 }
end end
--- Get a vertex of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Index Index of the vertex. Default 1.
-- @return DCS#Vec2 Vertex of the polygon.
function ZONE_POLYGON_BASE:GetVertexVec2(Index)
return self._.Polygon[Index or 1]
end
--- Get a vertex of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Index Index of the vertex. Default 1.
-- @return DCS#Vec3 Vertex of the polygon.
function ZONE_POLYGON_BASE:GetVertexVec3(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y}
return vec3
end
return nil
end
--- Get a vertex of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Index Index of the vertex. Default 1.
-- @return Core.Point#COORDINATE Vertex of the polygon.
function ZONE_POLYGON_BASE:GetVertexCoordinate(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local coord=COORDINATE:NewFromVec2(vec2)
return coord
end
return nil
end
--- Get a list of verticies of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return <DCS#Vec2> List of DCS#Vec2 verticies defining the edges of the polygon.
function ZONE_POLYGON_BASE:GetVerticiesVec2()
return self._.Polygon
end
--- Get a list of verticies of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return #table List of DCS#Vec3 verticies defining the edges of the polygon.
function ZONE_POLYGON_BASE:GetVerticiesVec3()
local coords={}
for i,vec2 in ipairs(self._.Polygon) do
local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y}
table.insert(coords, vec3)
end
return coords
end
--- Get a list of verticies of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return #table List of COORDINATES verticies defining the edges of the polygon.
function ZONE_POLYGON_BASE:GetVerticiesCoordinates()
local coords={}
for i,vec2 in ipairs(self._.Polygon) do
local coord=COORDINATE:NewFromVec2(vec2)
table.insert(coords, coord)
end
return coords
end
--- Flush polygon coordinates as a table in DCS.log. --- Flush polygon coordinates as a table in DCS.log.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @return #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self
@@ -1494,6 +1795,46 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
end end
--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported!
-- @param #ZONE_POLYGON_BASE self
-- @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.
-- @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.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or Color
FillAlpha=FillAlpha or self:GetColorAlpha()
if #self._.Polygon==4 then
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
else
local Coordinates=self:GetVerticiesCoordinates()
table.remove(Coordinates, 1)
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
end
return self
end
--- Smokes the zone boundaries in a color. --- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
@@ -1685,6 +2026,45 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
end end
--- Draw a frontier on the F10 map with small filled circles.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White.
-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000.
-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed)
Coalition = Coalition or -1
Color = Color or {1, 1, 1}
Radius = Radius or 1000
Alpha = Alpha or 1
Segments = Segments or 10
Closed = Closed or false
local i = 1
local j = #self._.Polygon
if (Closed) then
Limit = #self._.Polygon + 1
else
Limit = #self._.Polygon
end
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
if j ~= Limit then
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true)
end
end
j = i
i = i + 1
end
return self
end
--- @type ZONE_POLYGON --- @type ZONE_POLYGON
-- @extends #ZONE_POLYGON_BASE -- @extends #ZONE_POLYGON_BASE
@@ -1711,7 +2091,7 @@ end
-- This is especially handy if you want to quickly setup a SET_ZONE... -- This is especially handy if you want to quickly setup a SET_ZONE...
-- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`,
-- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection,
-- without much scripting overhead!!! -- without much scripting overhead!
-- --
-- @field #ZONE_POLYGON -- @field #ZONE_POLYGON
ZONE_POLYGON = { ZONE_POLYGON = {

View File

@@ -295,6 +295,17 @@ do -- country
-- @field QATAR -- @field QATAR
-- @field OMAN -- @field OMAN
-- @field UNITED_ARAB_EMIRATES -- @field UNITED_ARAB_EMIRATES
-- @field SOUTH_AFRICA
-- @field CUBA
-- @field PORTUGAL
-- @field GDR
-- @field LEBANON
-- @field CJTF_BLUE
-- @field CJTF_RED
-- @field UN_PEACEKEEPERS
-- @field Argentinia
-- @field Cyprus
-- @field Slovenia
country = {} --#country country = {} --#country

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. --- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FOX self -- @param #FOX self
-- @param DCS#Weapon weapon The weapon. -- @param DCS#Weapon weapon The weapon.
-- @return #number Notching heading right, i.e. missile heading +90<EFBFBD> -- @return #number Notching heading right, i.e. missile heading +90°.
-- @return #number Notching heading left, i.e. missile heading -90<EFBFBD>. -- @return #number Notching heading left, i.e. missile heading -90°.
function FOX:_GetNotchingHeadings(weapon) function FOX:_GetNotchingHeadings(weapon)
if weapon then if weapon then

View File

@@ -20,7 +20,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- Date: Apr 2021 -- Date: July 2021
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends #Core.Base#BASE --- **MANTIS** class, extends #Core.Base#BASE
@@ -191,7 +191,18 @@ MANTIS = {
ShoradLink = false, ShoradLink = false,
ShoradTime = 600, ShoradTime = 600,
ShoradActDistance = 15000, ShoradActDistance = 15000,
UseEmOnOff = true, UseEmOnOff = false,
TimeStamp = 0,
state2flag = false,
SamStateTracker = {},
}
--- Advanced state enumerator
-- @type MANTIS.AdvancedState
MANTIS.AdvancedState = {
GREEN = 0,
AMBER = 1,
RED = 2,
} }
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -208,7 +219,7 @@ do
--@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral"
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
--@param #string awacs Group name of your Awacs (optional) --@param #string awacs Group name of your Awacs (optional)
--@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN
--@return #MANTIS self --@return #MANTIS self
--@usage Start up your MANTIS with a basic setting --@usage Start up your MANTIS with a basic setting
-- --
@@ -263,10 +274,16 @@ do
self.ShoradLink = false self.ShoradLink = false
self.ShoradTime = 600 self.ShoradTime = 600
self.ShoradActDistance = 15000 self.ShoradActDistance = 15000
-- TODO: add emissions on/off when available .... in 2 weeks self.TimeStamp = timer.getAbsTime()
self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins
self.state2flag = false
self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode
if EmOnOff then if EmOnOff then
if EmOnOff == false then if EmOnOff == false then
self.UseEmOnOff = false self.UseEmOnOff = false
else
self.UseEmOnOff = true
end end
end end
@@ -277,7 +294,7 @@ do
end end
-- Inherit everything from BASE class. -- Inherit everything from BASE class.
local self = BASE:Inherit(self, BASE:New()) -- #MANTIS local self = BASE:Inherit(self, FSM:New()) -- #MANTIS
-- Set the string id for output to DCS.log file. -- Set the string id for output to DCS.log file.
self.lid=string.format("MANTIS %s | ", self.name) self.lid=string.format("MANTIS %s | ", self.name)
@@ -308,9 +325,102 @@ do
end end
-- @field #string version -- @field #string version
self.version="0.4.1" self.version="0.5.2"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions ---
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
self:AddTransition("*", "Status", "*") -- MANTIS status update.
self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating.
self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state.
self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state.
self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change.
self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers.
-- @function [parent=#MANTIS] Start
-- @param #MANTIS self
--- Triggers the FSM event "Start" after a delay. Starts the MANTIS. Initializes parameters and starts event handlers.
-- @function [parent=#MANTIS] __Start
-- @param #MANTIS self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the MANTIS and all its event handlers.
-- @param #MANTIS self
--- Triggers the FSM event "Stop" after a delay. Stops the MANTIS and all its event handlers.
-- @function [parent=#MANTIS] __Stop
-- @param #MANTIS self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status".
-- @function [parent=#MANTIS] Status
-- @param #MANTIS self
--- Triggers the FSM event "Status" after a delay.
-- @function [parent=#MANTIS] __Status
-- @param #MANTIS self
-- @param #number delay Delay in seconds.
--- On After "Relocating" event. HQ and/or EWR moved.
-- @function [parent=#MANTIS] OnAfterRelocating
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @return #MANTIS self
--- On After "GreenState" event. A SAM group was switched to GREEN alert.
-- @function [parent=#MANTIS] OnAfterGreenState
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
-- @return #MANTIS self
--- On After "RedState" event. A SAM group was switched to RED alert.
-- @function [parent=#MANTIS] OnAfterRedState
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
-- @return #MANTIS self
--- On After "AdvStateChange" event. Advanced state changed, influencing detection speed.
-- @function [parent=#MANTIS] OnAfterAdvStateChange
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red
-- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red
-- @param #number Interval Calculated detection interval based on state and advanced feature setting
-- @return #MANTIS self
--- On After "ShoradActivated" event. Mantis has activated a SHORAD.
-- @function [parent=#MANTIS] OnAfterShoradActivated
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param #string Name Name of the GROUP which SHORAD shall protect
-- @param #number Radius Radius around the named group to find SHORAD groups
-- @param #number Ontime Seconds the SHORAD will stay active
return self return self
end end
@@ -318,17 +428,19 @@ do
-- MANTIS helper functions -- MANTIS helper functions
----------------------------------------------------------------------- -----------------------------------------------------------------------
--- [internal] Function to get the self.SAM_Table --- [Internal] Function to get the self.SAM_Table
-- @param #MANTIS self -- @param #MANTIS self
-- @return #table table -- @return #table table
function MANTIS:_GetSAMTable() function MANTIS:_GetSAMTable()
self:T(self.lid .. "GetSAMTable")
return self.SAM_Table return self.SAM_Table
end end
--- [internal] Function to set the self.SAM_Table --- [Internal] Function to set the self.SAM_Table
-- @param #MANTIS self -- @param #MANTIS self
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:_SetSAMTable(table) function MANTIS:_SetSAMTable(table)
self:T(self.lid .. "SetSAMTable")
self.SAM_Table = table self.SAM_Table = table
return self return self
end end
@@ -337,41 +449,50 @@ do
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number radius Radius upon which detected objects will be grouped -- @param #number radius Radius upon which detected objects will be grouped
function MANTIS:SetEWRGrouping(radius) function MANTIS:SetEWRGrouping(radius)
self:T(self.lid .. "SetEWRGrouping")
local radius = radius or 5000 local radius = radius or 5000
self.grouping = radius self.grouping = radius
return self
end end
--- Function to set the detection radius of the EWR in meters --- Function to set the detection radius of the EWR in meters
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number radius Radius of the EWR detection zone -- @param #number radius Radius of the EWR detection zone
function MANTIS:SetEWRRange(radius) function MANTIS:SetEWRRange(radius)
self:T(self.lid .. "SetEWRRange")
local radius = radius or 80000 local radius = radius or 80000
self.acceptrange = radius self.acceptrange = radius
return self
end end
--- Function to set switch-on/off zone for the SAM sites in meters --- Function to set switch-on/off zone for the SAM sites in meters
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number radius Radius of the firing zone -- @param #number radius Radius of the firing zone
function MANTIS:SetSAMRadius(radius) function MANTIS:SetSAMRadius(radius)
self:T(self.lid .. "SetSAMRadius")
local radius = radius or 25000 local radius = radius or 25000
self.checkradius = radius self.checkradius = radius
return self
end end
--- Function to set SAM firing engage range, 0-100 percent, e.g. 75 --- Function to set SAM firing engage range, 0-100 percent, e.g. 75
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number range Percent of the max fire range -- @param #number range Percent of the max fire range
function MANTIS:SetSAMRange(range) function MANTIS:SetSAMRange(range)
self:T(self.lid .. "SetSAMRange")
local range = range or 75 local range = range or 75
if range < 0 or range > 100 then if range < 0 or range > 100 then
range = 75 range = 75
end end
self.engagerange = range self.engagerange = range
return self
end end
--- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number range Percent of the max fire range -- @param #number range Percent of the max fire range
function MANTIS:SetNewSAMRangeWhileRunning(range) function MANTIS:SetNewSAMRangeWhileRunning(range)
self:T(self.lid .. "SetNewSAMRangeWhileRunning")
local range = range or 75 local range = range or 75
if range < 0 or range > 100 then if range < 0 or range > 100 then
range = 75 range = 75
@@ -379,20 +500,32 @@ do
self.engagerange = range self.engagerange = range
self:_RefreshSAMTable() self:_RefreshSAMTable()
self.mysead.EngagementRange = range self.mysead.EngagementRange = range
return self
end end
--- Function to set switch-on/off the debug state --- Function to set switch-on/off the debug state
-- @param #MANTIS self -- @param #MANTIS self
-- @param #boolean onoff Set true to switch on -- @param #boolean onoff Set true to switch on
function MANTIS:Debug(onoff) function MANTIS:Debug(onoff)
self:T(self.lid .. "SetDebug")
local onoff = onoff or false local onoff = onoff or false
self.debug = onoff self.debug = onoff
if onoff then
-- Debug trace.
BASE:TraceOn()
BASE:TraceClass("MANTIS")
BASE:TraceLevel(1)
else
BASE:TraceOff()
end
return self
end end
--- Function to get the HQ object for further use --- Function to get the HQ object for further use
-- @param #MANTIS self -- @param #MANTIS self
-- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist
function MANTIS:GetCommandCenter() function MANTIS:GetCommandCenter()
self:T(self.lid .. "GetCommandCenter")
if self.HQ_CC then if self.HQ_CC then
return self.HQ_CC return self.HQ_CC
else else
@@ -404,26 +537,31 @@ do
-- @param #MANTIS self -- @param #MANTIS self
-- @param #string prefix Name of the AWACS group in the mission editor -- @param #string prefix Name of the AWACS group in the mission editor
function MANTIS:SetAwacs(prefix) function MANTIS:SetAwacs(prefix)
self:T(self.lid .. "SetAwacs")
if prefix ~= nil then if prefix ~= nil then
if type(prefix) == "string" then if type(prefix) == "string" then
self.AWACS_Prefix = prefix self.AWACS_Prefix = prefix
self.advAwacs = true self.advAwacs = true
end end
end end
return self
end end
--- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis!
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number range Detection range of the AWACS group -- @param #number range Detection range of the AWACS group
function MANTIS:SetAwacsRange(range) function MANTIS:SetAwacsRange(range)
self:T(self.lid .. "SetAwacsRange")
local range = range or 250000 local range = range or 250000
self.awacsrange = range self.awacsrange = range
return self
end end
--- Function to set the HQ object for further use --- Function to set the HQ object for further use
-- @param #MANTIS self -- @param #MANTIS self
-- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ
function MANTIS:SetCommandCenter(group) function MANTIS:SetCommandCenter(group)
self:T(self.lid .. "SetCommandCenter")
local group = group or nil local group = group or nil
if group ~= nil then if group ~= nil then
if type(group) == "string" then if type(group) == "string" then
@@ -434,14 +572,17 @@ do
self.HQ_Template_CC = group:GetName() self.HQ_Template_CC = group:GetName()
end end
end end
return self
end end
--- Function to set the detection interval --- Function to set the detection interval
-- @param #MANTIS self -- @param #MANTIS self
-- @param #number interval The interval in seconds -- @param #number interval The interval in seconds
function MANTIS:SetDetectInterval(interval) function MANTIS:SetDetectInterval(interval)
self:T(self.lid .. "SetDetectInterval")
local interval = interval or 30 local interval = interval or 30
self.detectinterval = interval self.detectinterval = interval
return self
end end
--- Function to set Advanded Mode --- Function to set Advanded Mode
@@ -451,7 +592,8 @@ do
-- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option.
-- E.g. `mymantis:SetAdvancedMode(true, 90)` -- E.g. `mymantis:SetAdvancedMode(true, 90)`
function MANTIS:SetAdvancedMode(onoff, ratio) function MANTIS:SetAdvancedMode(onoff, ratio)
self:F({onoff, ratio}) self:T(self.lid .. "SetAdvancedMode")
self:T({onoff, ratio})
local onoff = onoff or false local onoff = onoff or false
local ratio = ratio or 100 local ratio = ratio or 100
if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then
@@ -459,53 +601,58 @@ do
self.advanced = true self.advanced = true
self.adv_state = 0 self.adv_state = 0
self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
env.info(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version))
else else
local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both."
local m= MESSAGE:New(text,10,"MANTIS",true):ToAll() local m= MESSAGE:New(text,10,"MANTIS",true):ToAll()
BASE:E(text) self:E(text)
end end
return self
end end
--- Set using Emissions on/off instead of changing alarm state --- Set using Emissions on/off instead of changing alarm state
-- @param #MANTIS self -- @param #MANTIS self
-- @param #boolean switch Decide if we are changing alarm state or Emission state -- @param #boolean switch Decide if we are changing alarm state or Emission state
function MANTIS:SetUsingEmOnOff(switch) function MANTIS:SetUsingEmOnOff(switch)
self:T(self.lid .. "SetUsingEmOnOff")
self.UseEmOnOff = switch or false self.UseEmOnOff = switch or false
return self
end end
--- [Internal] Function to check if HQ is alive --- [Internal] Function to check if HQ is alive
-- @param #MANTIS self -- @param #MANTIS self
-- @return #boolean True if HQ is alive, else false -- @return #boolean True if HQ is alive, else false
function MANTIS:_CheckHQState() function MANTIS:_CheckHQState()
self:T(self.lid .. "CheckHQState")
local text = self.lid.." Checking HQ State" local text = self.lid.." Checking HQ State"
self:T(text)
local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
-- start check -- start check
if self.advanced then if self.advanced then
local hq = self.HQ_Template_CC local hq = self.HQ_Template_CC
local hqgrp = GROUP:FindByName(hq) local hqgrp = GROUP:FindByName(hq)
if hqgrp then if hqgrp then
if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive
env.info(self.lid.." HQ is alive!") self:T(self.lid.." HQ is alive!")
return true return true
else else
env.info(self.lid.." HQ is dead!") self:T(self.lid.." HQ is dead!")
return false return false
end end
end end
end end
return self
end end
--- [Internal] Function to check if EWR is (at least partially) alive --- [Internal] Function to check if EWR is (at least partially) alive
-- @param #MANTIS self -- @param #MANTIS self
-- @return #boolean True if EWR is alive, else false -- @return #boolean True if EWR is alive, else false
function MANTIS:_CheckEWRState() function MANTIS:_CheckEWRState()
self:T(self.lid .. "CheckEWRState")
local text = self.lid.." Checking EWR State" local text = self.lid.." Checking EWR State"
self:F(text) self:T(text)
local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
-- start check -- start check
if self.advanced then if self.advanced then
local EWR_Group = self.Adv_EWR_Group local EWR_Group = self.Adv_EWR_Group
@@ -519,24 +666,26 @@ do
end end
end end
end end
env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) self:T(self.lid..string.format(" No of EWR alive is %d", nalive))
if nalive > 0 then if nalive > 0 then
return true return true
else else
return false return false
end end
end end
return self
end end
--- [Internal] Function to determine state of the advanced mode --- [Internal] Function to determine state of the advanced mode
-- @param #MANTIS self -- @param #MANTIS self
-- @return #number Newly calculated interval -- @return #number Newly calculated interval
-- @return #number Previous state for tracking 0, 1, or 2 -- @return #number Previous state for tracking 0, 1, or 2
function MANTIS:_CheckAdvState() function MANTIS:_CalcAdvState()
local text = self.lid.." Checking Advanced State" self:T(self.lid .. "CalcAdvState")
self:F(text) local text = self.lid.." Calculating Advanced State"
self:T(text)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
-- start check -- start check
local currstate = self.adv_state -- save curr state for comparison later local currstate = self.adv_state -- save curr state for comparison later
local EWR_State = self:_CheckEWRState() local EWR_State = self:_CheckEWRState()
@@ -555,9 +704,9 @@ do
ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6
local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78
local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval)
self:F(text) self:T(text)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
return newinterval, currstate return newinterval, currstate
end end
@@ -566,7 +715,8 @@ do
-- @param #boolean hq If true, will relocate HQ object -- @param #boolean hq If true, will relocate HQ object
-- @param #boolean ewr If true, will relocate EWR objects -- @param #boolean ewr If true, will relocate EWR objects
function MANTIS:SetAutoRelocate(hq, ewr) function MANTIS:SetAutoRelocate(hq, ewr)
self:F({hq, ewr}) self:T(self.lid .. "SetAutoRelocate")
self:T({hq, ewr})
local hqrel = hq or false local hqrel = hq or false
local ewrel = ewr or false local ewrel = ewr or false
if hqrel or ewrel then if hqrel or ewrel then
@@ -574,22 +724,24 @@ do
self.autorelocateunits = { HQ = hqrel, EWR = ewrel } self.autorelocateunits = { HQ = hqrel, EWR = ewrel }
self:T({self.autorelocate, self.autorelocateunits}) self:T({self.autorelocate, self.autorelocateunits})
end end
return self
end end
--- [Internal] Function to execute the relocation --- [Internal] Function to execute the relocation
-- @param #MANTIS self -- @param #MANTIS self
function MANTIS:_RelocateGroups() function MANTIS:_RelocateGroups()
self:T(self.lid.." Relocating Groups") self:T(self.lid .. "RelocateGroups")
local text = self.lid.." Relocating Groups" local text = self.lid.." Relocating Groups"
local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
if self.autorelocate then if self.autorelocate then
-- relocate HQ -- relocate HQ
if self.autorelocateunits.HQ and self.HQ_CC then --only relocate if HQ exists local HQGroup = self.HQ_CC
if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists
local _hqgrp = self.HQ_CC local _hqgrp = self.HQ_CC
self:T(self.lid.." Relocating HQ") self:T(self.lid.." Relocating HQ")
local text = self.lid.." Relocating HQ" local text = self.lid.." Relocating HQ"
local m= MESSAGE:New(text,10,"MANTIS"):ToAll() --local m= MESSAGE:New(text,10,"MANTIS"):ToAll()
_hqgrp:RelocateGroundRandomInRadius(20,500,true,true) _hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
end end
--relocate EWR --relocate EWR
@@ -599,26 +751,27 @@ do
local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP
for _,_grp in pairs (EWR_Grps) do for _,_grp in pairs (EWR_Grps) do
if _grp:IsGround() then if _grp:IsAlive() and _grp:IsGround() then
self:T(self.lid.." Relocating EWR ".._grp:GetName()) self:T(self.lid.." Relocating EWR ".._grp:GetName())
local text = self.lid.." Relocating EWR ".._grp:GetName() local text = self.lid.." Relocating EWR ".._grp:GetName()
local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end if self.verbose then self:I(text) end
_grp:RelocateGroundRandomInRadius(20,500,true,true) _grp:RelocateGroundRandomInRadius(20,500,true,true)
end end
end end
end end
end end
return self
end end
--- (Internal) Function to check if any object is in the given SAM zone --- [Internal] Function to check if any object is in the given SAM zone
-- @param #MANTIS self -- @param #MANTIS self
-- @param #table dectset Table of coordinates of detected items -- @param #table dectset Table of coordinates of detected items
-- @param samcoordinate Core.Point#COORDINATE Coordinate object. -- @param Core.Point#COORDINATE samcoordinate Coordinate object.
-- @return #boolean True if in any zone, else false -- @return #boolean True if in any zone, else false
-- @return #number Distance Target distance in meters or zero when no object is in zone -- @return #number Distance Target distance in meters or zero when no object is in zone
function MANTIS:CheckObjectInZone(dectset, samcoordinate) function MANTIS:CheckObjectInZone(dectset, samcoordinate)
self:F(self.lid.."CheckObjectInZone Called") self:T(self.lid.."CheckObjectInZone")
-- check if non of the coordinate is in the given defense zone -- check if non of the coordinate is in the given defense zone
local radius = self.checkradius local radius = self.checkradius
local set = dectset local set = dectset
@@ -630,7 +783,7 @@ do
local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local targetdistance = samcoordinate:DistanceFromPointVec2(coord)
local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring)
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end if self.verbose then self:I(self.lid..text) end
-- end output to cross-check -- end output to cross-check
if targetdistance <= radius then if targetdistance <= radius then
return true, targetdistance return true, targetdistance
@@ -639,11 +792,11 @@ do
return false, 0 return false, 0
end end
--- (Internal) Function to start the detection via EWR groups --- [Internal] Function to start the detection via EWR groups
-- @param #MANTIS self -- @param #MANTIS self
-- @return Functional.Detection #DETECTION_AREAS The running detection set -- @return Functional.Detection #DETECTION_AREAS The running detection set
function MANTIS:StartDetection() function MANTIS:StartDetection()
self:F(self.lid.."Starting Detection") self:T(self.lid.."Starting Detection")
-- start detection -- start detection
local groupset = self.EWR_Group local groupset = self.EWR_Group
@@ -651,14 +804,14 @@ do
local acceptrange = self.acceptrange or 80000 local acceptrange = self.acceptrange or 80000
local interval = self.detectinterval or 60 local interval = self.detectinterval or 60
--@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object
_MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones
_MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER })
_MANTISdetection:SetAcceptRange(acceptrange) MANTISdetection:SetAcceptRange(acceptrange)
_MANTISdetection:SetRefreshTimeInterval(interval) MANTISdetection:SetRefreshTimeInterval(interval)
_MANTISdetection:Start() MANTISdetection:Start()
function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem)
--BASE:I( { From, Event, To, DetectedItem }) --BASE:I( { From, Event, To, DetectedItem })
local debug = false local debug = false
if DetectedItem.IsDetected and debug then if DetectedItem.IsDetected and debug then
@@ -667,14 +820,14 @@ do
local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
end end
end end
return _MANTISdetection return MANTISdetection
end end
--- (Internal) Function to start the detection via AWACS if defined as separate --- [Internal] Function to start the detection via AWACS if defined as separate
-- @param #MANTIS self -- @param #MANTIS self
-- @return Functional.Detection #DETECTION_AREAS The running detection set -- @return Functional.Detection #DETECTION_AREAS The running detection set
function MANTIS:StartAwacsDetection() function MANTIS:StartAwacsDetection()
self:F(self.lid.."Starting Awacs Detection") self:T(self.lid.."Starting Awacs Detection")
-- start detection -- start detection
local group = self.AWACS_Prefix local group = self.AWACS_Prefix
@@ -683,14 +836,14 @@ do
--local acceptrange = self.acceptrange or 80000 --local acceptrange = self.acceptrange or 80000
local interval = self.detectinterval or 60 local interval = self.detectinterval or 60
--@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object
_MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones
_MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER })
_MANTISAwacs:SetAcceptRange(self.awacsrange) --250km MANTISAwacs:SetAcceptRange(self.awacsrange) --250km
_MANTISAwacs:SetRefreshTimeInterval(interval) MANTISAwacs:SetRefreshTimeInterval(interval)
_MANTISAwacs:Start() MANTISAwacs:Start()
function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem)
--BASE:I( { From, Event, To, DetectedItem }) --BASE:I( { From, Event, To, DetectedItem })
local debug = false local debug = false
if DetectedItem.IsDetected and debug then if DetectedItem.IsDetected and debug then
@@ -699,15 +852,15 @@ do
local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
end end
end end
return _MANTISAwacs return MANTISAwacs
end end
--- (Internal) Function to set the SAM start state --- [Internal] Function to set the SAM start state
-- @param #MANTIS self -- @param #MANTIS self
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:SetSAMStartState() function MANTIS:SetSAMStartState()
-- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406
self:F(self.lid.."Setting SAM Start States") self:T(self.lid.."Setting SAM Start States")
-- get SAM Group -- get SAM Group
local SAM_SET = self.SAM_Group local SAM_SET = self.SAM_Group
local SAM_Grps = SAM_SET.Set --table of objects local SAM_Grps = SAM_SET.Set --table of objects
@@ -716,7 +869,7 @@ do
local engagerange = self.engagerange -- firing range in % of max local engagerange = self.engagerange -- firing range in % of max
--cycle through groups and set alarm state etc --cycle through groups and set alarm state etc
for _i,_group in pairs (SAM_Grps) do for _i,_group in pairs (SAM_Grps) do
local group = _group local group = _group -- Wrapper.Group#GROUP
-- TODO: add emissions on/off -- TODO: add emissions on/off
if self.UseEmOnOff then if self.UseEmOnOff then
group:EnableEmission(false) group:EnableEmission(false)
@@ -725,11 +878,12 @@ do
group:OptionAlarmStateGreen() -- AI off group:OptionAlarmStateGreen() -- AI off
end end
group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range
if group:IsGround() then if group:IsGround() and group:IsAlive() then
local grpname = group:GetName() local grpname = group:GetName()
local grpcoord = group:GetCoordinate() local grpcoord = group:GetCoordinate()
table.insert( SAM_Tbl, {grpname, grpcoord}) table.insert( SAM_Tbl, {grpname, grpcoord})
table.insert( SEAD_Grps, grpname ) table.insert( SEAD_Grps, grpname )
self.SamStateTracker[grpname] = "GREEN"
end end
end end
self.SAM_Table = SAM_Tbl self.SAM_Table = SAM_Tbl
@@ -740,11 +894,11 @@ do
return self return self
end end
--- (Internal) Function to update SAM table and SEAD state --- [Internal] Function to update SAM table and SEAD state
-- @param #MANTIS self -- @param #MANTIS self
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:_RefreshSAMTable() function MANTIS:_RefreshSAMTable()
self:F(self.lid.."Setting SAM Start States") self:T(self.lid.."RefreshSAMTable")
-- Requires SEAD 0.2.2 or better -- Requires SEAD 0.2.2 or better
-- get SAM Group -- get SAM Group
local SAM_SET = self.SAM_Group local SAM_SET = self.SAM_Group
@@ -756,7 +910,7 @@ do
for _i,_group in pairs (SAM_Grps) do for _i,_group in pairs (SAM_Grps) do
local group = _group local group = _group
group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range
if group:IsGround() then if group:IsGround() and group:IsAlive() then
local grpname = group:GetName() local grpname = group:GetName()
local grpcoord = group:GetCoordinate() local grpcoord = group:GetCoordinate()
table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here
@@ -777,6 +931,7 @@ do
-- @param Functional.Shorad#SHORAD Shorad The #SHORAD object -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object
-- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up
function MANTIS:AddShorad(Shorad,Shoradtime) function MANTIS:AddShorad(Shorad,Shoradtime)
self:T(self.lid.."AddShorad")
local Shorad = Shorad or nil local Shorad = Shorad or nil
local ShoradTime = Shoradtime or 600 local ShoradTime = Shoradtime or 600
local ShoradLink = true local ShoradLink = true
@@ -785,33 +940,30 @@ do
self.Shorad = Shorad --#SHORAD self.Shorad = Shorad --#SHORAD
self.ShoradTime = Shoradtime -- #number self.ShoradTime = Shoradtime -- #number
end end
return self
end end
--- Function to unlink #MANTIS from a #SHORAD installation --- Function to unlink #MANTIS from a #SHORAD installation
-- @param #MANTIS self -- @param #MANTIS self
function MANTIS:RemoveShorad() function MANTIS:RemoveShorad()
self:T(self.lid.."RemoveShorad")
self.ShoradLink = false self.ShoradLink = false
return self
end end
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- MANTIS main functions -- MANTIS main functions
----------------------------------------------------------------------- -----------------------------------------------------------------------
--- Function to set the SAM start state --- [Internal] Check detection function
-- @param #MANTIS self -- @param #MANTIS self
-- @param Functional.Detection#DETECTION_AREAS detection Detection object
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:Start() function MANTIS:_Check(detection)
self:F(self.lid.."Starting MANTIS") self:T(self.lid .. "Check")
self:SetSAMStartState()
self.Detection = self:StartDetection()
if self.advAwacs then
self.AWACS_Detection = self:StartAwacsDetection()
end
-- detection function
local function check(detection)
--get detected set --get detected set
local detset = detection:GetDetectedItemCoordinates() local detset = detection:GetDetectedItemCoordinates()
self:F("Check:", {detset}) self:T("Check:", {detset})
-- randomly update SAM Table -- randomly update SAM Table
local rand = math.random(1,100) local rand = math.random(1,100)
if rand > 65 then -- 1/3 of cases if rand > 65 then -- 1/3 of cases
@@ -833,6 +985,10 @@ do
samgroup:EnableEmission(true) samgroup:EnableEmission(true)
end end
samgroup:OptionAlarmStateRed() samgroup:OptionAlarmStateRed()
if self.SamStateTracker[name] ~= "RED" then
self:__RedState(1,samgroup)
self.SamStateTracker[name] = "RED"
end
-- link in to SHORAD if available -- link in to SHORAD if available
-- DONE: Test integration fully -- DONE: Test integration fully
if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early
@@ -840,51 +996,55 @@ do
local radius = self.checkradius local radius = self.checkradius
local ontime = self.ShoradTime local ontime = self.ShoradTime
Shorad:WakeUpShorad(name, radius, ontime) Shorad:WakeUpShorad(name, radius, ontime)
self:__ShoradActivated(1,name, radius, ontime)
end end
-- debug output -- debug output
local text = string.format("SAM %s switched to alarm state RED!", name) local text = string.format("SAM %s switched to alarm state RED!", name)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end if self.verbose then self:I(self.lid..text) end
end --end alive end --end alive
else else
if samgroup:IsAlive() then if samgroup:IsAlive() then
-- switch off SAM -- switch off SAM
if self.UseEmOnOff then if self.UseEmOnOff then
-- TODO: add emissions on/off
samgroup:EnableEmission(false) samgroup:EnableEmission(false)
--samgroup:SetAIOff()
else
samgroup:OptionAlarmStateGreen()
end end
--samgroup:OptionROEWeaponFree() samgroup:OptionAlarmStateGreen()
--samgroup:SetAIOn() if self.SamStateTracker[name] ~= "GREEN" then
self:__GreenState(1,samgroup)
self.SamStateTracker[name] = "GREEN"
end
local text = string.format("SAM %s switched to alarm state GREEN!", name) local text = string.format("SAM %s switched to alarm state GREEN!", name)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end if self.verbose then self:I(self.lid..text) end
end --end alive end --end alive
end --end check end --end check
end --for for loop end --for for loop
end --end function return self
-- relocation relay function
local function relocate()
self:_RelocateGroups()
end end
-- check advanced state
local function checkadvstate() --- [Internal] Relocation relay function
local interval, oldstate = self:_CheckAdvState() -- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_Relocate()
self:T(self.lid .. "Relocate")
self:_RelocateGroups()
return self
end
--- [Internal] Check advanced state
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_CheckAdvState()
self:T(self.lid .. "CheckAdvSate")
local interval, oldstate = self:_CalcAdvState()
local newstate = self.adv_state local newstate = self.adv_state
if newstate ~= oldstate then if newstate ~= oldstate then
-- deal with new state -- deal with new state
self:__AdvStateChange(1,oldstate,newstate,interval)
if newstate == 2 then if newstate == 2 then
-- switch alarm state RED -- switch alarm state RED
if self.MantisTimer.isrunning then self.state2flag = true
self.MantisTimer:Stop()
self.MantisTimer.isrunning = false
end -- stop Awacs timer
if self.MantisATimer.isrunning then
self.MantisATimer:Stop()
self.MantisATimer.isrunning = false
end -- stop timer
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates
for _,_data in pairs (samset) do for _,_data in pairs (samset) do
local name = _data[1] local name = _data[1]
@@ -900,69 +1060,159 @@ do
end -- end for loop end -- end for loop
elseif newstate <= 1 then elseif newstate <= 1 then
-- change MantisTimer to slow down or speed up -- change MantisTimer to slow down or speed up
if self.MantisTimer.isrunning then self.detectinterval = interval
self.MantisTimer:Stop() self.state2flag = false
self.MantisTimer.isrunning = false
end
if self.MantisATimer.isrunning then
self.MantisATimer:Stop()
self.MantisATimer.isrunning = false
end
self.MantisTimer = TIMER:New(check,self.Detection)
self.MantisTimer:Start(5,interval,nil)
self.MantisTimer.isrunning = true
if self.advAwacs then
self.MantisATimer = TIMER:New(check,self.AWACS_Detection)
self.MantisATimer:Start(15,interval,nil)
self.MantisATimer.isrunning = true
end
end end
end -- end newstate vs oldstate end -- end newstate vs oldstate
return self
end end
-- timers to run the system
local interval = self.detectinterval --- [Internal] Function to set start state
self.MantisTimer = TIMER:New(check,self.Detection) -- @param #MANTIS self
self.MantisTimer:Start(5,interval,nil) -- @param #string From The From State
self.MantisTimer.isrunning = true -- @param #string Event The Event
-- Awacs timer -- @param #string To The To State
-- @return #MANTIS self
function MANTIS:onafterStart(From, Event, To)
self:T({From, Event, To})
self:T(self.lid.."Starting MANTIS")
self:SetSAMStartState()
self.Detection = self:StartDetection()
if self.advAwacs then if self.advAwacs then
self.MantisATimer = TIMER:New(check,self.AWACS_Detection) self.AWACS_Detection = self:StartAwacsDetection()
self.MantisATimer:Start(15,interval,nil)
self.MantisATimer.isrunning = true
end end
-- timer to relocate HQ and EWR self:__Status(self.detectinterval)
return self
end
--- [Internal] Before status function for MANTIS
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @return #MANTIS self
function MANTIS:onbeforeStatus(From, Event, To)
self:T({From, Event, To})
-- check detection
if not self.state2flag then
self:_Check(self.Detection)
end
-- check Awacs
if self.advAwacs and not self.state2flag then
self:_Check(self.AWACS_Detection)
end
-- relocate HQ and EWR
if self.autorelocate then if self.autorelocate then
local relointerval = math.random(1800,3600) -- random between 30 and 60 mins local relointerval = self.relointerval
self.MantisReloTimer = TIMER:New(relocate) local thistime = timer.getAbsTime()
self.MantisReloTimer:Start(relointerval,relointerval,nil) local timepassed = thistime - self.TimeStamp
local halfintv = math.floor(timepassed / relointerval)
--self:T({timepassed=timepassed, halfintv=halfintv})
if halfintv >= 1 then
self.TimeStamp = timer.getAbsTime()
self:_Relocate()
self:__Relocating(1)
end end
end
-- timer for advanced state check -- timer for advanced state check
if self.advanced then if self.advanced then
self.MantisAdvTimer = TIMER:New(checkadvstate) self:_CheckAdvState()
self.MantisAdvTimer:Start(30,interval*5,nil)
end end
return self return self
end end
--- Function to stop MANTIS --- [Internal] Status function for MANTIS
-- @param #MANTIS self -- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:Stop() function MANTIS:onafterStatus(From,Event,To)
if self.MantisTimer.isrunning then self:T({From, Event, To})
self.MantisTimer:Stop() local interval = self.detectinterval * -1
end self:__Status(interval)
if self.MantisATimer.isrunning then
self.MantisATimer:Stop()
end
if self.autorelocate then
self.MantisReloTimer:Stop()
end
if self.advanced then
self.MantisAdvTimer:Stop()
end
return self return self
end end
--- [Internal] Function to stop MANTIS
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @return #MANTIS self
function MANTIS:onafterStop(From, Event, To)
self:T({From, Event, To})
return self
end
--- [Internal] Function triggered by Event Relocating
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @return #MANTIS self
function MANTIS:onafterRelocating(From, Event, To)
self:T({From, Event, To})
return self
end
--- [Internal] Function triggered by Event GreenState
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
-- @return #MANTIS self
function MANTIS:onafterGreenState(From, Event, To, Group)
self:T({From, Event, To, Group})
return self
end
--- [Internal] Function triggered by Event RedState
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
-- @return #MANTIS self
function MANTIS:onafterRedState(From, Event, To, Group)
self:T({From, Event, To, Group})
return self
end
--- [Internal] Function triggered by Event AdvStateChange
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red
-- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red
-- @param #number Interval Calculated detection interval based on state and advanced feature setting
-- @return #MANTIS self
function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval)
self:T({From, Event, To, Oldstate, Newstate, Interval})
return self
end
--- [Internal] Function triggered by Event ShoradActivated
-- @param #MANTIS self
-- @param #string From The From State
-- @param #string Event The Event
-- @param #string To The To State
-- @param #string Name Name of the GROUP which SHORAD shall protect
-- @param #number Radius Radius around the named group to find SHORAD groups
-- @param #number Ontime Seconds the SHORAD will stay active
function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime)
self:T({From, Event, To, Name, Radius, Ontime})
return self
end
end end
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- MANTIS end -- MANTIS end

View File

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

View File

@@ -5371,7 +5371,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
if spawnonground then 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 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())) 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. -- 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 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 -- ## 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 -- 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. -- 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 defaultsmokebomb If true, initialize player settings to smoke bomb.
-- @field #boolean autosave If true, automatically save results every X seconds. -- @field #boolean autosave If true, automatically save results every X seconds.
-- @field #number instructorfreq Frequency on which the range control transmitts. -- @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 #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 rangecontrolrelayname Name of relay unit.
-- @field #string instructorrelayname 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/". -- @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 -- Get elevation
local elevation=coord:GetLandHeight() local elevation=coord:GetLandHeight()
local eltxt=string.format("%d m", elevation) local eltxt=string.format("%d m", elevation)
if _settings:IsImperial() then if not _settings:IsMetric() then
elevation=UTILS.MetersToFeet(elevation) elevation=UTILS.MetersToFeet(elevation)
eltxt=string.format("%d ft", elevation) eltxt=string.format("%d ft", elevation)
end end

View File

@@ -17,7 +17,7 @@
-- --
-- ### Authors: **FlightControl**, **applevangelist** -- ### Authors: **FlightControl**, **applevangelist**
-- --
-- Last Update: April 2021 -- Last Update: July 2021
-- --
-- === -- ===
-- --
@@ -51,26 +51,9 @@ SEAD = {
EngagementRange = 75 -- default 75% engagement range Feature Request #1355 EngagementRange = 75 -- default 75% engagement range Feature Request #1355
} }
-- TODO Complete list?
--- Missile enumerators --- Missile enumerators
-- @field Harms -- @field Harms
SEAD.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_88"] = "AGM_88",
["AGM_45"] = "AGM_45", ["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122", ["AGM_122"] = "AGM_122",
@@ -108,8 +91,8 @@ function SEAD:New( SEADGroupPrefixes )
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end end
self:HandleEvent( EVENTS.Shot ) self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
self:I("*** SEAD - Started Version 0.2.7") self:I("*** SEAD - Started Version 0.2.9")
return self return self
end end
@@ -119,7 +102,7 @@ end
-- @return #SEAD self -- @return #SEAD self
function SEAD:UpdateSet( SEADGroupPrefixes ) function SEAD:UpdateSet( SEADGroupPrefixes )
self:F( SEADGroupPrefixes ) self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
@@ -137,7 +120,7 @@ end
-- @param #number range Set the engagement range in percent, e.g. 50 -- @param #number range Set the engagement range in percent, e.g. 50
-- @return self -- @return self
function SEAD:SetEngagementRange(range) function SEAD:SetEngagementRange(range)
self:F( { range } ) self:T( { range } )
range = range or 75 range = range or 75
if range < 0 or range > 100 then if range < 0 or range > 100 then
range = 75 range = 75
@@ -152,7 +135,7 @@ end
-- @param #string WeaponName -- @param #string WeaponName
-- @return #boolean Returns true for a match -- @return #boolean Returns true for a match
function SEAD:_CheckHarms(WeaponName) function SEAD:_CheckHarms(WeaponName)
self:F( { WeaponName } ) self:T( { WeaponName } )
local hit = false local hit = false
for _,_name in pairs (SEAD.Harms) do 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 end
@@ -164,7 +147,7 @@ end
-- @see SEAD -- @see SEAD
-- @param #SEAD -- @param #SEAD
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SEAD:OnEventShot( EventData ) function SEAD:HandleEventShot( EventData )
self:T( { EventData } ) self:T( { EventData } )
local SEADUnit = EventData.IniDCSUnit local SEADUnit = EventData.IniDCSUnit
@@ -175,35 +158,6 @@ function SEAD:OnEventShot( EventData )
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
self:T({ SEADWeapon }) 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
--]]
if self:_CheckHarms(SEADWeaponName) then if self:_CheckHarms(SEADWeaponName) then
local _targetskill = "Random" local _targetskill = "Random"
local _targetMimgroupName = "none" local _targetMimgroupName = "none"
@@ -212,7 +166,7 @@ function SEAD:OnEventShot( EventData )
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
if _targetUnit and _targetUnit:IsAlive() then if _targetUnit and _targetUnit:IsAlive() then
local _targetMimgroup = _targetUnit:GetGroup() local _targetMimgroup = _targetUnit:GetGroup()
local _targetMimgroupName = _targetMimgroup:GetName() -- group name _targetMimgroupName = _targetMimgroup:GetName() -- group name
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
self:T( self.SEADGroupPrefixes ) self:T( self.SEADGroupPrefixes )
self:T( _targetMimgroupName ) self:T( _targetMimgroupName )
@@ -220,6 +174,7 @@ function SEAD:OnEventShot( EventData )
-- see if we are shot at -- see if we are shot at
local SEADGroupFound = false local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
self:T( SEADGroupPrefix )
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
SEADGroupFound = true SEADGroupFound = true
self:T( '*** SEAD - Group Found' ) self:T( '*** SEAD - Group Found' )

View File

@@ -18,7 +18,7 @@
-- @module Functional.Shorad -- @module Functional.Shorad
-- @image Functional.Shorad.jpg -- @image Functional.Shorad.jpg
-- --
-- Date: May 2021 -- Date: July 2021
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE --- **SHORAD** class, extends Core.Base#BASE
@@ -108,22 +108,6 @@ do
--- Missile enumerators --- Missile enumerators
-- @field Harms -- @field Harms
SHORAD.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_88"] = "AGM_88",
["AGM_45"] = "AGM_45", ["AGM_45"] = "AGM_45",
["AGM_122"] = "AGM_122", ["AGM_122"] = "AGM_122",
@@ -157,7 +141,9 @@ do
-- @param #number Radius Defense radius in meters, used to switch on groups -- @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 #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" -- @param #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() ) local self = BASE:Inherit( self, BASE:New() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
@@ -171,22 +157,23 @@ do
self.ActiveTimer = ActiveTimer or 600 self.ActiveTimer = ActiveTimer or 600
self.ActiveGroups = {} self.ActiveGroups = {}
self.Groupset = GroupSet self.Groupset = GroupSet
self:HandleEvent( EVENTS.Shot )
self.DefendHarms = true self.DefendHarms = true
self.DefendMavs = true self.DefendMavs = true
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
self.DefenseHighProb = 90 -- probability to detect a missile shot, high 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.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
self:I("*** SHORAD - Started Version 0.2.5") self:I("*** SHORAD - Started Version 0.2.8")
-- Set the string id for output to DCS.log file. -- Set the string id for output to DCS.log file.
self.lid=string.format("SHORAD %s | ", self.name) self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState() self:_InitState()
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
return self return self
end end
--- Initially set all groups to alarm state GREEN --- Initially set all groups to alarm state GREEN
-- @param #SHORAD self -- @param #SHORAD self
function SHORAD:_InitState() function SHORAD:_InitState()
self:T(self.lid .. " _InitState")
local table = {} local table = {}
local set = self.Groupset local set = self.Groupset
self:T({set = set}) self:T({set = set})
@@ -195,31 +182,48 @@ do
if self.UseEmOnOff then if self.UseEmOnOff then
--_group:SetAIOff() --_group:SetAIOff()
_group:EnableEmission(false) _group:EnableEmission(false)
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
else else
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
end end
_group:OptionDisperseOnAttack(30)
end end
-- gather entropy -- gather entropy
for i=1,10 do for i=1,100 do
math.random() math.random()
end end
return self
end end
--- Switch debug state --- Switch debug state on
-- @param #SHORAD self -- @param #SHORAD self
-- @param #boolean debug Switch debug on (true) or off (false) -- @param #boolean debug Switch debug on (true) or off (false)
function SHORAD:SwitchDebug(debug) function SHORAD:SwitchDebug(onoff)
self:T( { debug } ) self:T( { onoff } )
local onoff = debug or false if onoff then
if debug then self:SwitchDebugOn()
else
self.SwitchDebugOff()
end
return self
end
--- Switch debug state on
-- @param #SHORAD self
function SHORAD:SwitchDebugOn()
self.debug = true self.debug = true
--tracing --tracing
BASE:TraceOn() BASE:TraceOn()
BASE:TraceClass("SHORAD") BASE:TraceClass("SHORAD")
else return self
end
--- Switch debug state off
-- @param #SHORAD self
function SHORAD:SwitchDebugOff()
self.debug = false self.debug = false
BASE:TraceOff() BASE:TraceOff()
end return self
end end
--- Switch defense for HARMs --- Switch defense for HARMs
@@ -229,6 +233,7 @@ do
self:T( { onoff } ) self:T( { onoff } )
local onoff = onoff or true local onoff = onoff or true
self.DefendHarms = onoff self.DefendHarms = onoff
return self
end end
--- Switch defense for AGMs --- Switch defense for AGMs
@@ -238,6 +243,7 @@ do
self:T( { onoff } ) self:T( { onoff } )
local onoff = onoff or true local onoff = onoff or true
self.DefendMavs = onoff self.DefendMavs = onoff
return self
end end
--- Set defense probability limits --- Set defense probability limits
@@ -256,35 +262,42 @@ do
end end
self.DefenseLowProb = low self.DefenseLowProb = low
self.DefenseHighProb = high self.DefenseHighProb = high
return self
end end
--- Set the number of seconds a SHORAD site will stay active --- Set the number of seconds a SHORAD site will stay active
-- @param #SHORAD self -- @param #SHORAD self
-- @param #number seconds Number of seconds systems stay active -- @param #number seconds Number of seconds systems stay active
function SHORAD:SetActiveTimer(seconds) function SHORAD:SetActiveTimer(seconds)
self:T(self.lid .. " SetActiveTimer")
local timer = seconds or 600 local timer = seconds or 600
if timer < 0 then if timer < 0 then
timer = 600 timer = 600
end end
self.ActiveTimer = timer self.ActiveTimer = timer
return self
end end
--- Set the number of meters for the SHORAD defense zone --- Set the number of meters for the SHORAD defense zone
-- @param #SHORAD self -- @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 -- @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) function SHORAD:SetDefenseRadius(meters)
self:T(self.lid .. " SetDefenseRadius")
local radius = meters or 20000 local radius = meters or 20000
if radius < 0 then if radius < 0 then
radius = 20000 radius = 20000
end end
self.Radius = radius self.Radius = radius
return self
end end
--- Set using Emission on/off instead of changing alarm state --- Set using Emission on/off instead of changing alarm state
-- @param #SHORAD self -- @param #SHORAD self
-- @param #boolean switch Decide if we are changing alarm state or AI state -- @param #boolean switch Decide if we are changing alarm state or AI state
function SHORAD:SetUsingEmOnOff(switch) function SHORAD:SetUsingEmOnOff(switch)
self:T(self.lid .. " SetUsingEmOnOff")
self.UseEmOnOff = switch or false self.UseEmOnOff = switch or false
return self
end end
--- Check if a HARM was fired --- Check if a HARM was fired
@@ -292,6 +305,7 @@ do
-- @param #string WeaponName -- @param #string WeaponName
-- @return #boolean Returns true for a match -- @return #boolean Returns true for a match
function SHORAD:_CheckHarms(WeaponName) function SHORAD:_CheckHarms(WeaponName)
self:T(self.lid .. " _CheckHarms")
self:T( { WeaponName } ) self:T( { WeaponName } )
local hit = false local hit = false
if self.DefendHarms then if self.DefendHarms then
@@ -307,6 +321,7 @@ do
-- @param #string WeaponName -- @param #string WeaponName
-- @return #boolean Returns true for a match -- @return #boolean Returns true for a match
function SHORAD:_CheckMavs(WeaponName) function SHORAD:_CheckMavs(WeaponName)
self:T(self.lid .. " _CheckMavs")
self:T( { WeaponName } ) self:T( { WeaponName } )
local hit = false local hit = false
if self.DefendMavs then if self.DefendMavs then
@@ -322,6 +337,7 @@ do
-- @param #string Coalition name -- @param #string Coalition name
-- @return #boolean Returns false for a match -- @return #boolean Returns false for a match
function SHORAD:_CheckCoalition(Coalition) function SHORAD:_CheckCoalition(Coalition)
self:T(self.lid .. " _CheckCoalition")
local owncoalition = self.Coalition local owncoalition = self.Coalition
local othercoalition = "" local othercoalition = ""
if Coalition == 0 then if Coalition == 0 then
@@ -344,6 +360,7 @@ do
-- @param #string TargetGroupName Name of the target group -- @param #string TargetGroupName Name of the target group
-- @return #boolean Returns true for a match, else false -- @return #boolean Returns true for a match, else false
function SHORAD:_CheckShotAtShorad(TargetGroupName) function SHORAD:_CheckShotAtShorad(TargetGroupName)
self:T(self.lid .. " _CheckShotAtShorad")
local tgtgrp = TargetGroupName local tgtgrp = TargetGroupName
local shorad = self.Groupset local shorad = self.Groupset
local shoradset = shorad:GetAliveSet() --#table local shoradset = shorad:GetAliveSet() --#table
@@ -352,7 +369,7 @@ do
local groupname = _groups:GetName() local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then if string.find(groupname, tgtgrp, 1) then
returnname = true returnname = true
_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive --_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
end end
end end
return returnname return returnname
@@ -363,6 +380,7 @@ do
-- @param #string TargetGroupName Name of the target group -- @param #string TargetGroupName Name of the target group
-- @return #boolean Returns true for a match, else false -- @return #boolean Returns true for a match, else false
function SHORAD:_CheckShotAtSams(TargetGroupName) function SHORAD:_CheckShotAtSams(TargetGroupName)
self:T(self.lid .. " _CheckShotAtSams")
local tgtgrp = TargetGroupName local tgtgrp = TargetGroupName
local shorad = self.Samset local shorad = self.Samset
--local shoradset = shorad:GetAliveSet() --#table --local shoradset = shorad:GetAliveSet() --#table
@@ -381,6 +399,7 @@ do
-- @param #SHORAD self -- @param #SHORAD self
-- @return #boolean Returns true for a detection, else false -- @return #boolean Returns true for a detection, else false
function SHORAD:_ShotIsDetected() function SHORAD:_ShotIsDetected()
self:T(self.lid .. " _ShotIsDetected")
local IsDetected = false local IsDetected = false
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
local ActualDetection = math.random(1,100) -- value for this shot local ActualDetection = math.random(1,100) -- value for this shot
@@ -405,6 +424,7 @@ do
-- mymantis:AddShorad(myshorad,720) -- mymantis:AddShorad(myshorad,720)
-- mymantis:Start() -- mymantis:Start()
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
self:T(self.lid .. " WakeUpShorad")
self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
local targetcat = TargetCat or Object.Category.UNIT local targetcat = TargetCat or Object.Category.UNIT
local targetgroup = TargetGroup local targetgroup = TargetGroup
@@ -442,7 +462,7 @@ do
self:T(text) self:T(text)
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then if self.UseEmOnOff then
_group:SetAIOn() --_group:SetAIOn()
_group:EnableEmission(true) _group:EnableEmission(true)
end end
_group:OptionAlarmStateRed() _group:OptionAlarmStateRed()
@@ -454,14 +474,15 @@ do
end end
end end
end end
return self
end end
--- Main function - work on the EventData --- Main function - work on the EventData
-- @param #SHORAD self -- @param #SHORAD self
-- @param Core.Event#EVENTDATA EventData The event details table data set -- @param Core.Event#EVENTDATA EventData The event details table data set
function SHORAD:OnEventShot( EventData ) function SHORAD:HandleEventShot( EventData )
self:T( { EventData } ) self:T( { EventData } )
self:T(self.lid .. " HandleEventShot")
--local ShootingUnit = EventData.IniDCSUnit --local ShootingUnit = EventData.IniDCSUnit
--local ShootingUnitName = EventData.IniDCSUnitName --local ShootingUnitName = EventData.IniDCSUnitName
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired local ShootingWeapon = EventData.Weapon -- Identify the weapon fired

View File

@@ -1,5 +1,4 @@
-- The order of the declarations is important here. Don't touch it. --- GLOBALS: The order of the declarations is important here. Don't touch it.
--- Declare the event dispatcher based on the EVENT class --- Declare the event dispatcher based on the EVENT class
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
@@ -10,9 +9,37 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
--- Declare the main database object, which is used internally by the MOOSE classes. --- Declare the main database object, which is used internally by the MOOSE classes.
_DATABASE = DATABASE:New() -- Core.Database#DATABASE _DATABASE = DATABASE:New() -- Core.Database#DATABASE
--- Settings
_SETTINGS = SETTINGS:Set() _SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn() _SETTINGS:SetPlayerMenuOn()
--- Register cargos.
_DATABASE:_RegisterCargos() _DATABASE:_RegisterCargos()
--- Register zones.
_DATABASE:_RegisterZones() _DATABASE:_RegisterZones()
--- 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/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.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/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' ) __Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' ) __Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
@@ -20,9 +22,6 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
@@ -74,6 +73,8 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
@@ -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_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )

View File

@@ -18,6 +18,7 @@
-- * Option to present information in imperial or metric units -- * Option to present information in imperial or metric units
-- * Runway length and airfield elevation (optional) -- * Runway length and airfield elevation (optional)
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (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 frequency Radio frequency in MHz.
-- @field #number modulation Radio modulation 0=AM or 1=FM. -- @field #number modulation Radio modulation 0=AM or 1=FM.
-- @field #number power Radio power in Watts. Default 100 W. -- @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 soundpath Path to sound files.
-- @field #string relayunitname Name of the radio relay unit. -- @field #string relayunitname Name of the radio relay unit.
-- @field #table towerfrequency Table with tower frequencies. -- @field #table towerfrequency Table with tower frequencies.
@@ -88,6 +89,9 @@
-- @field #boolean usemarker Use mark on the F10 map. -- @field #boolean usemarker Use mark on the F10 map.
-- @field #number markerid Numerical ID of the F10 map mark point. -- @field #number markerid Numerical ID of the F10 map mark point.
-- @field #number relHumidity Relative humidity (used to approximately calculate the dew 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 -- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -253,6 +257,16 @@
-- --
-- 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). -- 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 -- # Examples
-- --
-- ## Caucasus: Batumi -- ## Caucasus: Batumi
@@ -284,6 +298,13 @@
-- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:SetVOR(114.25)
-- atisAbuDhabi:Start() -- 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 -- @field #ATIS
ATIS = { ATIS = {
@@ -368,6 +389,7 @@ ATIS.Alphabet = {
-- @field #number PersianGulf +2° (East). -- @field #number PersianGulf +2° (East).
-- @field #number TheChannel -10° (West). -- @field #number TheChannel -10° (West).
-- @field #number Syria +5° (East). -- @field #number Syria +5° (East).
-- @field #number MarianaIslands +2° (East).
ATIS.RunwayM2T={ ATIS.RunwayM2T={
Caucasus=0, Caucasus=0,
Nevada=12, Nevada=12,
@@ -375,6 +397,7 @@ ATIS.RunwayM2T={
PersianGulf=2, PersianGulf=2,
TheChannel=-10, TheChannel=-10,
Syria=5, Syria=5,
MarianaIslands=2,
} }
--- Whether ICAO phraseology is used for ATIS broadcasts. --- Whether ICAO phraseology is used for ATIS broadcasts.
@@ -385,6 +408,7 @@ ATIS.RunwayM2T={
-- @field #boolean PersianGulf true. -- @field #boolean PersianGulf true.
-- @field #boolean TheChannel true. -- @field #boolean TheChannel true.
-- @field #boolean Syria true. -- @field #boolean Syria true.
-- @field #boolean MarianaIslands true.
ATIS.ICAOPhraseology={ ATIS.ICAOPhraseology={
Caucasus=true, Caucasus=true,
Nevada=false, Nevada=false,
@@ -392,6 +416,7 @@ ATIS.ICAOPhraseology={
PersianGulf=true, PersianGulf=true,
TheChannel=true, TheChannel=true,
Syria=true, Syria=true,
MarianaIslands=true,
} }
--- Nav point data. --- Nav point data.
@@ -564,7 +589,7 @@ _ATIS={}
--- ATIS class version. --- ATIS class version.
-- @field #string version -- @field #string version
ATIS.version="0.9.1" ATIS.version="0.9.6"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -628,6 +653,7 @@ function ATIS:New(airbasename, frequency, modulation)
self:SetAltimeterQNH(true) self:SetAltimeterQNH(true)
self:SetMapMarks(false) self:SetMapMarks(false)
self:SetRelativeHumidity() self:SetRelativeHumidity()
self:SetQueueUpdateTime()
-- Start State. -- Start State.
self:SetStartState("Stopped") self:SetStartState("Stopped")
@@ -955,7 +981,9 @@ end
-- * 170° on the Normany map -- * 170° on the Normany map
-- * 182° on the Persian Gulf 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 #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}. -- @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
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 -- Start & Status
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1147,6 +1213,10 @@ function ATIS:onafterStart(From, Event, To)
-- Start radio queue. -- Start radio queue.
self.radioqueue:Start(1, 0.1) self.radioqueue:Start(1, 0.1)
-- Handle airbase capture
-- Handle events.
self:HandleEvent(EVENTS.BaseCaptured)
-- Init status updates. -- Init status updates.
self:__Status(-2) self:__Status(-2)
self:__CheckQueue(-3) self:__CheckQueue(-3)
@@ -1171,7 +1241,13 @@ function ATIS:onafterStatus(From, Event, To)
end end
-- Info text. -- 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:I(self.lid..text)
self:__Status(-60) self:__Status(-60)
@@ -1188,6 +1264,12 @@ end
-- @param #string To To state. -- @param #string To To state.
function ATIS:onafterCheckQueue(From, Event, To) function ATIS:onafterCheckQueue(From, Event, To)
if self.useSRS then
self:Broadcast()
else
if #self.radioqueue.queue==0 then if #self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message.")) self:T(self.lid..string.format("Radio queue empty. Repeating message."))
self:Broadcast() self:Broadcast()
@@ -1195,8 +1277,12 @@ function ATIS:onafterCheckQueue(From, Event, To)
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
end end
end
-- Check back in 5 seconds. -- Check back in 5 seconds.
self:__CheckQueue(-5) self:__CheckQueue(-math.abs(self.dTQueueCheck))
end end
--- Broadcast ATIS radio message. --- Broadcast ATIS radio message.
@@ -1327,6 +1413,9 @@ function ATIS:onafterBroadcast(From, Event, To)
local clock=UTILS.SecondsToClock(time) local clock=UTILS.SecondsToClock(time)
local zulu=UTILS.Split(clock, ":") local zulu=UTILS.Split(clock, ":")
local ZULU=string.format("%s%s", zulu[1], zulu[2]) 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. -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc.
@@ -1346,10 +1435,17 @@ function ATIS:onafterBroadcast(From, Event, To)
local sunrise=coord:GetSunrise() local sunrise=coord:GetSunrise()
sunrise=UTILS.Split(sunrise, ":") sunrise=UTILS.Split(sunrise, ":")
local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2]) 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() local sunset=coord:GetSunset()
sunset=UTILS.Split(sunset, ":") sunset=UTILS.Split(sunset, ":")
local SUNSET=string.format("%s%s", sunset[1], sunset[2]) 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 --- --- Temperature and Dew Point ---
@@ -1528,6 +1624,30 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
precepitation=3 -- snow precepitation=3 -- snow
end 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
precepitation=1 -- rain
else
precepitation=3 -- snow
end
end end
local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase)) local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase))
@@ -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 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" subtitle=subtitle.." Airport"
end end
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) 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 local alltext=subtitle
-- Information tag -- Information tag
subtitle=string.format("Information %s", NATO) subtitle=string.format("Information %s", NATO)
local _INFORMATION=subtitle local _INFORMATION=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Information, 0.5, subtitle) self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Zulu Time -- Zulu Time
subtitle=string.format("%s Zulu", ZULU) subtitle=string.format("%s Zulu", ZULU)
if not self.useSRS then
self.radioqueue:Number2Transmission(ZULU, nil, 0.5) self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
if not self.zulutimeonly then if not self.zulutimeonly then
-- Sunrise Time -- Sunrise Time
subtitle=string.format("Sunrise at %s local time", SUNRISE) subtitle=string.format("Sunrise at %s local time", SUNRISE)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
self:Transmission(ATIS.Sound.TimeLocal, 0.2) self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Sunset Time -- Sunset Time
subtitle=string.format("Sunset at %s local time", SUNSET) subtitle=string.format("Sunset at %s local time", SUNSET)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
self:Transmission(ATIS.Sound.TimeLocal, 0.2) self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -1634,6 +1764,7 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle..", gusting" subtitle=subtitle..", gusting"
end end
local _WIND=subtitle local _WIND=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
self.radioqueue:Number2Transmission(WINDFROM) self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At, 0.2) self:Transmission(ATIS.Sound.At, 0.2)
@@ -1646,6 +1777,7 @@ function ATIS:onafterBroadcast(From, Event, To)
if turbulence>0 then if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting, 0.2) self:Transmission(ATIS.Sound.Gusting, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Visibility -- Visibility
@@ -1654,6 +1786,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
subtitle=string.format("Visibility %s SM", VISIBILITY) subtitle=string.format("Visibility %s SM", VISIBILITY)
end end
if not self.useSRS then
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
self.radioqueue:Number2Transmission(VISIBILITY) self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then if self.metric then
@@ -1661,6 +1794,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
self:Transmission(ATIS.Sound.StatuteMiles, 0.2) self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Weather phenomena -- Weather phenomena
@@ -1699,6 +1833,7 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Actual output -- Actual output
if wp then if wp then
subtitle=string.format("Weather phenomena:%s", wpsub) subtitle=string.format("Weather phenomena:%s", wpsub)
if not self.useSRS then
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
if precepitation==1 then if precepitation==1 then
self:Transmission(ATIS.Sound.Rain, 0.5) self:Transmission(ATIS.Sound.Rain, 0.5)
@@ -1715,18 +1850,26 @@ function ATIS:onafterBroadcast(From, Event, To)
if dust then if dust then
self:Transmission(ATIS.Sound.Dust, 0.5) self:Transmission(ATIS.Sound.Dust, 0.5)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
-- Cloud base -- Cloud base
if not self.useSRS then
self:Transmission(CloudCover, 1.0, CLOUDSsub) self:Transmission(CloudCover, 1.0, CLOUDSsub)
end
if CLOUDBASE and static then if CLOUDBASE and static then
-- Base -- Base
local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100)
local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100)
if self.metric then 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 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 end
if not self.useSRS then
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
if tonumber(CLOUDBASE1000)>0 then if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000) self.radioqueue:Number2Transmission(CLOUDBASE1000)
@@ -1752,6 +1895,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self:Transmission(ATIS.Sound.Feet, 0.1) self:Transmission(ATIS.Sound.Feet, 0.1)
end end
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Temperature -- Temperature
@@ -1769,6 +1913,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end end
end end
local _TEMPERATURE=subtitle local _TEMPERATURE=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
if temperature<0 then if temperature<0 then
self:Transmission(ATIS.Sound.Minus, 0.2) self:Transmission(ATIS.Sound.Minus, 0.2)
@@ -1779,6 +1924,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Dew point -- Dew point
@@ -1796,6 +1942,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end end
end end
local _DEWPOINT=subtitle local _DEWPOINT=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
if dewpoint<0 then if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus, 0.2) self:Transmission(ATIS.Sound.Minus, 0.2)
@@ -1806,6 +1953,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Altimeter QNH/QFE. -- Altimeter QNH/QFE.
@@ -1813,24 +1961,25 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.qnhonly then if self.qnhonly then
subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2]) subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2])
else 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 end
else else
if self.metric then if self.metric then
if self.qnhonly then if self.qnhonly then
subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2]) subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2])
else 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 end
else else
if self.qnhonly then if self.qnhonly then
subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2]) subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2])
else 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 end
end end
local _ALTIMETER=subtitle local _ALTIMETER=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
if not self.qnhonly then if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH, 0.5) self:Transmission(ATIS.Sound.QNH, 0.5)
@@ -1860,6 +2009,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
end end
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Active runway. -- Active runway.
@@ -1870,6 +2020,7 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." Right" subtitle=subtitle.." Right"
end end
local _RUNACT=subtitle local _RUNACT=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runway) self.radioqueue:Number2Transmission(runway)
if rwyLeft==true then if rwyLeft==true then
@@ -1877,6 +2028,7 @@ function ATIS:onafterBroadcast(From, Event, To)
elseif rwyLeft==false then elseif rwyLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2) self:Transmission(ATIS.Sound.Right, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Runway length. -- Runway length.
@@ -1900,6 +2052,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end end
-- Transmit. -- Transmit.
if not self.useSRS then
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
if tonumber(L1000)>0 then if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000) self.radioqueue:Number2Transmission(L1000)
@@ -1914,7 +2067,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
self:Transmission(ATIS.Sound.Feet, 0.1) self:Transmission(ATIS.Sound.Feet, 0.1)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -1937,7 +2090,8 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." feet" subtitle=subtitle.." feet"
end end
-- Transmitt. -- Transmit.
if not self.useSRS then
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
if tonumber(L1000)>0 then if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000) self.radioqueue:Number2Transmission(L1000)
@@ -1952,7 +2106,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else else
self:Transmission(ATIS.Sound.Feet, 0.1) self:Transmission(ATIS.Sound.Feet, 0.1)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -1966,6 +2120,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end end
end end
subtitle=string.format("Tower frequency %s", freqs) subtitle=string.format("Tower frequency %s", freqs)
if not self.useSRS then
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
for _,freq in pairs(self.towerfrequency) do for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq) local f=string.format("%.3f", freq)
@@ -1977,7 +2132,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end end
self:Transmission(ATIS.Sound.MegaHertz, 0.2) self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end end
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -1985,6 +2140,7 @@ function ATIS:onafterBroadcast(From, Event, To)
local ils=self:GetNavPoint(self.ils, runway, rwyLeft) local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
if ils then if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
local f=string.format("%.2f", ils.frequency) local f=string.format("%.2f", ils.frequency)
f=UTILS.Split(f, ".") f=UTILS.Split(f, ".")
@@ -1994,7 +2150,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:Number2Transmission(f[2]) self.radioqueue:Number2Transmission(f[2])
end end
self:Transmission(ATIS.Sound.MegaHertz, 0.2) self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -2002,6 +2158,7 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
if ndb then if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency) local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".") f=UTILS.Split(f, ".")
@@ -2011,7 +2168,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:Number2Transmission(f[2]) self.radioqueue:Number2Transmission(f[2])
end end
self:Transmission(ATIS.Sound.MegaHertz, 0.2) self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -2019,6 +2176,7 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft) local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft)
if ndb then if ndb then
subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency) local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".") f=UTILS.Split(f, ".")
@@ -2028,13 +2186,17 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:Number2Transmission(f[2]) self.radioqueue:Number2Transmission(f[2])
end end
self:Transmission(ATIS.Sound.MegaHertz, 0.2) self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
-- VOR -- VOR
if self.vor then if self.vor then
subtitle=string.format("VOR frequency %.2f MHz", self.vor) subtitle=string.format("VOR frequency %.2f MHz", self.vor)
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) self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
local f=string.format("%.2f", self.vor) local f=string.format("%.2f", self.vor)
f=UTILS.Split(f, ".") f=UTILS.Split(f, ".")
@@ -2044,26 +2206,28 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:Number2Transmission(f[2]) self.radioqueue:Number2Transmission(f[2])
end end
self:Transmission(ATIS.Sound.MegaHertz, 0.2) self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
-- TACAN -- TACAN
if self.tacan then if self.tacan then
subtitle=string.format("TACAN channel %dX", self.tacan) subtitle=string.format("TACAN channel %dX", self.tacan)
if not self.useSRS then
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
-- RSBN -- RSBN
if self.rsbn then if self.rsbn then
subtitle=string.format("RSBN channel %d", self.rsbn) subtitle=string.format("RSBN channel %d", self.rsbn)
if not self.useSRS then
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
@@ -2071,17 +2235,19 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft) local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft)
if ndb then if ndb then
subtitle=string.format("PRMG channel %d", ndb.frequency) subtitle=string.format("PRMG channel %d", ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
end end
-- Advice on initial... -- Advice on initial...
subtitle=string.format("Advise on initial contact, you have information %s", NATO) subtitle=string.format("Advise on initial contact, you have information %s", NATO)
if not self.useSRS then
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle alltext=alltext..";\n"..subtitle
-- Report ATIS text. -- Report ATIS text.
@@ -2102,6 +2268,62 @@ end
-- @param #string Text Report text. -- @param #string Text Report text.
function ATIS:onafterReport(From, Event, To, Text) function ATIS:onafterReport(From, Event, To, Text)
self:T(self.lid..string.format("Report:\n%s", 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 end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -39,6 +39,7 @@
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
-- * F/A-18C Hornet (AI) -- * F/A-18C Hornet (AI)
-- * F-14A Tomcat (AI) -- * F-14A Tomcat (AI)
-- * E-2D Hawkeye (AI) -- * E-2D Hawkeye (AI)
@@ -101,7 +102,7 @@
-- ### Wags DCS Hornet Videos: -- ### Wags DCS Hornet Videos:
-- --
-- * [DCS: F/A-18C Hornet - Episode 9: CASE I Carrier Landing](https://www.youtube.com/watch?v=TuigBLhtAH8) -- * [DCS: F/A-18C Hornet - Episode 9: CASE I Carrier Landing](https://www.youtube.com/watch?v=TuigBLhtAH8)
-- * [DCS: F/A-18C Hornet Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) -- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ)
-- * [DCS: F/A-18C Hornet Case I Carrier Landing Training Lesson Recording](https://www.youtube.com/watch?v=D33uM9q4xgA) -- * [DCS: F/A-18C Hornet Case I Carrier Landing Training Lesson Recording](https://www.youtube.com/watch?v=D33uM9q4xgA)
-- --
-- ### AV-8B Harrier at USS Tarawa -- ### AV-8B Harrier at USS Tarawa
@@ -266,7 +267,7 @@
-- --
-- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate. -- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate.
-- --
-- This is a lot of responsability. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! -- This is a lot of responsibility. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong!
-- --
-- Recovery windows can be set up via the @{#AIRBOSS.AddRecoveryWindow} function as explained below. With this it is possible to seamlessly (within reason!) switch recovery cases in the same mission. -- Recovery windows can be set up via the @{#AIRBOSS.AddRecoveryWindow} function as explained below. With this it is possible to seamlessly (within reason!) switch recovery cases in the same mission.
-- --
@@ -293,14 +294,14 @@
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png)
-- --
-- Once the aircraft reaches the Inital, the landing pattern begins. The important steps of the pattern are shown in the image above. -- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above.
-- --
-- --
-- ## CASE III -- ## CASE III
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png)
-- --
-- A Case III recovery is conducted during nighttime. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. -- A Case III recovery is conducted during nighttime or when the visibility is below CASE II minima during the day. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above.
-- --
-- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat -- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat
-- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1. -- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1.
@@ -572,7 +573,10 @@
-- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR
-- * Too **H**igh or too **LO**w: H, LO -- * Too **H**igh or too **LO**w: H, LO
-- * Too **F**ast or too **SLO**w: F, SLO -- * Too **F**ast or too **SLO**w: F, SLO
-- * **Fly through** glideslope **down** or **up**: \\ , / -- * **O**ver**S**hoot: OS, only referenced during **X**
-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only
-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only
-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only
-- --
-- Each grading, x, is subdivided by -- Each grading, x, is subdivided by
-- --
@@ -633,7 +637,7 @@
-- --
-- ## Foul Deck Waveoff -- ## Foul Deck Waveoff
-- --
-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, -- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations,
-- or with an aircraft approaching the 3/4 NM during Case III operations. -- or with an aircraft approaching the 3/4 NM during Case III operations.
-- --
-- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**.
@@ -1263,6 +1267,7 @@ AIRBOSS = {
-- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string S3BTANKER Lockheed S-3B Viking tanker.
-- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string E2D Grumman E-2D Hawkeye AWACS.
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
-- @field #string T45C T-45C by VNAO
AIRBOSS.AircraftCarrier={ AIRBOSS.AircraftCarrier={
AV8B="AV8BNA", AV8B="AV8BNA",
HORNET="FA-18C_hornet", HORNET="FA-18C_hornet",
@@ -1271,6 +1276,7 @@ AIRBOSS.AircraftCarrier={
F14B="F-14B", F14B="F-14B",
F14A_AI="F-14A", F14A_AI="F-14A",
FA18C="F/A-18C", FA18C="F/A-18C",
T45C="T-45",
S3B="S-3B", S3B="S-3B",
S3BTANKER="S-3B Tanker", S3BTANKER="S-3B Tanker",
E2D="E-2C", E2D="E-2C",
@@ -1338,6 +1344,8 @@ AIRBOSS.CarrierType={
-- @field #number _min Min _OK_ value. Default -0.5 deg. -- @field #number _min Min _OK_ value. Default -0.5 deg.
-- @field #number Left (LUR) threshold. Default -1.0 deg. -- @field #number Left (LUR) threshold. Default -1.0 deg.
-- @field #number Right (LUL) threshold. Default 1.0 deg. -- @field #number Right (LUL) threshold. Default 1.0 deg.
-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg.
-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg.
-- @field #number LEFT LUR threshold. Default -3.0 deg. -- @field #number LEFT LUR threshold. Default -3.0 deg.
-- @field #number RIGHT LUL threshold. Default 3.0 deg. -- @field #number RIGHT LUL threshold. Default 3.0 deg.
@@ -1940,6 +1948,11 @@ function AIRBOSS:New(carriername, alias)
-- Welcome players. -- Welcome players.
self:SetWelcomePlayers(true) self:SetWelcomePlayers(true)
-- Coordinates
self.landingcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE
self.sterncoord=COORDINATE:New(0, 0, 0) --Core.Point#COORDINATE
self.landingspotcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE
-- Init carrier parameters. -- Init carrier parameters.
if self.carriertype==AIRBOSS.CarrierType.STENNIS then if self.carriertype==AIRBOSS.CarrierType.STENNIS then
self:_InitStennis() self:_InitStennis()
@@ -2643,10 +2656,10 @@ end
--- Set multiplayer environment wire correction. --- Set multiplayer environment wire correction.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number Dcorr Correction distance in meters. Default 8.7 m. -- @param #number Dcorr Correction distance in meters. Default 12 m.
-- @return #AIRBOSS self -- @return #AIRBOSS self
function AIRBOSS:SetMPWireCorrection(Dcorr) function AIRBOSS:SetMPWireCorrection(Dcorr)
self.mpWireCorrection=Dcorr or 8.7 self.mpWireCorrection=Dcorr or 12
return self return self
end end
@@ -2808,16 +2821,20 @@ end
-- @param #number _max -- @param #number _max
-- @param #number _min -- @param #number _min
-- @param #number Left -- @param #number Left
-- @param #number LeftMed
-- @param #number LEFT -- @param #number LEFT
-- @param #number Right -- @param #number Right
-- @param #number RightMed
-- @param #number RIGHT -- @param #number RIGHT
-- @return #AIRBOSS self -- @return #AIRBOSS self
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT)
self.lue._max=_max or 0.5 self.lue._max=_max or 0.5
self.lue._min=_min or -0.5 self.lue._min=_min or -0.5
self.lue.Left=Left or -1.0 self.lue.Left=Left or -1.0
self.lue.LeftMed=LeftMed or -2.0
self.lue.LEFT=LEFT or -3.0 self.lue.LEFT=LEFT or -3.0
self.lue.Right=Right or 1.0 self.lue.Right=Right or 1.0
self.lue.RightMed=RightMed or 2.0
self.lue.RIGHT=RIGHT or 3.0 self.lue.RIGHT=RIGHT or 3.0
return self return self
end end
@@ -3398,6 +3415,12 @@ function AIRBOSS:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.Ejection) self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft)
self:HandleEvent(EVENTS.MissionEnd) self:HandleEvent(EVENTS.MissionEnd)
self:HandleEvent(EVENTS.RemoveUnit)
--self.StatusScheduler=SCHEDULER:New(self)
--self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5)
self.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5)
-- Start status check in 1 second. -- Start status check in 1 second.
self:__Status(1) self:__Status(1)
@@ -3410,19 +3433,12 @@ end
-- @param #string To To state. -- @param #string To To state.
function AIRBOSS:onafterStatus(From, Event, To) function AIRBOSS:onafterStatus(From, Event, To)
if true then
--env.info("FF Status ==> return")
--return
end
-- Get current time. -- Get current time.
local time=timer.getTime() local time=timer.getTime()
-- Update marshal and pattern queue every 30 seconds. -- Update marshal and pattern queue every 30 seconds.
if time-self.Tqueue>self.dTqueue then if time-self.Tqueue>self.dTqueue then
--collectgarbage()
-- Get time. -- Get time.
local clock=UTILS.SecondsToClock(timer.getAbsTime()) local clock=UTILS.SecondsToClock(timer.getAbsTime())
local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) local eta=UTILS.SecondsToClock(self:_GetETAatNextWP())
@@ -3433,7 +3449,7 @@ function AIRBOSS:onafterStatus(From, Event, To)
local speed=self.carrier:GetVelocityKNOTS() local speed=self.carrier:GetVelocityKNOTS()
-- Check water is ahead. -- Check water is ahead.
local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) local collision=false --self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg))
local holdtime=0 local holdtime=0
if self.holdtimestamp then if self.holdtimestamp then
@@ -3490,15 +3506,9 @@ function AIRBOSS:onafterStatus(From, Event, To)
self.recoverywindow.WIND=false self.recoverywindow.WIND=false
end end
else
-- Find path around the obstacle.
if not self.detour then
--self:_Pathfinder()
end end
end end
end
-- Check recovery times and start/stop recovery mode if necessary. -- Check recovery times and start/stop recovery mode if necessary.
@@ -3528,14 +3538,21 @@ function AIRBOSS:onafterStatus(From, Event, To)
self:_ActivateBeacons() self:_ActivateBeacons()
end end
-- Call status every ~0.5 seconds.
self:__Status(-30)
end
--- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone?
-- @param #AIRBOSS self
function AIRBOSS:_Status()
-- Check player status. -- Check player status.
self:_CheckPlayerStatus() self:_CheckPlayerStatus()
-- Check AI landing pattern status -- Check AI landing pattern status
self:_CheckAIStatus() self:_CheckAIStatus()
-- Call status every ~0.5 seconds.
self:__Status(-self.dTstatus)
end end
--- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? --- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone?
@@ -3587,11 +3604,15 @@ function AIRBOSS:_CheckAIStatus()
-- Get lineup and distance to carrier. -- Get lineup and distance to carrier.
local lineup=self:_Lineup(unit, true) local lineup=self:_Lineup(unit, true)
local unitcoord=unit:GetCoord()
local dist=unitcoord:Get2DDistance(self:GetCoord())
-- Distance in NM. -- Distance in NM.
local distance=UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())) local distance=UTILS.MetersToNM(dist)
-- Altitude in ft. -- Altitude in ft.
local alt=UTILS.MetersToFeet(unit:GetAltitude()) local alt=UTILS.MetersToFeet(unitcoord.y)
-- Check if parameters are right and flight is in the groove. -- Check if parameters are right and flight is in the groove.
if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then
@@ -5471,6 +5492,7 @@ function AIRBOSS:_GetAircraftAoA(playerData)
-- Get AC type. -- Get AC type.
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
@@ -5497,6 +5519,15 @@ function AIRBOSS:_GetAircraftAoA(playerData)
aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units
aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units
aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units
elseif goshawk then
-- T-45C Goshawk parameters.
aoa.SLOW = 8.00 --19
aoa.Slow = 7.75 --18
aoa.OnSpeedMax = 7.25 --17.5
aoa.OnSpeed = 7.00 --17
aoa.OnSpeedMin = 6.75 --16.5
aoa.Fast = 6.25 --16
aoa.FAST = 6.00 --15
elseif skyhawk then elseif skyhawk then
-- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390 -- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390
-- Note that these are arbitrary UNITS and not degrees. We need a conversion formula! -- Note that these are arbitrary UNITS and not degrees. We need a conversion formula!
@@ -5681,6 +5712,9 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(600) alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250) speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end end
elseif step==AIRBOSS.PatternStep.BREAKENTRY then elseif step==AIRBOSS.PatternStep.BREAKENTRY then
@@ -5691,11 +5725,14 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(600) alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250) speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end end
elseif step==AIRBOSS.PatternStep.EARLYBREAK then elseif step==AIRBOSS.PatternStep.EARLYBREAK then
if hornet or tomcat or harrier then if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800) alt=UTILS.FeetToMeters(800)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(600) alt=UTILS.FeetToMeters(600)
@@ -5703,7 +5740,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif step==AIRBOSS.PatternStep.LATEBREAK then elseif step==AIRBOSS.PatternStep.LATEBREAK then
if hornet or tomcat or harrier then if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800) alt=UTILS.FeetToMeters(800)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(600) alt=UTILS.FeetToMeters(600)
@@ -5711,7 +5748,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif step==AIRBOSS.PatternStep.ABEAM then elseif step==AIRBOSS.PatternStep.ABEAM then
if hornet or tomcat or harrier then if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(600) alt=UTILS.FeetToMeters(600)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(500) alt=UTILS.FeetToMeters(500)
@@ -5726,10 +5763,19 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
dist=UTILS.NMToMeters(1.2) dist=UTILS.NMToMeters(1.2)
end end
if goshawk then
-- 0.9 to 1.1 NM per natops ch.4 page 48
dist=UTILS.NMToMeters(0.9)
else
dist=UTILS.NMToMeters(1.1)
end
elseif step==AIRBOSS.PatternStep.NINETY then elseif step==AIRBOSS.PatternStep.NINETY then
if hornet or tomcat then if hornet or tomcat then
alt=UTILS.FeetToMeters(500) alt=UTILS.FeetToMeters(500)
elseif goshawk then
alt=UTILS.FeetToMeters(450)
elseif skyhawk then elseif skyhawk then
alt=UTILS.FeetToMeters(500) alt=UTILS.FeetToMeters(500)
elseif harrier then elseif harrier then
@@ -5740,7 +5786,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif step==AIRBOSS.PatternStep.WAKE then elseif step==AIRBOSS.PatternStep.WAKE then
if hornet then if hornet or goshawk then
alt=UTILS.FeetToMeters(370) alt=UTILS.FeetToMeters(370)
elseif tomcat then elseif tomcat then
alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher.
@@ -5753,7 +5799,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
elseif step==AIRBOSS.PatternStep.FINAL then elseif step==AIRBOSS.PatternStep.FINAL then
if hornet then if hornet or goshawk then
alt=UTILS.FeetToMeters(300) alt=UTILS.FeetToMeters(300)
elseif tomcat then elseif tomcat then
alt=UTILS.FeetToMeters(360) alt=UTILS.FeetToMeters(360)
@@ -6079,66 +6125,32 @@ function AIRBOSS:_ScanCarrierZone()
-- Create a new flight group -- Create a new flight group
if knownflight then if knownflight then
-- Debug output.
self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.", groupname, actype))
-- Check if flight is AI and if we want to handle it at all. -- Check if flight is AI and if we want to handle it at all.
if knownflight.ai and self.handleai then if knownflight.ai and knownflight.flag==-100 and self.handleai then
-- Defines if AI group should be handled by the airboss. local putintomarshal=false
local iscarriersquad=true
-- Check if AI group is part of the group set if a set was defined. -- Get flight group.
if self.squadsetAI then local flight=_DATABASE:GetFlightGroup(groupname)
local group=self.squadsetAI:FindGroup(groupname)
if group then if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then
iscarriersquad=true if flight.ishelo then
else else
iscarriersquad=false putintomarshal=true
end
end
-- Check if group was explicitly excluded.
if self.excludesetAI then
local group=self.excludesetAI:FindGroup(groupname)
if group then
iscarriersquad=false
end end
flight.airboss=self
end end
-- Get distance to carrier.
local dist=knownflight.group:GetCoordinate():Get2DDistance(self:GetCoordinate())
-- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0. -- Send AI flight to marshal stack.
local closein=knownflight.dist0-dist if putintomarshal then
-- Debug info.
self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein)))
-- Is this group the tanker?
local istanker=self.tanker and self.tanker.tanker:GetName()==groupname
-- Is this group the AWACS?
local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname
-- Send tanker to marshal stack?
local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true
-- Send AWACS to marhsal stack?
local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true
-- Put flight into Marshal.
local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and (not istanker) and (not isawacs)
-- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value.
if putintomarshal or tanker2marshal or awacs2marshal then
-- Get the next free stack for current recovery case. -- Get the next free stack for current recovery case.
local stack=self:_GetFreeStack(knownflight.ai) local stack=self:_GetFreeStack(knownflight.ai)
-- Repawn. -- Repawn.
local respawn=self.respawnAI --or tanker2marshal local respawn=self.respawnAI
if stack then if stack then
@@ -6158,7 +6170,8 @@ function AIRBOSS:_ScanCarrierZone()
break break
end -- Closed in or tanker/AWACS end -- Closed in or tanker/AWACS
end -- AI
end
else else
@@ -8877,6 +8890,58 @@ function AIRBOSS:OnEventEjection(EventData)
end end
--- Airboss event handler for event REMOVEUNIT.
-- @param #AIRBOSS self
-- @param Core.Event#EVENTDATA EventData
function AIRBOSS:OnEventRemoveUnit(EventData)
self:F3({eventland = EventData})
-- Nil checks.
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s removed!",_playername))
-- Get player flight.
local flight=self.players[_playername]
-- Remove flight completely from all queues and collapse marshal if necessary.
if flight then
self:_RemoveFlight(flight, true)
end
else
-- Debug message.
self:T(self.lid..string.format("AI unit %s removed!", EventData.IniUnitName))
-- Remove element/unit from flight group and from all queues if no elements alive.
self:_RemoveUnitFromFlight(EventData.IniUnit)
-- What could happen is, that another element has landed (recovered) already and this one crashes.
-- This would mean that the flight would not be deleted from the queue ==> Check if section recovered.
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights)
self:_CheckSectionRecovered(flight)
end
end
--- Airboss event handler for event player leave unit. --- Airboss event handler for event player leave unit.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
@@ -10041,6 +10106,27 @@ function AIRBOSS:_Groove(playerData)
-- Distance in NM. -- Distance in NM.
local d=UTILS.MetersToNM(rho) local d=UTILS.MetersToNM(rho)
-- Drift on lineup.
if rho>=RAR and rho<=RIM then
if gd.LUE>0.22 and lineupError<-0.22 then
env.info" Drift Right across centre ==> DR-"
gd.Drift=" DR"
self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE<-0.22 and lineupError>0.22 then
env.info" Drift Left ==> DL-"
gd.Drift=" DL"
self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE>0.13 and lineupError<-0.14 then
env.info" Little Drift Right across centre ==> (DR-)"
gd.Drift=" (DR)"
self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE<-0.13 and lineupError>0.14 then
env.info" Little Drift Left across centre ==> (DL-)"
gd.Drift=" (DL)"
self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
end
end
-- Update max deviation of line up error. -- Update max deviation of line up error.
if math.abs(lineupError)>math.abs(gd.LUE) then if math.abs(lineupError)>math.abs(gd.LUE) then
self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE)) self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE))
@@ -10339,24 +10425,25 @@ function AIRBOSS:_GetSternCoord()
local FB=self:GetFinalBearing() local FB=self:GetFinalBearing()
-- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing.
local stern=self:GetCoordinate() self.sterncoord:UpdateFromCoordinate(self:GetCoordinate())
--local stern=self:GetCoordinate()
-- Stern coordinate (sterndist<0). -- Stern coordinate (sterndist<0).
if self.carriertype==AIRBOSS.CarrierType.TARAWA then if self.carriertype==AIRBOSS.CarrierType.TARAWA then
-- Tarawa: Translate 8 meters port. -- Tarawa: Translate 8 meters port.
stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8, FB-90) self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true)
elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
-- Stennis: translate 7 meters starboard wrt Final bearing. -- Stennis: translate 7 meters starboard wrt Final bearing.
stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(7, FB+90) self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true)
else else
-- Nimitz SC: translate 8 meters starboard wrt Final bearing. -- Nimitz SC: translate 8 meters starboard wrt Final bearing.
stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(9.5, FB+90) self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true)
end end
-- Set altitude. -- Set altitude.
stern:SetAltitude(self.carrierparam.deckheight) self.sterncoord:SetAltitude(self.carrierparam.deckheight)
return stern return self.sterncoord
end end
--- Get wire from landing position. --- Get wire from landing position.
@@ -10475,6 +10562,9 @@ function AIRBOSS:_Trapped(playerData)
dcorr=100 dcorr=100
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
-- A-4E gets slowed down much faster the the F/A-18C! -- A-4E gets slowed down much faster the the F/A-18C!
dcorr=56
elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then
-- T-45 also gets slowed down much faster the the F/A-18C.
dcorr=56 dcorr=56
end end
@@ -10561,6 +10651,8 @@ end
-- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone.
function AIRBOSS:_GetZoneInitial(case) function AIRBOSS:_GetZoneInitial(case)
self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial")
-- Get radial, i.e. inverse of BRC. -- Get radial, i.e. inverse of BRC.
local radial=self:GetRadial(2, false, false) local radial=self:GetRadial(2, false, false)
@@ -10568,7 +10660,7 @@ function AIRBOSS:_GetZoneInitial(case)
local cv=self:GetCoordinate() local cv=self:GetCoordinate()
-- Vec2 array. -- Vec2 array.
local vec2 local vec2={}
if case==1 then if case==1 then
-- Case I -- Case I
@@ -10599,9 +10691,12 @@ function AIRBOSS:_GetZoneInitial(case)
end end
-- Polygon zone. -- Polygon zone.
local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) --local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2)
return zone self.zoneInitial:UpdateFromVec2(vec2)
--return zone
return self.zoneInitial
end end
--- Get lineup groove zone. --- Get lineup groove zone.
@@ -10609,6 +10704,8 @@ end
-- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. -- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone.
function AIRBOSS:_GetZoneLineup() function AIRBOSS:_GetZoneLineup()
self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup")
-- Get radial, i.e. inverse of BRC. -- Get radial, i.e. inverse of BRC.
local fbi=self:GetRadial(1, false, false) local fbi=self:GetRadial(1, false, false)
@@ -10625,10 +10722,13 @@ function AIRBOSS:_GetZoneLineup()
-- Vec2 array. -- Vec2 array.
local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()}
-- Polygon zone. self.zoneLineup:UpdateFromVec2(vec2)
local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2)
return zone -- Polygon zone.
--local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2)
--return zone
return self.zoneLineup
end end
@@ -10640,6 +10740,8 @@ end
-- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone.
function AIRBOSS:_GetZoneGroove(l, w, b) function AIRBOSS:_GetZoneGroove(l, w, b)
self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove")
l=l or 1.50 l=l or 1.50
w=w or 0.25 w=w or 0.25
b=b or 0.10 b=b or 0.10
@@ -10661,10 +10763,13 @@ function AIRBOSS:_GetZoneGroove(l, w, b)
-- Vec2 array. -- Vec2 array.
local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()}
-- Polygon zone. self.zoneGroove:UpdateFromVec2(vec2)
local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2)
return zone -- Polygon zone.
--local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2)
--return zone
return self.zoneGroove
end end
--- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case. --- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case.
@@ -10688,8 +10793,9 @@ function AIRBOSS:_GetZoneBullseye(case)
-- Create zone. -- Create zone.
local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius) local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius)
return zone return zone
--self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius)
end end
--- Get dirty up zone with radius 1 NM and DME 9 NM from the carrier. Radial depends on recovery case. --- Get dirty up zone with radius 1 NM and DME 9 NM from the carrier. Radial depends on recovery case.
@@ -10885,6 +10991,8 @@ end
-- @return Core.Zone#ZONE Zone surrounding the carrier. -- @return Core.Zone#ZONE Zone surrounding the carrier.
function AIRBOSS:_GetZoneCarrierBox() function AIRBOSS:_GetZoneCarrierBox()
self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone")
-- Stern coordinate. -- Stern coordinate.
local S=self:_GetSternCoord() local S=self:_GetSternCoord()
@@ -10913,9 +11021,12 @@ function AIRBOSS:_GetZoneCarrierBox()
end end
-- Create polygon zone. -- Create polygon zone.
local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2)
--return zone
return zone self.zoneCarrierbox:UpdateFromVec2(vec2)
return self.zoneCarrierbox
end end
--- Get zone of landing runway. --- Get zone of landing runway.
@@ -10923,6 +11034,8 @@ end
-- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway.
function AIRBOSS:_GetZoneRunwayBox() function AIRBOSS:_GetZoneRunwayBox()
self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone")
-- Stern coordinate. -- Stern coordinate.
local S=self:_GetSternCoord() local S=self:_GetSternCoord()
@@ -10945,9 +11058,12 @@ function AIRBOSS:_GetZoneRunwayBox()
end end
-- Create polygon zone. -- Create polygon zone.
local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2)
--return zone
return zone self.zoneRunwaybox:UpdateFromVec2(vec2)
return self.zoneRunwaybox
end end
@@ -11050,12 +11166,14 @@ function AIRBOSS:_GetZoneHolding(case, stack)
-- Post 2.5 NM port of carrier. -- Post 2.5 NM port of carrier.
local Post=self:GetCoordinate():Translate(D, hdg+270) local Post=self:GetCoordinate():Translate(D, hdg+270)
--TODO: update zone not creating a new one.
-- Create holding zone. -- Create holding zone.
zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius)
-- Delta pattern. -- Delta pattern.
if self.carriertype==AIRBOSS.CarrierType.TARAWA then if self.carriertype==AIRBOSS.CarrierType.TARAWA then
zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5))
end end
@@ -11074,10 +11192,12 @@ function AIRBOSS:_GetZoneHolding(case, stack)
-- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. -- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier.
-- So stay 0-5 NM (+1 NM error margin) port of carrier. -- So stay 0-5 NM (+1 NM error margin) port of carrier.
zoneHolding=ZONE_POLYGON_BASE:New("CASE II/III Holding Zone", p) self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone")
self.zoneHolding:UpdateFromVec2(p)
end end
return zoneHolding return self.zoneHolding
end end
--- Get zone where player are automatically commence when enter. --- Get zone where player are automatically commence when enter.
@@ -11118,7 +11238,9 @@ function AIRBOSS:_GetZoneCommence(case, stack)
end end
-- Create holding zone. -- Create holding zone.
zone=ZONE_RADIUS:New("CASE I Commence Zone", Three:GetVec2(), R) self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone")
self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R)
else else
-- Case II/III -- Case II/III
@@ -11149,11 +11271,13 @@ function AIRBOSS:_GetZoneCommence(case, stack)
end end
-- Zone polygon. -- Zone polygon.
zone=ZONE_POLYGON_BASE:New("CASE II/III Commence Zone", p) self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone")
self.zoneCommence:UpdateFromVec2(p)
end end
return zone return self.zoneCommence
end end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -11397,8 +11521,11 @@ end
-- @return Core.Point#COORDINATE Optimal landing coordinate. -- @return Core.Point#COORDINATE Optimal landing coordinate.
function AIRBOSS:_GetOptLandingCoordinate() function AIRBOSS:_GetOptLandingCoordinate()
-- Start with stern coordiante.
self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord())
-- Stern coordinate. -- Stern coordinate.
local stern=self:_GetSternCoord() --local stern=self:_GetSternCoord()
-- Final bearing. -- Final bearing.
local FB=self:GetFinalBearing(false) local FB=self:GetFinalBearing(false)
@@ -11406,10 +11533,11 @@ function AIRBOSS:_GetOptLandingCoordinate()
if self.carriertype==AIRBOSS.CarrierType.TARAWA then if self.carriertype==AIRBOSS.CarrierType.TARAWA then
-- Landing 100 ft abeam, 120 ft alt. -- Landing 100 ft abeam, 120 ft alt.
stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true)
--stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
-- Alitude 120 ft. -- Alitude 120 ft.
stern:SetAltitude(UTILS.FeetToMeters(120)) self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
else else
@@ -11417,15 +11545,15 @@ function AIRBOSS:_GetOptLandingCoordinate()
if self.carrierparam.wire3 then if self.carrierparam.wire3 then
-- We take the position of the 3rd wire to approximately account for the length of the aircraft. -- We take the position of the 3rd wire to approximately account for the length of the aircraft.
local w3=self.carrierparam.wire3 local w3=self.carrierparam.wire3
stern=stern:Translate(w3, FB, true) self.landingcoord:Translate(w3, FB, true, true)
end end
-- Add 2 meters to account for aircraft height. -- Add 2 meters to account for aircraft height.
stern.y=stern.y+2 self.landingcoord.y=self.landingcoord.y+2
end end
return stern return self.landingcoord
end end
--- Get landing spot on Tarawa. --- Get landing spot on Tarawa.
@@ -11433,8 +11561,10 @@ end
-- @return Core.Point#COORDINATE Primary landing spot coordinate. -- @return Core.Point#COORDINATE Primary landing spot coordinate.
function AIRBOSS:_GetLandingSpotCoordinate() function AIRBOSS:_GetLandingSpotCoordinate()
self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord())
-- Stern coordinate. -- Stern coordinate.
local stern=self:_GetSternCoord() --local stern=self:_GetSternCoord()
if self.carriertype==AIRBOSS.CarrierType.TARAWA then if self.carriertype==AIRBOSS.CarrierType.TARAWA then
@@ -11442,11 +11572,11 @@ function AIRBOSS:_GetLandingSpotCoordinate()
local hdg=self:GetHeading() local hdg=self:GetHeading()
-- Primary landing spot 7.5 -- Primary landing spot 7.5
stern=stern:Translate(57, hdg):SetAltitude(self.carrierparam.deckheight) self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight)
end end
return stern return self.landingspotcoord
end end
--- Get true (or magnetic) heading of carrier. --- Get true (or magnetic) heading of carrier.
@@ -11509,7 +11639,7 @@ end
--- Get wind speed on carrier deck parallel and perpendicular to runway. --- Get wind speed on carrier deck parallel and perpendicular to runway.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number alt Altitude in meters. Default 50 m. -- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog)
-- @return #number Wind component parallel to runway im m/s. -- @return #number Wind component parallel to runway im m/s.
-- @return #number Wind component perpendicular to runway in m/s. -- @return #number Wind component perpendicular to runway in m/s.
-- @return #number Total wind strength in m/s. -- @return #number Total wind strength in m/s.
@@ -11532,7 +11662,7 @@ function AIRBOSS:GetWindOnDeck(alt)
zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle)
-- Wind (from) vector -- Wind (from) vector
local vw=cv:GetWindWithTurbulenceVec3(alt or 50) local vw=cv:GetWindWithTurbulenceVec3(alt or 15)
-- Total wind velocity vector. -- Total wind velocity vector.
-- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total.
@@ -11946,15 +12076,15 @@ function AIRBOSS:_EvalGrooveTime(playerData)
local grade="" local grade=""
if t<9 then if t<9 then
grade="--" grade="_NESA_"
elseif t<12 then elseif t<15 then
grade="(OK)" grade="NESA"
elseif t<22 then elseif t<19 then
grade="OK" grade="OK Groove"
elseif t<=24 then elseif t<=24 then
grade="(OK)" grade="(LIG)"
else else
grade="--" grade="LIG"
end end
-- The unicorn! -- The unicorn!
@@ -11993,9 +12123,9 @@ function AIRBOSS:_LSOgrade(playerData)
local nS=count(G, '%(') local nS=count(G, '%(')
local nN=N-nS-nL local nN=N-nS-nL
-- Groove time 16-18 sec for a unicorn. -- Groove time 15-18.99 sec for a unicorn.
local Tgroove=playerData.Tgroove local Tgroove=playerData.Tgroove
local TgrooveUnicorn=Tgroove and (Tgroove>=16.0 and Tgroove<=18.0) or false local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false
local grade local grade
local points local points
@@ -12129,6 +12259,34 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
-- Aircraft specific AoA values. -- Aircraft specific AoA values.
local acaoa=self:_GetAircraftAoA(playerData) local acaoa=self:_GetAircraftAoA(playerData)
--Angled Approach.
local P=nil
if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then
if LUE>self.lue.RIGHT then
P=underline("AA")
elseif
LUE>self.lue.RightMed then
P="AA "
elseif
LUE>self.lue.Right then
P=little("AA")
end
end
--Overshoot Start.
local O=nil
if step==AIRBOSS.PatternStep.GROOVE_XX then
if LUE<self.lue.LEFT then
O=underline("OS")
elseif
LUE<self.lue.Left then
O="OS"
elseif
LUE<self.lue._min then
O=little("OS")
end
end
-- Speed via AoA. Depends on aircraft type. -- Speed via AoA. Depends on aircraft type.
local S=nil local S=nil
if AOA>acaoa.SLOW then if AOA>acaoa.SLOW then
@@ -12161,7 +12319,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
A=little("LO") A=little("LO")
end end
-- Line up. Good [-0.5, 0.5] -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5]
local D=nil local D=nil
if LUE>self.lue.RIGHT then if LUE>self.lue.RIGHT then
D=underline("LUL") D=underline("LUL")
@@ -12169,13 +12327,23 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
D="LUL" D="LUL"
elseif LUE>self.lue._max then elseif LUE>self.lue._max then
D=little("LUL") D=little("LUL")
elseif LUE<self.lue.LEFT then elseif playerData.case<3 then
if LUE<self.lue.LEFT and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=underline("LUR")
elseif LUE<self.lue.Left and step~=AIRBOSS.PatternStep.GROOVE_XX then
D="LUR"
elseif LUE<self.lue._min and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=little("LUR")
end
elseif playerData.case==3 then
if LUE<self.lue.LEFT then
D=underline("LUR") D=underline("LUR")
elseif LUE<self.lue.Left then elseif LUE<self.lue.Left then
D="LUR" D="LUR"
elseif LUE<self.lue._min then elseif LUE<self.lue._min then
D=little("LUR") D=little("LUR")
end end
end
-- Compile. -- Compile.
local G="" local G=""
@@ -12184,6 +12352,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
if fdata.FlyThrough then if fdata.FlyThrough then
G=G..fdata.FlyThrough G=G..fdata.FlyThrough
end end
-- Angled Approach - doesn't affect score, advisory only.
if P then
G=G..P
n=n
end
-- Speed. -- Speed.
if S then if S then
G=G..S G=G..S
@@ -12199,6 +12372,16 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
G=G..D G=G..D
n=n+1 n=n+1
end end
--Drift in Lineup
if fdata.Drift then
G=G..fdata.Drift
n=n -- Drift doesn't affect score, advisory only.
end
-- Overshoot.
if O then
G=G..O
n=n+1
end
-- Add current step. -- Add current step.
local step=self:_GS(step) local step=self:_GS(step)
@@ -14025,6 +14208,8 @@ function AIRBOSS:_GetACNickname(actype)
local nickname="unknown" local nickname="unknown"
if actype==AIRBOSS.AircraftCarrier.A4EC then if actype==AIRBOSS.AircraftCarrier.A4EC then
nickname="Skyhawk" nickname="Skyhawk"
elseif actype==AIRBOSS.AircraftCarrier.T45C then
nickname="Goshawk"
elseif actype==AIRBOSS.AircraftCarrier.AV8B then elseif actype==AIRBOSS.AircraftCarrier.AV8B then
nickname="Harrier" nickname="Harrier"
elseif actype==AIRBOSS.AircraftCarrier.E2D then elseif actype==AIRBOSS.AircraftCarrier.E2D then
@@ -14390,9 +14575,15 @@ end
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @return Core.Point#COORDINATE Carrier coordinate. -- @return Core.Point#COORDINATE Carrier coordinate.
function AIRBOSS:GetCoordinate() function AIRBOSS:GetCoordinate()
return self.carrier:GetCoordinate() return self.carrier:GetCoord()
end end
--- Get carrier coordinate.
-- @param #AIRBOSS self
-- @return Core.Point#COORDINATE Carrier coordinate.
function AIRBOSS:GetCoord()
return self.carrier:GetCoord()
end
--- Get static weather of this mission from env.mission.weather. --- Get static weather of this mission from env.mission.weather.
-- @param #AIRBOSS self -- @param #AIRBOSS self
@@ -15526,8 +15717,12 @@ function AIRBOSS:_MarshalCallRecoveryStart(case)
-- Debug output. -- Debug output.
local text=string.format("Starting aircraft recovery Case %d ops.", case) local text=string.format("Starting aircraft recovery Case %d ops.", case)
if case>1 then if case==1 then
text=text..string.format(" Marshal radial %03d°.", radial) text=text..string.format(" BRC %03d°.", self:GetBRC())
elseif case==2 then
text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC())
elseif case==3 then
text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false))
end end
self:T(self.lid..text) self:T(self.lid..text)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,10 @@
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions. --- **Sound** - Radio transmissions.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Provide radio functionality to broadcast radio transmissions. -- * 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? -- What are radio communications in DCS?
-- --
@@ -35,13 +32,13 @@
-- --
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Core.Radio -- @module Sound.Radio
-- @image Core_Radio.JPG -- @image Core_Radio.JPG
--- Models the radio capability. --- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
-- --
-- ## RADIO usage -- # RADIO usage
-- --
-- There are 3 steps to a successful radio transmission. -- There are 3 steps to a successful radio transmission.
-- --
@@ -395,438 +392,3 @@ function RADIO:StopBroadcast()
end end
return self return self
end end
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## AA TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- Use @#BEACON:StopAATACAN}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE
BEACON = {
ClassName = "BEACON",
Positionable = nil,
name=nil,
}
--- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN TACtical Air Navigation system.
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number PRMG_LOCALIZER
-- @field #number PRMG_GLIDESLOPE
-- @field #number ICLS Same as ICLS glideslope.
-- @field #number ICLS_LOCALIZER
-- @field #number ICLS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 128,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16424,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
PRMG_LOCALIZER = 33024,
PRMG_GLIDESLOPE = 33280,
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
ICLS_LOCALIZER = 131328,
ICLS_GLIDESLOPE = 131584,
NAUTICAL_HOMER = 65536,
}
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
-- @type BEACON.System
-- @field #number PAR_10 ?
-- @field #number RSBN_5 Russian VOR/DME system.
-- @field #number TACAN TACtical Air Navigation system on ground.
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
-- @field #number VOR Very High Frequency Omni-Directional Range
-- @field #number ILS_LOCALIZER ILS localizer
-- @field #number ILS_GLIDESLOPE ILS glideslope.
-- @field #number PRGM_LOCALIZER PRGM localizer.
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
-- @field #number BROADCAST_STATION Broadcast station.
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
-- @field #number ICLS_LOCALIZER Carrier landing system.
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER_X = 4,
TACAN_TANKER_Y = 5,
VOR = 6,
ILS_LOCALIZER = 7,
ILS_GLIDESLOPE = 8,
PRMG_LOCALIZER = 9,
PRMG_GLIDESLOPE = 10,
BROADCAST_STATION = 11,
VORTAC = 12,
TACAN_AA_MODE_X = 13,
TACAN_AA_MODE_Y = 14,
VORDME = 15,
ICLS_LOCALIZER = 16,
ICLS_GLIDESLOPE = 17,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s", tostring(self.name)))
return self
end
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
return nil
end
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" then
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
end
end
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
-- @param #boolean Bearing Can the BEACON be homed on ?
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration})
local IsValid = true
if not self.Positionable:IsAir() then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
IsValid = false
end
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
-- or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = 5
else
System = 14
end
if IsValid then -- Starts the BEACON
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = 4,
system = System,
callsign = Message,
frequency = Frequency,
}
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id = 'DeactivateBeacon',
params = {
}
})
end
end
--- Activates a general pupose Radio Beacon
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
-- They can home in on these specific frequencies :
-- * **Mi8**
-- * R-828 -> 20-60MHz
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
-- * ARK9 -> 150-1300KHz
-- * **Huey**
-- * AN/ARC-131 -> 30-76 Mhz FM
-- @param #BEACON self
-- @param #string FileName The name of the audio file
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a beacon for a unit in distress.
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
-- -- The beacon they use is battery-powered, and only lasts for 5 min
-- local UnitInDistress = UNIT:FindByName("Unit1")
-- local UnitBeacon = UnitInDistress:GetBeacon()
--
-- -- Set the beacon and start it
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
-- Check the filename
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
IsValid = true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
end
-- Check the Frequency
if type(Frequency) ~= "number" and IsValid then
self:E({"Frequency invalid. ", Frequency})
IsValid = false
end
Frequency = Frequency * 1000000 -- Conversion to Hz
-- Check the modulation
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
IsValid = false
end
-- Check the Power
if type(Power) ~= "number" and IsValid then
self:E({"Power is invalid. ", Power})
IsValid = false
end
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
function()
self:StopRadioBeacon()
end, {}, BeaconDuration)
end
end
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
return self
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

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

View File

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

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 -- @image Core_Usersound.JPG
do -- UserSound do -- UserSound

View File

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

View File

@@ -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"]
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -50,6 +50,7 @@ BIGSMOKEPRESET = {
-- @field #string PersianGulf Persian Gulf map. -- @field #string PersianGulf Persian Gulf map.
-- @field #string TheChannel The Channel map. -- @field #string TheChannel The Channel map.
-- @field #string Syria Syria map. -- @field #string Syria Syria map.
-- @field #string MarianaIslands Mariana Islands map.
DCSMAP = { DCSMAP = {
Caucasus="Caucasus", Caucasus="Caucasus",
NTTR="Nevada", NTTR="Nevada",
@@ -57,6 +58,7 @@ DCSMAP = {
PersianGulf="PersianGulf", PersianGulf="PersianGulf",
TheChannel="TheChannel", TheChannel="TheChannel",
Syria="Syria", Syria="Syria",
MarianaIslands="MarianaIslands"
} }
@@ -489,7 +491,7 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end end
-- 024<EFBFBD> 23' 12"N or 024<EFBFBD> 23' 12.03"N -- 024° 23' 12"N or 024° 23' 12.03"N
return string.format('%03d°', latDeg)..string.format('%02d', latMin)..'\''..string.format(secFrmtStr, latSec)..'"'..latHemi..' ' return string.format('%03d°', latDeg)..string.format('%02d', latMin)..'\''..string.format(secFrmtStr, latSec)..'"'..latHemi..' '
.. string.format('%03d°', lonDeg)..string.format('%02d', lonMin)..'\''..string.format(secFrmtStr, lonSec)..'"'..lonHemi .. string.format('%03d°', lonDeg)..string.format('%02d', lonMin)..'\''..string.format(secFrmtStr, lonSec)..'"'..lonHemi
@@ -652,6 +654,17 @@ function UTILS.GetMarkID()
end end
--- Remove an object (marker, circle, arrow, text, quad, ...) on the F10 map.
-- @param #number MarkID Unique ID of the object.
-- @param #number Delay (Optional) Delay in seconds before the mark is removed.
function UTILS.RemoveMark(MarkID, Delay)
if Delay and Delay>0 then
TIMER:New(UTILS.RemoveMark, MarkID):Start(Delay)
else
trigger.action.removeMark(MarkID)
end
end
-- Test if a Vec2 is in a radius of another Vec2 -- Test if a Vec2 is in a radius of another Vec2
function UTILS.IsInRadius( InVec2, Vec2, Radius ) function UTILS.IsInRadius( InVec2, Vec2, Radius )
@@ -669,7 +682,10 @@ function UTILS.IsInSphere( InVec3, Vec3, Radius )
return InSphere return InSphere
end end
-- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s. --- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s.
-- @param #number speed Wind speed in m/s.
-- @return #number Beaufort number.
-- @return #string Beauford wind description.
function UTILS.BeaufortScale(speed) function UTILS.BeaufortScale(speed)
local bn=nil local bn=nil
local bd=nil local bd=nil
@@ -729,6 +745,21 @@ function UTILS.Split(str, sep)
return result return result
end end
--- Get a table of all characters in a string.
-- @param #string str Sting.
-- @return #table Individual characters.
function UTILS.GetCharacters(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars, c)
end
return chars
end
--- Convert time in seconds to hours, minutes and seconds. --- Convert time in seconds to hours, minutes and seconds.
-- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. -- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function.
-- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day.
@@ -1151,6 +1182,9 @@ end
-- * NTTR +12 (East), year ~ 2011 -- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944 -- * Normandy -10 (West), year ~ 1944
-- * Persian Gulf +2 (East), year ~ 2011 -- * Persian Gulf +2 (East), year ~ 2011
-- * The Cannel Map -10 (West)
-- * Syria +5 (East)
-- * Mariana Islands +2 (East)
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees. -- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map) function UTILS.GetMagneticDeclination(map)
@@ -1171,6 +1205,8 @@ function UTILS.GetMagneticDeclination(map)
declination=-10 declination=-10
elseif map==DCSMAP.Syria then elseif map==DCSMAP.Syria then
declination=5 declination=5
elseif map==DCSMAP.MarianaIslands then
declination=2
else else
declination=0 declination=0
end end
@@ -1299,6 +1335,8 @@ function UTILS.GMTToLocalTimeDifference()
return 2 -- This map currently needs +2 return 2 -- This map currently needs +2
elseif theatre==DCSMAP.Syria then elseif theatre==DCSMAP.Syria then
return 3 -- Damascus is UTC+3 hours return 3 -- Damascus is UTC+3 hours
elseif theatre==DCSMAP.MarianaIslands then
return 10 -- Guam is UTC+10 hours.
else else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0 return 0
@@ -1466,3 +1504,208 @@ function UTILS.GetOSTime()
return nil return nil
end end
--- Shuffle a table accoring to Fisher Yeates algorithm
--@param #table table to be shuffled
--@return #table
function UTILS.ShuffleTable(t)
if t == nil or type(t) ~= "table" then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local TempTable = {}
for i = 1, #t do
local r = math.random(1,#t)
TempTable[i] = t[r]
table.remove(t,r)
end
return TempTable
end
--- (Helicopter) Check if one loading door is open.
--@param #string unit_name Unit name to be checked
--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists.
function UTILS.IsLoadingDoorOpen( unit_name )
local ret_val = false
local unit = Unit.getByName(unit_name)
if unit ~= nil then
local type_name = unit:getTypeName()
if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then
BASE:T(unit_name .. " Cargo doors are open or cargo door not present")
ret_val = true
end
if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then
BASE:T(unit_name .. " a side door is open")
ret_val = true
end
if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then
BASE:T(unit_name .. " a side door is open ")
ret_val = true
end
if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then
BASE:T(unit_name .. " front door(s) are open")
ret_val = true
end
if ret_val == false then
BASE:T(unit_name .. " all doors are closed")
end
return ret_val
end -- nil
return nil
end
--- Function to generate valid FM frequencies in mHz for radio beacons (FM).
-- @return #table Table of frequencies.
function UTILS.GenerateFMFrequencies()
local FreeFMFrequencies = {}
for _first = 3, 7 do
for _second = 0, 5 do
for _third = 0, 9 do
local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit
table.insert(FreeFMFrequencies, _frequency)
end
end
end
return FreeFMFrequencies
end
--- Function to generate valid VHF frequencies in kHz for radio beacons (FM).
-- @return #table VHFrequencies
function UTILS.GenerateVHFrequencies()
-- known and sorted map-wise NDBs in kHz
local _skipFrequencies = {
214,274,291.5,295,297.5,
300.5,304,307,309.5,311,312,312.5,316,
320,324,328,329,330,336,337,
342,343,348,351,352,353,358,
363,365,368,372.5,374,
380,381,384,389,395,396,
414,420,430,432,435,440,450,455,462,470,485,
507,515,520,525,528,540,550,560,570,577,580,
602,625,641,662,670,680,682,690,
705,720,722,730,735,740,745,750,770,795,
822,830,862,866,
905,907,920,935,942,950,995,
1000,1025,1030,1050,1065,1116,1175,1182,1210
}
local FreeVHFFrequencies = {}
-- first range
local _start = 200000
while _start < 400000 do
-- skip existing NDB frequencies#
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(FreeVHFFrequencies, _start)
end
_start = _start + 10000
end
-- second range
_start = 400000
while _start < 850000 do
-- skip existing NDB frequencies
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(FreeVHFFrequencies, _start)
end
_start = _start + 10000
end
-- third range
_start = 850000
while _start <= 999000 do -- adjusted for Gazelle
-- skip existing NDB frequencies
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(FreeVHFFrequencies, _start)
end
_start = _start + 50000
end
return FreeVHFFrequencies
end
--- Function to generate valid UHF Frequencies in mHz (AM).
-- @return #table UHF Frequencies
function UTILS.GenerateUHFrequencies()
local FreeUHFFrequencies = {}
local _start = 220000000
while _start < 399000000 do
table.insert(FreeUHFFrequencies, _start)
_start = _start + 500000
end
return FreeUHFFrequencies
end
--- Function to generate valid laser codes for JTAC.
-- @return #table Laser Codes.
function UTILS.GenerateLaserCodes()
local jtacGeneratedLaserCodes = {}
-- helper function
local function ContainsDigit(_number, _numberToFind)
local _thisNumber = _number
local _thisDigit = 0
while _thisNumber ~= 0 do
_thisDigit = _thisNumber % 10
_thisNumber = math.floor(_thisNumber / 10)
if _thisDigit == _numberToFind then
return true
end
end
return false
end
-- generate list of laser codes
local _code = 1111
local _count = 1
while _code < 1777 and _count < 30 do
while true do
_code = _code + 1
if not self:_ContainsDigit(_code, 8)
and not ContainsDigit(_code, 9)
and not ContainsDigit(_code, 0) then
table.insert(jtacGeneratedLaserCodes, _code)
break
end
end
_count = _count + 1
end
return jtacGeneratedLaserCodes
end

View File

@@ -73,7 +73,7 @@ AIRBASE = {
--- Enumeration to identify the airbases in the Caucasus region. --- 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.Gelendzhik
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky -- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
@@ -122,7 +122,7 @@ AIRBASE.Caucasus = {
["Beslan"] = "Beslan", ["Beslan"] = "Beslan",
} }
--- These are all airbases of Nevada: --- Airbases of the Nevada map:
-- --
-- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Creech_AFB
-- * AIRBASE.Nevada.Groom_Lake_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB
@@ -141,6 +141,7 @@ AIRBASE.Caucasus = {
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
-- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Airport
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
--
-- @field Nevada -- @field Nevada
AIRBASE.Nevada = { AIRBASE.Nevada = {
["Creech_AFB"] = "Creech AFB", ["Creech_AFB"] = "Creech AFB",
@@ -162,7 +163,7 @@ AIRBASE.Nevada = {
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", ["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.Saint_Pierre_du_Mont
-- * AIRBASE.Normandy.Lignerolles -- * AIRBASE.Normandy.Lignerolles
@@ -195,6 +196,7 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Funtington
-- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Tangmere
-- * AIRBASE.Normandy.Ford_AF -- * AIRBASE.Normandy.Ford_AF
--
-- @field Normandy -- @field Normandy
AIRBASE.Normandy = { AIRBASE.Normandy = {
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
@@ -237,7 +239,7 @@ AIRBASE.Normandy = {
["Conches"] = "Conches", ["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_Dhabi_International_Airport
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
@@ -268,6 +270,7 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Sirri_Island
-- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Island_AFB
-- * AIRBASE.PersianGulf.Tunb_Kochak -- * AIRBASE.PersianGulf.Tunb_Kochak
--
-- @field PersianGulf -- @field PersianGulf
AIRBASE.PersianGulf = { AIRBASE.PersianGulf = {
["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl",
@@ -301,7 +304,7 @@ AIRBASE.PersianGulf = {
["Tunb_Kochak"] = "Tunb Kochak", ["Tunb_Kochak"] = "Tunb Kochak",
} }
--- These are all airbases of the The Channel Map: --- Airbases of The Channel Map:
-- --
-- * AIRBASE.TheChannel.Abbeville_Drucat -- * AIRBASE.TheChannel.Abbeville_Drucat
-- * AIRBASE.TheChannel.Merville_Calonne -- * AIRBASE.TheChannel.Merville_Calonne
@@ -326,7 +329,7 @@ AIRBASE.TheChannel = {
["High_Halden"] = "High Halden", ["High_Halden"] = "High Halden",
} }
--- Airbases of Syria --- Airbases of the Syria map:
-- --
-- * AIRBASE.Syria.Kuweires -- * AIRBASE.Syria.Kuweires
-- * AIRBASE.Syria.Marj_Ruhayyil -- * AIRBASE.Syria.Marj_Ruhayyil
@@ -336,39 +339,51 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Incirlik -- * AIRBASE.Syria.Incirlik
-- * AIRBASE.Syria.Damascus -- * AIRBASE.Syria.Damascus
-- * AIRBASE.Syria.Bassel_Al_Assad -- * AIRBASE.Syria.Bassel_Al_Assad
-- * AIRBASE.Syria.Rosh_Pina
-- * AIRBASE.Syria.Aleppo -- * AIRBASE.Syria.Aleppo
-- * AIRBASE.Syria.Qabr_as_Sitt -- * AIRBASE.Syria.Al_Qusayr
-- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Al_Dumayr -- * AIRBASE.Syria.Al_Dumayr
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Ru_Convoy_4
-- * AIRBASE.Syria.Hatay -- * AIRBASE.Syria.Hatay
-- * AIRBASE.Syria.Nicosia
-- * AIRBASE.Syria.Pinarbashi
-- * AIRBASE.Syria.Paphos
-- * AIRBASE.Syria.Kingsfield
-- * AIRBASE.Syria.Thalah
-- * AIRBASE.Syria.Haifa -- * AIRBASE.Syria.Haifa
-- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Khalkhalah
-- * AIRBASE.Syria.Megiddo -- * AIRBASE.Syria.Megiddo
-- * AIRBASE.Syria.Lakatamia
-- * AIRBASE.Syria.Rayak -- * AIRBASE.Syria.Rayak
-- * AIRBASE.Syria.Larnaca
-- * AIRBASE.Syria.Mezzeh -- * AIRBASE.Syria.Mezzeh
-- * AIRBASE.Syria.King_Hussein_Air_College -- * AIRBASE.Syria.Gecitkale
-- * AIRBASE.Syria.Jirah -- * 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.Taftanaz
-- * AIRBASE.Syria.H4
-- * AIRBASE.Syria.King_Hussein_Air_College
-- * AIRBASE.Syria.Rene_Mouawad -- * AIRBASE.Syria.Rene_Mouawad
-- * AIRBASE.Syria.Jirah
-- * AIRBASE.Syria.Ramat_David -- * AIRBASE.Syria.Ramat_David
-- * AIRBASE.Syria.Qabr_as_Sitt
-- * AIRBASE.Syria.Minakh -- * AIRBASE.Syria.Minakh
-- * AIRBASE.Syria.Adana_Sakirpasa -- * AIRBASE.Syria.Adana_Sakirpasa
-- * AIRBASE.Syria.Marj_as_Sultan_South
-- * AIRBASE.Syria.Hama
-- * AIRBASE.Syria.Al_Qusayr
-- * AIRBASE.Syria.Palmyra -- * AIRBASE.Syria.Palmyra
-- * AIRBASE.Syria.Hama
-- * AIRBASE.Syria.Ercan
-- * AIRBASE.Syria.Marj_as_Sultan_South
-- * AIRBASE.Syria.Tabqa -- * AIRBASE.Syria.Tabqa
-- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.Beirut_Rafic_Hariri
-- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.An_Nasiriyah
-- * AIRBASE.Syria.Abu_al_Duhur -- * 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={ AIRBASE.Syria={
@@ -380,39 +395,71 @@ AIRBASE.Syria={
["Incirlik"]="Incirlik", ["Incirlik"]="Incirlik",
["Damascus"]="Damascus", ["Damascus"]="Damascus",
["Bassel_Al_Assad"]="Bassel Al-Assad", ["Bassel_Al_Assad"]="Bassel Al-Assad",
["Rosh_Pina"]="Rosh Pina",
["Aleppo"]="Aleppo", ["Aleppo"]="Aleppo",
["Qabr_as_Sitt"]="Qabr as Sitt", ["Al_Qusayr"]="Al Qusayr",
["Wujah_Al_Hajar"]="Wujah Al Hajar", ["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Al_Dumayr"]="Al-Dumayr", ["Al_Dumayr"]="Al-Dumayr",
["Gazipasa"]="Gazipasa",
["Ru_Convoy_4"]="Ru Convoy-4",
["Hatay"]="Hatay", ["Hatay"]="Hatay",
["Nicosia"]="Nicosia",
["Pinarbashi"]="Pinarbashi",
["Paphos"]="Paphos",
["Kingsfield"]="Kingsfield",
["Thalah"]="Tha'lah",
["Haifa"]="Haifa", ["Haifa"]="Haifa",
["Khalkhalah"]="Khalkhalah", ["Khalkhalah"]="Khalkhalah",
["Megiddo"]="Megiddo", ["Megiddo"]="Megiddo",
["Lakatamia"]="Lakatamia",
["Rayak"]="Rayak", ["Rayak"]="Rayak",
["Larnaca"]="Larnaca",
["Mezzeh"]="Mezzeh", ["Mezzeh"]="Mezzeh",
["King_Hussein_Air_College"]="King Hussein Air College", ["Gecitkale"]="Gecitkale",
["Jirah"]="Jirah", ["Akrotiri"]="Akrotiri",
["Naqoura"]="Naqoura",
["Gaziantep"]="Gaziantep",
["Sayqal"]="Sayqal",
["Tiyas"]="Tiyas",
["Shayrat"]="Shayrat",
["Taftanaz"]="Taftanaz", ["Taftanaz"]="Taftanaz",
["H4"]="H4",
["King_Hussein_Air_College"]="King Hussein Air College",
["Rene_Mouawad"]="Rene Mouawad", ["Rene_Mouawad"]="Rene Mouawad",
["Jirah"]="Jirah",
["Ramat_David"]="Ramat David", ["Ramat_David"]="Ramat David",
["Qabr_as_Sitt"]="Qabr as Sitt",
["Minakh"]="Minakh", ["Minakh"]="Minakh",
["Adana_Sakirpasa"]="Adana Sakirpasa", ["Adana_Sakirpasa"]="Adana Sakirpasa",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Hama"]="Hama",
["Al_Qusayr"]="Al Qusayr",
["Palmyra"]="Palmyra", ["Palmyra"]="Palmyra",
["Hama"]="Hama",
["Ercan"]="Ercan",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Tabqa"]="Tabqa", ["Tabqa"]="Tabqa",
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
["An_Nasiriyah"]="An Nasiriyah", ["An_Nasiriyah"]="An Nasiriyah",
["Abu_al_Duhur"]="Abu al-Duhur", ["Abu_al_Duhur"]="Abu al-Duhur",
["H4"]="H4", }
["Gaziantep"]="Gaziantep",
["Rosh_Pina"]="Rosh Pina",
["Sayqal"]="Sayqal",
["Shayrat"]="Shayrat", --- Airbases of the Mariana Islands map:
["Tiyas"]="Tiyas", --
["Tha_lah"]="Tha'lah", -- * AIRBASE.MarianaIslands.Rota_Intl
["Naqoura"]="Naqoura", -- * 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",
} }
@@ -1372,7 +1419,8 @@ function AIRBASE:GetRunwayData(magvar, mark)
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport 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 -- 1-->4, 2-->3, 3-->2, 4-->1
exception=1 exception=1

View File

@@ -177,9 +177,6 @@
-- ## 5.5) Air-2-Air missile attack range: -- ## 5.5) Air-2-Air missile attack range:
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . -- * @{#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.
--
-- @field #CONTROLLABLE -- @field #CONTROLLABLE
CONTROLLABLE = { CONTROLLABLE = {
ClassName = "CONTROLLABLE", ClassName = "CONTROLLABLE",
@@ -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. --- (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. -- 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. -- 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 #CONTROLLABLE self
-- @param Wrapper.Group#GROUP AttackGroup Target GROUP object. -- @param Wrapper.Group#GROUP AttackGroup Target GROUP object.
-- @param #number WeaponType Bitmask of weapon types, which are allowed to use. -- @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 #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 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 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**. -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**.
-- @return DCS#Task The DCS task structure. -- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber ) function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber )
@@ -1853,7 +1851,26 @@ do -- Patrol methods
-- Calculate the new Route. -- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate() 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 ) table.insert( Waypoints, 1, From )
@@ -1894,7 +1911,16 @@ do -- Patrol methods
if ToWaypoint then if ToWaypoint then
FromWaypoint = ToWaypoint FromWaypoint = ToWaypoint
end 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. -- 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 -- 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. -- 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 } ) 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 -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {} local Route = {}
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] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
end
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint )
@@ -1956,15 +1986,30 @@ do -- Patrol methods
-- Calculate the new Route. -- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate() 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. -- Select a random Zone and get the Coordinate of the new Zone.
local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
local ToCoord = RandomZone:GetRandomCoordinate( 10 ) local ToCoord = RandomZone:GetRandomCoordinate( 10 )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {} local Route = {}
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] = FromCoord:WaypointGround( Speed, Formation )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
end
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax ) local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax )
@@ -3772,10 +3817,30 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
local Controller = self:_GetController() local Controller = self:_GetController()
if Controller then if Controller then
if self:IsGround() 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
end end
return self return self
end end
return nil return nil
end 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

@@ -2551,9 +2551,10 @@ do -- Players
end end
--- GROUND - Switch on/off radar emissions --- GROUND - Switch on/off radar emissions for the group.
-- @param #GROUP self -- @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) function GROUP:EnableEmission(switch)
self:F2( self.GroupName ) self:F2( self.GroupName )
local switch = switch or false local switch = switch or false
@@ -2566,6 +2567,31 @@ function GROUP:EnableEmission(switch)
end 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 )
local switch = switch or false
local SetInvisible = {id = 'SetInvisible', params = {value = true}}
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 )
local switch = switch or false
local SetInvisible = {id = 'SetImmortal', params = {value = true}}
self:SetCommand(SetInvisible)
return self
end end
--do -- Smoke --do -- Smoke

View File

@@ -580,6 +580,18 @@ function UNIT:GetAmmo()
return nil return nil
end 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. --- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has.
-- @param #UNIT self -- @param #UNIT self
-- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles. -- @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 return nil
end 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}. --- Returns a list of one @{Wrapper.Unit}.
-- @param #UNIT self -- @param #UNIT self
@@ -1429,9 +1407,10 @@ function UNIT:GetTemplateFuel()
return nil return nil
end end
--- GROUND - Switch on/off radar emissions. --- GROUND - Switch on/off radar emissions of a unit.
-- @param #UNIT self -- @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) function UNIT:EnableEmission(switch)
self:F2( self.UnitName ) self:F2( self.UnitName )
@@ -1445,4 +1424,5 @@ function UNIT:EnableEmission(switch)
end end
return self
end end

View File

@@ -3,10 +3,12 @@ Utilities/Routines.lua
Utilities/Utils.lua Utilities/Utils.lua
Utilities/Enums.lua Utilities/Enums.lua
Utilities/Profiler.lua Utilities/Profiler.lua
Utilities/Templates.lua
Utilities/STTS.lua
Core/Base.lua Core/Base.lua
Core/Beacon.lua
Core/UserFlag.lua Core/UserFlag.lua
Core/UserSound.lua
Core/Report.lua Core/Report.lua
Core/Scheduler.lua Core/Scheduler.lua
Core/ScheduleDispatcher.lua Core/ScheduleDispatcher.lua
@@ -21,9 +23,6 @@ Core/Point.lua
Core/Velocity.lua Core/Velocity.lua
Core/Message.lua Core/Message.lua
Core/Fsm.lua Core/Fsm.lua
Core/Radio.lua
Core/RadioQueue.lua
Core/RadioSpeech.lua
Core/Spawn.lua Core/Spawn.lua
Core/SpawnStatic.lua Core/SpawnStatic.lua
Core/Timer.lua Core/Timer.lua
@@ -75,6 +74,8 @@ Ops/Airboss.lua
Ops/RecoveryTanker.lua Ops/RecoveryTanker.lua
Ops/RescueHelo.lua Ops/RescueHelo.lua
Ops/ATIS.lua Ops/ATIS.lua
Ops/CTLD.lua
Ops/CSAR.lua
AI/AI_Balancer.lua AI/AI_Balancer.lua
AI/AI_Air.lua AI/AI_Air.lua
@@ -113,6 +114,13 @@ Actions/Act_Route.lua
Actions/Act_Account.lua Actions/Act_Account.lua
Actions/Act_Assist.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/CommandCenter.lua
Tasking/Mission.lua Tasking/Mission.lua
Tasking/Task.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 ### [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 ### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use