mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33d761503d | ||
|
|
b52272e18e | ||
|
|
70fa1cf19f | ||
|
|
7ca7caea75 | ||
|
|
277c26821e | ||
|
|
22826b4cd1 | ||
|
|
8cc1c24b64 | ||
|
|
0db35a0e9f | ||
|
|
b4707bb3eb | ||
|
|
bbfc2e9ed7 | ||
|
|
2fb0ab1aed | ||
|
|
6fbf050b72 | ||
|
|
29210f670c | ||
|
|
18b45c9621 | ||
|
|
9356d67d74 | ||
|
|
5bb2b05f71 | ||
|
|
89308f7d06 | ||
|
|
353d6dfec0 | ||
|
|
3f5e322948 | ||
|
|
0e8732fd44 | ||
|
|
77a3c7369d | ||
|
|
8d8070bbd7 | ||
|
|
72df687ce7 | ||
|
|
246935622c | ||
|
|
078573629d | ||
|
|
973b8323c9 | ||
|
|
6d61c5ee94 | ||
|
|
3e8db6a1fa | ||
|
|
832d6b1c08 | ||
|
|
e7936950f4 | ||
|
|
831e986ee8 | ||
|
|
8a44fae3d4 | ||
|
|
0c9390914a | ||
|
|
4fa525a4ae | ||
|
|
858b00336b | ||
|
|
82d78c98bb | ||
|
|
06c3f7998b | ||
|
|
e03e87f501 | ||
|
|
cf83abfe90 | ||
|
|
5a00f461e9 | ||
|
|
bb43b08190 | ||
|
|
05f95796f6 | ||
|
|
cfcd7d7588 | ||
|
|
4c3d44a63b | ||
|
|
fcd75a34eb | ||
|
|
4827b73bb1 | ||
|
|
3129c8c8ea | ||
|
|
6b747e924b | ||
|
|
e55bb21401 | ||
|
|
7a3fb95851 | ||
|
|
d39074bf3e | ||
|
|
0410ae6877 |
442
Moose Development/Moose/Core/Beacon.lua
Normal file
442
Moose Development/Moose/Core/Beacon.lua
Normal 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
|
||||
@@ -236,6 +236,7 @@ do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string PlayerName (Optional) Set settings for this player.
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:Set( PlayerName )
|
||||
|
||||
|
||||
@@ -3408,8 +3408,8 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
if Stamp.Vec2 then
|
||||
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
|
||||
local NewVec2 = SpawnUnit:GetVec2()
|
||||
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
|
||||
-- If the plane is not moving, and is on the ground, assign it with a timestamp...
|
||||
if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then
|
||||
-- If the plane is not moving or dead , and is on the ground, assign it with a timestamp...
|
||||
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
|
||||
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
|
||||
self:ReSpawn( SpawnCursor )
|
||||
@@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
else
|
||||
if SpawnUnit:InAir() == false then
|
||||
Stamp.Vec2 = SpawnUnit:GetVec2()
|
||||
if SpawnUnit:GetVelocityKMH() < 1 then
|
||||
if (SpawnUnit:GetVelocityKMH() < 1) then
|
||||
Stamp.Time = timer.getTime()
|
||||
end
|
||||
else
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5786,6 +5786,8 @@ end
|
||||
-- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops.
|
||||
-- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero.
|
||||
--
|
||||
-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups.
|
||||
--
|
||||
-- ## Example
|
||||
-- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft
|
||||
-- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours.
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## 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 +91,9 @@
|
||||
-- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb.
|
||||
-- @field #boolean autosave If true, automatically save results every X seconds.
|
||||
-- @field #number instructorfreq Frequency on which the range control transmitts.
|
||||
-- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
|
||||
-- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
|
||||
-- @field #number rangecontrolfreq Frequency on which the range control transmitts.
|
||||
-- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
|
||||
-- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
|
||||
-- @field #string rangecontrolrelayname Name of relay unit.
|
||||
-- @field #string instructorrelayname Name of relay unit.
|
||||
-- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/".
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: April 2021
|
||||
--
|
||||
-- Last Update: July 2021
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
@@ -39,38 +39,21 @@
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75 -- default 75% engagement range Feature Request #1355
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75 -- default 75% engagement range Feature Request #1355
|
||||
}
|
||||
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
--[[
|
||||
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
|
||||
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
|
||||
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
|
||||
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
|
||||
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
|
||||
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
|
||||
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
|
||||
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
|
||||
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
|
||||
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
|
||||
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
|
||||
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
|
||||
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
|
||||
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
@@ -97,20 +80,20 @@ SEAD = {
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
self:I("*** SEAD - Started Version 0.2.7")
|
||||
return self
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
self:I("*** SEAD - Started Version 0.2.8")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update the active SEAD Set
|
||||
@@ -164,112 +147,83 @@ end
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
self:T({ SEADWeapon })
|
||||
|
||||
--[[check for SEAD missiles
|
||||
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
local _targetskill = "Random"
|
||||
local _targetMimgroupName = "none"
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
local _targetMimgroup = _targetUnit:GetGroup()
|
||||
local _targetMimgroupName = _targetMimgroup:GetName() -- group name
|
||||
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
--tracker ID table to switch groups off and on again
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
local _targetMimgroup = _targetUnit:GetGroup()
|
||||
local _targetMimgroupName = _targetMimgroup:GetName() -- group name
|
||||
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
--tracker ID table to switch groups off and on again
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local function SuppressionEnd(id) --switch group back on
|
||||
local range = self.EngagementRange -- Feature Request #1355
|
||||
self:T(string.format("*** SEAD - Engagement Range is %d", range))
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
--id.groupName:enableEmission(true)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
|
||||
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
|
||||
end
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
local SuppressionEndTime = timer.getTime() + delay
|
||||
--create entry
|
||||
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet
|
||||
self.SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = delay
|
||||
}
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
--_targetMimgroup:enableEmission(false)
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function SuppressionEnd(id) --switch group back on
|
||||
local range = self.EngagementRange -- Feature Request #1355
|
||||
self:T(string.format("*** SEAD - Engagement Range is %d", range))
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
--id.groupName:enableEmission(true)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
|
||||
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
|
||||
end
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
local SuppressionEndTime = timer.getTime() + delay
|
||||
--create entry
|
||||
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet
|
||||
self.SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = delay
|
||||
}
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
--_targetMimgroup:enableEmission(false)
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: May 2021
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -108,22 +108,6 @@ do
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
--[[
|
||||
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
|
||||
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
|
||||
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
|
||||
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
|
||||
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
|
||||
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
|
||||
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
|
||||
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
|
||||
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
|
||||
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
|
||||
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
|
||||
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
|
||||
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
|
||||
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
@@ -157,7 +141,9 @@ do
|
||||
-- @param #number Radius Defense radius in meters, used to switch on groups
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition)
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
@@ -171,22 +157,23 @@ do
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
self.DefendHarms = true
|
||||
self.DefendMavs = true
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.5")
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.8")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initially set all groups to alarm state GREEN
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:_InitState()
|
||||
self:T(self.lid .. " _InitState")
|
||||
local table = {}
|
||||
local set = self.Groupset
|
||||
self:T({set = set})
|
||||
@@ -195,31 +182,48 @@ do
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOff()
|
||||
_group:EnableEmission(false)
|
||||
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
|
||||
else
|
||||
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
|
||||
end
|
||||
_group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
-- gather entropy
|
||||
for i=1,10 do
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(debug)
|
||||
self:T( { debug } )
|
||||
local onoff = debug or false
|
||||
if debug then
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
self.SwitchDebugOff()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOn()
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state off
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOff()
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for HARMs
|
||||
@@ -229,6 +233,7 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
@@ -238,6 +243,7 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
@@ -256,35 +262,42 @@ do
|
||||
end
|
||||
self.DefenseLowProb = low
|
||||
self.DefenseHighProb = high
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of seconds a SHORAD site will stay active
|
||||
-- @param #SHORAD self
|
||||
-- @param #number seconds Number of seconds systems stay active
|
||||
function SHORAD:SetActiveTimer(seconds)
|
||||
self:T(self.lid .. " SetActiveTimer")
|
||||
local timer = seconds or 600
|
||||
if timer < 0 then
|
||||
timer = 600
|
||||
end
|
||||
self.ActiveTimer = timer
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of meters for the SHORAD defense zone
|
||||
-- @param #SHORAD self
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a HARM was fired
|
||||
@@ -292,6 +305,7 @@ do
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
@@ -307,6 +321,7 @@ do
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
@@ -322,6 +337,7 @@ do
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
@@ -344,6 +360,7 @@ do
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
@@ -352,7 +369,7 @@ do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
@@ -363,6 +380,7 @@ do
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
@@ -381,6 +399,7 @@ do
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
@@ -405,6 +424,7 @@ do
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
@@ -442,7 +462,7 @@ do
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:SetAIOn()
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
@@ -454,14 +474,15 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
function SHORAD:OnEventShot( EventData )
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
--local ShootingUnit = EventData.IniDCSUnit
|
||||
--local ShootingUnitName = EventData.IniDCSUnitName
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
@@ -524,4 +545,4 @@ do
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
-----------------------------------------------------------------------
|
||||
@@ -1,5 +1,4 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- GLOBALS: The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
@@ -10,9 +9,37 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
|
||||
--- Register zones.
|
||||
_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
|
||||
|
||||
@@ -3,10 +3,11 @@ __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
|
||||
@@ -21,9 +22,6 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
@@ -75,6 +73,8 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
@@ -113,6 +113,13 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
-- * Option to present information in imperial or metric units
|
||||
-- * Runway length and airfield elevation (optional)
|
||||
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional)
|
||||
-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -34,7 +35,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel.
|
||||
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -59,7 +60,7 @@
|
||||
-- @field #number frequency Radio frequency in MHz.
|
||||
-- @field #number modulation Radio modulation 0=AM or 1=FM.
|
||||
-- @field #number power Radio power in Watts. Default 100 W.
|
||||
-- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages.
|
||||
-- @field Sound.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages.
|
||||
-- @field #string soundpath Path to sound files.
|
||||
-- @field #string relayunitname Name of the radio relay unit.
|
||||
-- @field #table towerfrequency Table with tower frequencies.
|
||||
@@ -88,6 +89,9 @@
|
||||
-- @field #boolean usemarker Use mark on the F10 map.
|
||||
-- @field #number markerid Numerical ID of the F10 map mark point.
|
||||
-- @field #number relHumidity Relative humidity (used to approximately calculate the dew point).
|
||||
-- @field #boolean useSRS If true, use SRS for transmission.
|
||||
-- @field Sound.SRS#MSRS msrs Moose SRS object.
|
||||
-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@@ -252,6 +256,16 @@
|
||||
-- # Marks on the F10 Map
|
||||
--
|
||||
-- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature).
|
||||
--
|
||||
-- # Text-To-Speech
|
||||
--
|
||||
-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing.
|
||||
-- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented.
|
||||
--
|
||||
-- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file.
|
||||
--
|
||||
-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before
|
||||
-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds.
|
||||
--
|
||||
-- # Examples
|
||||
--
|
||||
@@ -283,7 +297,14 @@
|
||||
-- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2})
|
||||
-- atisAbuDhabi:SetVOR(114.25)
|
||||
-- atisAbuDhabi:Start()
|
||||
--
|
||||
-- ## SRS
|
||||
--
|
||||
-- atis=ATIS:New("Batumi", 305, radio.modulation.AM)
|
||||
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US")
|
||||
-- atis:Start()
|
||||
--
|
||||
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
|
||||
--
|
||||
-- @field #ATIS
|
||||
ATIS = {
|
||||
@@ -368,6 +389,7 @@ ATIS.Alphabet = {
|
||||
-- @field #number PersianGulf +2° (East).
|
||||
-- @field #number TheChannel -10° (West).
|
||||
-- @field #number Syria +5° (East).
|
||||
-- @field #number MarianaIslands +2° (East).
|
||||
ATIS.RunwayM2T={
|
||||
Caucasus=0,
|
||||
Nevada=12,
|
||||
@@ -375,6 +397,7 @@ ATIS.RunwayM2T={
|
||||
PersianGulf=2,
|
||||
TheChannel=-10,
|
||||
Syria=5,
|
||||
MarianaIslands=2,
|
||||
}
|
||||
|
||||
--- Whether ICAO phraseology is used for ATIS broadcasts.
|
||||
@@ -385,6 +408,7 @@ ATIS.RunwayM2T={
|
||||
-- @field #boolean PersianGulf true.
|
||||
-- @field #boolean TheChannel true.
|
||||
-- @field #boolean Syria true.
|
||||
-- @field #boolean MarianaIslands true.
|
||||
ATIS.ICAOPhraseology={
|
||||
Caucasus=true,
|
||||
Nevada=false,
|
||||
@@ -392,6 +416,7 @@ ATIS.ICAOPhraseology={
|
||||
PersianGulf=true,
|
||||
TheChannel=true,
|
||||
Syria=true,
|
||||
MarianaIslands=true,
|
||||
}
|
||||
|
||||
--- Nav point data.
|
||||
@@ -564,7 +589,7 @@ _ATIS={}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version="0.9.1"
|
||||
ATIS.version="0.9.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -628,6 +653,7 @@ function ATIS:New(airbasename, frequency, modulation)
|
||||
self:SetAltimeterQNH(true)
|
||||
self:SetMapMarks(false)
|
||||
self:SetRelativeHumidity()
|
||||
self:SetQueueUpdateTime()
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
@@ -955,7 +981,9 @@ end
|
||||
-- * 170° on the Normany map
|
||||
-- * 182° on the Persian Gulf map
|
||||
--
|
||||
-- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation.
|
||||
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
|
||||
--
|
||||
-- Or you make your life simple and just include the sign so you don't have to bother about East/West.
|
||||
--
|
||||
-- @param #ATIS self
|
||||
-- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}.
|
||||
@@ -1100,6 +1128,44 @@ function ATIS:MarkRunways(markall)
|
||||
end
|
||||
end
|
||||
|
||||
--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary.
|
||||
-- @param #ATIS self
|
||||
-- @param #string PathToSRS Path to SRS directory.
|
||||
-- @param #string Gender Gender: "male" or "female" (default).
|
||||
-- @param #string Culture Culture, e.g. "en-GB" (default).
|
||||
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`.
|
||||
-- @param #number Port SRS port. Default 5002.
|
||||
-- @return #ATIS self
|
||||
function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port)
|
||||
self.useSRS=true
|
||||
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
|
||||
self.msrs:SetGender(Gender)
|
||||
self.msrs:SetCulture(Culture)
|
||||
self.msrs:SetVoice(Voice)
|
||||
self.msrs:SetPort(Port)
|
||||
self.msrs:SetCoalition(self:GetCoalition())
|
||||
if self.dTQueueCheck<=10 then
|
||||
self:SetQueueUpdateTime(90)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the time interval between radio queue updates.
|
||||
-- @param #ATIS self
|
||||
-- @param #number TimeInterval Interval in seconds. Default 5 sec.
|
||||
-- @return #ATIS self
|
||||
function ATIS:SetQueueUpdateTime(TimeInterval)
|
||||
self.dTQueueCheck=TimeInterval or 5
|
||||
end
|
||||
|
||||
--- Get the coalition of the associated airbase.
|
||||
-- @param #ATIS self
|
||||
-- @return #number Coalition of the associcated airbase.
|
||||
function ATIS:GetCoalition()
|
||||
local coal=self.airbase and self.airbase:GetCoalition() or nil
|
||||
return coal
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1146,6 +1212,10 @@ function ATIS:onafterStart(From, Event, To)
|
||||
|
||||
-- Start radio queue.
|
||||
self.radioqueue:Start(1, 0.1)
|
||||
|
||||
-- Handle airbase capture
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.BaseCaptured)
|
||||
|
||||
-- Init status updates.
|
||||
self:__Status(-2)
|
||||
@@ -1171,7 +1241,13 @@ function ATIS:onafterStatus(From, Event, To)
|
||||
end
|
||||
|
||||
-- Info text.
|
||||
local text=string.format("State %s: Freq=%.3f MHz %s, Relay unit=%s (alive=%s)", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation), tostring(self.relayunitname), relayunitstatus)
|
||||
local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation))
|
||||
if self.useSRS then
|
||||
text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s",
|
||||
tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice))
|
||||
else
|
||||
text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
|
||||
self:__Status(-60)
|
||||
@@ -1188,15 +1264,25 @@ end
|
||||
-- @param #string To To state.
|
||||
function ATIS:onafterCheckQueue(From, Event, To)
|
||||
|
||||
if #self.radioqueue.queue==0 then
|
||||
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
|
||||
if self.useSRS then
|
||||
|
||||
self:Broadcast()
|
||||
|
||||
else
|
||||
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
|
||||
|
||||
if #self.radioqueue.queue==0 then
|
||||
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
|
||||
self:Broadcast()
|
||||
else
|
||||
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
-- Check back in 5 seconds.
|
||||
self:__CheckQueue(-5)
|
||||
-- Check back in 5 seconds.
|
||||
self:__CheckQueue(-math.abs(self.dTQueueCheck))
|
||||
end
|
||||
|
||||
--- Broadcast ATIS radio message.
|
||||
@@ -1322,11 +1408,14 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
|
||||
if time < 0 then
|
||||
time = 24*60*60 + time --avoid negative time around midnight
|
||||
end
|
||||
end
|
||||
|
||||
local clock=UTILS.SecondsToClock(time)
|
||||
local zulu=UTILS.Split(clock, ":")
|
||||
local ZULU=string.format("%s%s", zulu[1], zulu[2])
|
||||
if self.useSRS then
|
||||
ZULU=string.format("%s hours", zulu[1])
|
||||
end
|
||||
|
||||
|
||||
-- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc.
|
||||
@@ -1346,10 +1435,17 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
local sunrise=coord:GetSunrise()
|
||||
sunrise=UTILS.Split(sunrise, ":")
|
||||
local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2])
|
||||
if self.useSRS then
|
||||
SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2])
|
||||
end
|
||||
|
||||
local sunset=coord:GetSunset()
|
||||
sunset=UTILS.Split(sunset, ":")
|
||||
local SUNSET=string.format("%s%s", sunset[1], sunset[2])
|
||||
if self.useSRS then
|
||||
SUNSET=string.format("%s %s hours", sunset[1], sunset[2])
|
||||
end
|
||||
|
||||
|
||||
---------------------------------
|
||||
--- Temperature and Dew Point ---
|
||||
@@ -1521,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
-- Scattered 4
|
||||
clouddens=4
|
||||
elseif cloudspreset:find("RainyPreset") then
|
||||
-- Overcast + Rain
|
||||
clouddens=9
|
||||
if temperature>5 then
|
||||
precepitation=1 -- rain
|
||||
else
|
||||
precepitation=3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find("RainyPreset1") then
|
||||
-- Overcast + Rain
|
||||
clouddens=9
|
||||
if temperature>5 then
|
||||
precepitation=1 -- rain
|
||||
else
|
||||
precepitation=3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find("RainyPreset2") then
|
||||
-- Overcast + Rain
|
||||
clouddens=9
|
||||
if temperature>5 then
|
||||
precepitation=1 -- rain
|
||||
else
|
||||
precepitation=3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find("RainyPreset3") then
|
||||
-- Overcast + Rain
|
||||
clouddens=9
|
||||
if temperature>5 then
|
||||
@@ -1591,36 +1711,46 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then
|
||||
subtitle=subtitle.." Airport"
|
||||
end
|
||||
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
|
||||
if not self.useSRS then
|
||||
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
|
||||
end
|
||||
local alltext=subtitle
|
||||
|
||||
-- Information tag
|
||||
subtitle=string.format("Information %s", NATO)
|
||||
local _INFORMATION=subtitle
|
||||
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
|
||||
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
|
||||
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
-- Zulu Time
|
||||
subtitle=string.format("%s Zulu", ZULU)
|
||||
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
|
||||
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
|
||||
if not self.useSRS then
|
||||
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
|
||||
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
if not self.zulutimeonly then
|
||||
|
||||
-- Sunrise Time
|
||||
subtitle=string.format("Sunrise at %s local time", SUNRISE)
|
||||
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
|
||||
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
|
||||
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
|
||||
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
|
||||
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
-- Sunset Time
|
||||
subtitle=string.format("Sunset at %s local time", SUNSET)
|
||||
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
|
||||
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
|
||||
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
|
||||
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
|
||||
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
@@ -1634,17 +1764,19 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
subtitle=subtitle..", gusting"
|
||||
end
|
||||
local _WIND=subtitle
|
||||
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(WINDFROM)
|
||||
self:Transmission(ATIS.Sound.At, 0.2)
|
||||
self.radioqueue:Number2Transmission(WINDSPEED)
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Knots, 0.2)
|
||||
end
|
||||
if turbulence>0 then
|
||||
self:Transmission(ATIS.Sound.Gusting, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(WINDFROM)
|
||||
self:Transmission(ATIS.Sound.At, 0.2)
|
||||
self.radioqueue:Number2Transmission(WINDSPEED)
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Knots, 0.2)
|
||||
end
|
||||
if turbulence>0 then
|
||||
self:Transmission(ATIS.Sound.Gusting, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
@@ -1654,12 +1786,14 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
else
|
||||
subtitle=string.format("Visibility %s SM", VISIBILITY)
|
||||
end
|
||||
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(VISIBILITY)
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Kilometers, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(VISIBILITY)
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Kilometers, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
@@ -1699,57 +1833,67 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
-- Actual output
|
||||
if wp then
|
||||
subtitle=string.format("Weather phenomena:%s", wpsub)
|
||||
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
|
||||
if precepitation==1 then
|
||||
self:Transmission(ATIS.Sound.Rain, 0.5)
|
||||
elseif precepitation==2 then
|
||||
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
|
||||
elseif precepitation==3 then
|
||||
self:Transmission(ATIS.Sound.Snow, 0.5)
|
||||
elseif precepitation==4 then
|
||||
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
|
||||
end
|
||||
if fog then
|
||||
self:Transmission(ATIS.Sound.Fog, 0.5)
|
||||
end
|
||||
if dust then
|
||||
self:Transmission(ATIS.Sound.Dust, 0.5)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
|
||||
if precepitation==1 then
|
||||
self:Transmission(ATIS.Sound.Rain, 0.5)
|
||||
elseif precepitation==2 then
|
||||
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
|
||||
elseif precepitation==3 then
|
||||
self:Transmission(ATIS.Sound.Snow, 0.5)
|
||||
elseif precepitation==4 then
|
||||
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
|
||||
end
|
||||
if fog then
|
||||
self:Transmission(ATIS.Sound.Fog, 0.5)
|
||||
end
|
||||
if dust then
|
||||
self:Transmission(ATIS.Sound.Dust, 0.5)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- Cloud base
|
||||
self:Transmission(CloudCover, 1.0, CLOUDSsub)
|
||||
if not self.useSRS then
|
||||
self:Transmission(CloudCover, 1.0, CLOUDSsub)
|
||||
end
|
||||
if CLOUDBASE and static then
|
||||
-- Base
|
||||
local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100)
|
||||
local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100)
|
||||
if self.metric then
|
||||
subtitle=string.format("Cloudbase %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
|
||||
--subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
|
||||
subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil)
|
||||
else
|
||||
subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL)
|
||||
--subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL)
|
||||
subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil)
|
||||
end
|
||||
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
|
||||
if tonumber(CLOUDBASE1000)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDBASE1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(CLOUDBASE0100)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDBASE0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
-- Ceiling
|
||||
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
|
||||
if tonumber(CLOUDCEIL1000)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(CLOUDCEIL0100)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
|
||||
if tonumber(CLOUDBASE1000)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDBASE1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(CLOUDBASE0100)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDBASE0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
-- Ceiling
|
||||
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
|
||||
if tonumber(CLOUDCEIL1000)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(CLOUDCEIL0100)>0 then
|
||||
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
end
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
@@ -1769,15 +1913,17 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
end
|
||||
end
|
||||
local _TEMPERATURE=subtitle
|
||||
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
|
||||
if temperature<0 then
|
||||
self:Transmission(ATIS.Sound.Minus, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(TEMPERATURE)
|
||||
if self.TDegF then
|
||||
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
|
||||
if temperature<0 then
|
||||
self:Transmission(ATIS.Sound.Minus, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(TEMPERATURE)
|
||||
if self.TDegF then
|
||||
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
@@ -1796,15 +1942,17 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
end
|
||||
end
|
||||
local _DEWPOINT=subtitle
|
||||
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
|
||||
if dewpoint<0 then
|
||||
self:Transmission(ATIS.Sound.Minus, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(DEWPOINT)
|
||||
if self.TDegF then
|
||||
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
|
||||
if dewpoint<0 then
|
||||
self:Transmission(ATIS.Sound.Minus, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(DEWPOINT)
|
||||
if self.TDegF then
|
||||
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
@@ -1813,51 +1961,53 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
if self.qnhonly then
|
||||
subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2])
|
||||
else
|
||||
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
end
|
||||
else
|
||||
if self.metric then
|
||||
if self.qnhonly then
|
||||
subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2])
|
||||
else
|
||||
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
end
|
||||
else
|
||||
if self.qnhonly then
|
||||
subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2])
|
||||
else
|
||||
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
local _ALTIMETER=subtitle
|
||||
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
|
||||
if not self.qnhonly then
|
||||
self:Transmission(ATIS.Sound.QNH, 0.5)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QNH[1])
|
||||
|
||||
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QNH[2])
|
||||
|
||||
if not self.qnhonly then
|
||||
self:Transmission(ATIS.Sound.QFE, 0.75)
|
||||
self.radioqueue:Number2Transmission(QFE[1])
|
||||
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
|
||||
if not self.qnhonly then
|
||||
self:Transmission(ATIS.Sound.QNH, 0.5)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QFE[2])
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QNH[1])
|
||||
|
||||
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QNH[2])
|
||||
|
||||
if self.PmmHg then
|
||||
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
|
||||
else
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
|
||||
if not self.qnhonly then
|
||||
self:Transmission(ATIS.Sound.QFE, 0.75)
|
||||
self.radioqueue:Number2Transmission(QFE[1])
|
||||
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
end
|
||||
self.radioqueue:Number2Transmission(QFE[2])
|
||||
end
|
||||
|
||||
if self.PmmHg then
|
||||
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
|
||||
end
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
@@ -1870,12 +2020,14 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
subtitle=subtitle.." Right"
|
||||
end
|
||||
local _RUNACT=subtitle
|
||||
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(runway)
|
||||
if rwyLeft==true then
|
||||
self:Transmission(ATIS.Sound.Left, 0.2)
|
||||
elseif rwyLeft==false then
|
||||
self:Transmission(ATIS.Sound.Right, 0.2)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(runway)
|
||||
if rwyLeft==true then
|
||||
self:Transmission(ATIS.Sound.Left, 0.2)
|
||||
elseif rwyLeft==false then
|
||||
self:Transmission(ATIS.Sound.Right, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
@@ -1900,21 +2052,22 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
end
|
||||
|
||||
-- Transmit.
|
||||
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
|
||||
if tonumber(L1000)>0 then
|
||||
self.radioqueue:Number2Transmission(L1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
|
||||
if tonumber(L1000)>0 then
|
||||
self.radioqueue:Number2Transmission(L1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(L0100)>0 then
|
||||
self.radioqueue:Number2Transmission(L0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
end
|
||||
end
|
||||
if tonumber(L0100)>0 then
|
||||
self.radioqueue:Number2Transmission(L0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
end
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
@@ -1937,22 +2090,23 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
subtitle=subtitle.." feet"
|
||||
end
|
||||
|
||||
-- Transmitt.
|
||||
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
|
||||
if tonumber(L1000)>0 then
|
||||
self.radioqueue:Number2Transmission(L1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
-- Transmit.
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
|
||||
if tonumber(L1000)>0 then
|
||||
self.radioqueue:Number2Transmission(L1000)
|
||||
self:Transmission(ATIS.Sound.Thousand, 0.1)
|
||||
end
|
||||
if tonumber(L0100)>0 then
|
||||
self.radioqueue:Number2Transmission(L0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
end
|
||||
end
|
||||
if tonumber(L0100)>0 then
|
||||
self.radioqueue:Number2Transmission(L0100)
|
||||
self:Transmission(ATIS.Sound.Hundred, 0.1)
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission(ATIS.Sound.Meters, 0.1)
|
||||
else
|
||||
self:Transmission(ATIS.Sound.Feet, 0.1)
|
||||
end
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
@@ -1966,9 +2120,47 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
end
|
||||
end
|
||||
subtitle=string.format("Tower frequency %s", freqs)
|
||||
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
|
||||
for _,freq in pairs(self.towerfrequency) do
|
||||
local f=string.format("%.3f", freq)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
|
||||
for _,freq in pairs(self.towerfrequency) do
|
||||
local f=string.format("%.3f", freq)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
end
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- ILS
|
||||
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
|
||||
if ils then
|
||||
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ils.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- Outer NDB
|
||||
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
|
||||
if ndb then
|
||||
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ndb.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
@@ -1977,41 +2169,6 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
end
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- ILS
|
||||
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
|
||||
if ils then
|
||||
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
|
||||
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ils.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- Outer NDB
|
||||
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
|
||||
if ndb then
|
||||
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
|
||||
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ndb.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
@@ -2019,51 +2176,58 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft)
|
||||
if ndb then
|
||||
subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency)
|
||||
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ndb.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", ndb.frequency)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- VOR
|
||||
if self.vor then
|
||||
subtitle=string.format("VOR frequency %.2f MHz", self.vor)
|
||||
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", self.vor)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
if self.useSRS then
|
||||
subtitle=string.format("V O R frequency %.2f MHz", self.vor)
|
||||
end
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
|
||||
local f=string.format("%.2f", self.vor)
|
||||
f=UTILS.Split(f, ".")
|
||||
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
|
||||
if tonumber(f[2])>0 then
|
||||
self:Transmission(ATIS.Sound.Decimal, 0.2)
|
||||
self.radioqueue:Number2Transmission(f[2])
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
end
|
||||
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
|
||||
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- TACAN
|
||||
if self.tacan then
|
||||
subtitle=string.format("TACAN channel %dX", self.tacan)
|
||||
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
|
||||
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
|
||||
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
|
||||
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- RSBN
|
||||
if self.rsbn then
|
||||
subtitle=string.format("RSBN channel %d", self.rsbn)
|
||||
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
|
||||
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
@@ -2071,17 +2235,19 @@ function ATIS:onafterBroadcast(From, Event, To)
|
||||
local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft)
|
||||
if ndb then
|
||||
subtitle=string.format("PRMG channel %d", ndb.frequency)
|
||||
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
|
||||
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
end
|
||||
|
||||
-- Advice on initial...
|
||||
subtitle=string.format("Advise on initial contact, you have information %s", NATO)
|
||||
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
|
||||
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
|
||||
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
|
||||
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
|
||||
end
|
||||
alltext=alltext..";\n"..subtitle
|
||||
|
||||
-- Report ATIS text.
|
||||
@@ -2102,6 +2268,62 @@ end
|
||||
-- @param #string Text Report text.
|
||||
function ATIS:onafterReport(From, Event, To, Text)
|
||||
self:T(self.lid..string.format("Report:\n%s", Text))
|
||||
|
||||
if self.useSRS and self.msrs then
|
||||
|
||||
-- Remove line breaks
|
||||
local text=string.gsub(Text, "[\r\n]", "")
|
||||
|
||||
-- Replace other stuff.
|
||||
local text=string.gsub(text, "SM", "statute miles")
|
||||
local text=string.gsub(text, "°C", "degrees Celsius")
|
||||
local text=string.gsub(text, "°F", "degrees Fahrenheit")
|
||||
local text=string.gsub(text, "inHg", "inches of Mercury")
|
||||
local text=string.gsub(text, "mmHg", "millimeters of Mercury")
|
||||
local text=string.gsub(text, "hPa", "hecto Pascals")
|
||||
local text=string.gsub(text, "m/s", "meters per second")
|
||||
|
||||
-- Replace ";" by "."
|
||||
local text=string.gsub(text, ";", " . ")
|
||||
|
||||
--Debug output.
|
||||
self:T("SRS TTS: "..text)
|
||||
|
||||
-- Play text-to-speech report.
|
||||
self.msrs:PlayText(text)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Base captured
|
||||
-- @param #ATIS self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ATIS:OnEventBaseCaptured(EventData)
|
||||
|
||||
if EventData and EventData.Place then
|
||||
|
||||
-- Place is the airbase that was captured.
|
||||
local airbase=EventData.Place --Wrapper.Airbase#AIRBASE
|
||||
|
||||
-- Check that this airbase belongs or did belong to this warehouse.
|
||||
if EventData.PlaceName==self.airbasename then
|
||||
|
||||
-- New coalition of airbase after it was captured.
|
||||
local NewCoalitionAirbase=airbase:GetCoalition()
|
||||
|
||||
if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then
|
||||
self.msrs:SetCoalition(NewCoalitionAirbase)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
2086
Moose Development/Moose/Ops/CSAR.lua
Normal file
2086
Moose Development/Moose/Ops/CSAR.lua
Normal file
File diff suppressed because it is too large
Load Diff
2867
Moose Development/Moose/Ops/CTLD.lua
Normal file
2867
Moose Development/Moose/Ops/CTLD.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
--
|
||||
-- * 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?
|
||||
--
|
||||
@@ -35,13 +32,13 @@
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @module Sound.Radio
|
||||
-- @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.
|
||||
--
|
||||
@@ -87,15 +84,15 @@
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias=nil,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias = nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
@@ -395,438 +392,3 @@ function RADIO:StopBroadcast()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name=nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode~="Y" then
|
||||
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
--- **Core** - Queues Radio Transmissions.
|
||||
--- **Sound** - Queues Radio Transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Managed Radio Transmissions.
|
||||
-- * Manage Radio Transmissions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: funkyfranky
|
||||
--
|
||||
-- @module Core.RadioQueue
|
||||
-- @module Sound.RadioQueue
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Manages radio transmissions.
|
||||
--
|
||||
-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence.
|
||||
-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other.
|
||||
-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file.
|
||||
--
|
||||
-- @type RADIOQUEUE
|
||||
-- @field #string ClassName Name of the class "RADIOQUEUE".
|
||||
-- @field #boolean Debugmode Debug mode. More info.
|
||||
@@ -35,6 +39,7 @@
|
||||
-- @field #table numbers Table of number transmission parameters.
|
||||
-- @field #boolean checking Scheduler is checking the radio queue.
|
||||
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
|
||||
-- @field Sound.SRS#MSRS msrs Moose SRS class.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIOQUEUE = {
|
||||
ClassName = "RADIOQUEUE",
|
||||
@@ -69,12 +74,14 @@ RADIOQUEUE = {
|
||||
-- @field #boolean isplaying If true, transmission is currently playing.
|
||||
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
|
||||
-- @field #number interval Interval in seconds before next transmission.
|
||||
-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS.
|
||||
-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS.
|
||||
|
||||
|
||||
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number frequency The radio frequency in MHz.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0).
|
||||
-- @param #string alias (Optional) Name of the radio queue.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:New(frequency, modulation, alias)
|
||||
@@ -125,9 +132,9 @@ function RADIOQUEUE:Start(delay, dt)
|
||||
|
||||
-- Start Scheduler.
|
||||
if self.schedonce then
|
||||
self:_CheckRadioQueueDelayed(delay)
|
||||
self:_CheckRadioQueueDelayed(self.delay)
|
||||
else
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt)
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt)
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -170,6 +177,17 @@ function RADIOQUEUE:SetRadioPower(power)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SRS.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string PathToSRS Path to SRS.
|
||||
-- @param #number Port SRS port. Default 5002.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:SetSRS(PathToSRS, Port)
|
||||
self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation)
|
||||
self.msrs:SetPort(Port)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set parameters of a digit.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number digit The digit 0-9.
|
||||
@@ -202,7 +220,7 @@ end
|
||||
--- Add a transmission to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddTransmission(transmission)
|
||||
self:F({transmission=transmission})
|
||||
|
||||
@@ -221,7 +239,7 @@ function RADIOQUEUE:AddTransmission(transmission)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a transmission to the radio queue.
|
||||
--- Create a new transmission and add it to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
|
||||
-- @param #number duration Duration in seconds the file lasts.
|
||||
@@ -230,7 +248,7 @@ end
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @param #string subtitle Subtitle of the transmission.
|
||||
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE.Transmission Radio transmission table.
|
||||
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
|
||||
|
||||
-- Sanity checks.
|
||||
@@ -269,9 +287,36 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval,
|
||||
-- Add transmission to queue.
|
||||
self:AddTransmission(transmission)
|
||||
|
||||
return transmission
|
||||
end
|
||||
|
||||
--- Add a SOUNDFILE to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval)
|
||||
--env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName()))
|
||||
local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration)
|
||||
transmission.soundfile=soundfile
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a SOUNDTEXT to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundText(soundtext, tstart, interval)
|
||||
|
||||
local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration)
|
||||
transmission.soundtext=soundtext
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Convert a number (as string) into a radio transmission.
|
||||
-- E.g. for board number or headings.
|
||||
-- @param #RADIOQUEUE self
|
||||
@@ -280,19 +325,9 @@ end
|
||||
-- @param #number interval Interval between the next call.
|
||||
-- @return #number Duration of the call in seconds.
|
||||
function RADIOQUEUE:Number2Transmission(number, delay, interval)
|
||||
|
||||
--- Split string into characters.
|
||||
local function _split(str)
|
||||
local chars={}
|
||||
for i=1,#str do
|
||||
local c=str:sub(i,i)
|
||||
table.insert(chars, c)
|
||||
end
|
||||
return chars
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers=_split(number)
|
||||
local numbers=UTILS.GetCharacters(number)
|
||||
|
||||
local wait=0
|
||||
for i=1,#numbers do
|
||||
@@ -325,6 +360,11 @@ end
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:Broadcast(transmission)
|
||||
|
||||
if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then
|
||||
self:_BroadcastSRS(transmission)
|
||||
return
|
||||
end
|
||||
|
||||
-- Get unit sending the transmission.
|
||||
local sender=self:_GetRadioSender()
|
||||
|
||||
@@ -416,6 +456,19 @@ function RADIOQUEUE:Broadcast(transmission)
|
||||
end
|
||||
end
|
||||
|
||||
--- Broadcast radio message.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:_BroadcastSRS(transmission)
|
||||
|
||||
if transmission.soundfile and transmission.soundfile.useSRS then
|
||||
self.msrs:PlaySoundFile(transmission.soundfile)
|
||||
elseif transmission.soundtext then
|
||||
self.msrs:PlaySoundText(transmission.soundtext)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Start checking the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number delay Delay in seconds before checking.
|
||||
@@ -547,7 +600,7 @@ function RADIOQUEUE:_GetRadioSender()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @return DCS#Vec3 Vector 3D.
|
||||
function RADIOQUEUE:_GetRadioSenderCoord()
|
||||
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ### Authors: FlightControl
|
||||
--
|
||||
-- @module Core.RadioSpeech
|
||||
-- @module Sound.RadioSpeech
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
@@ -162,31 +162,31 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["степени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["Ñ<EFBFBD>тепени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["km"] = { "kilometers", 0.65 },
|
||||
["миль"] = { "miles", 0.45 },
|
||||
["миль"] = { "miles", 0.45 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["метры"] = { "meters", 0.41 },
|
||||
["метры"] = { "meters", 0.41 },
|
||||
["m"] = { "meters", 0.41 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеса вверх"] = { "wheels_up", 0.92 },
|
||||
["посадка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
["возвращаÑ<EFBFBD>Ñ<EFBFBD>ÑŒ на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват Ñ<>амолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие Ñ<>амолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеÑ<EFBFBD>а вверх"] = { "wheels_up", 0.92 },
|
||||
["поÑ<EFBFBD>адка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
|
||||
["за"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["за"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["dot"] = { "dot", 0.51 },
|
||||
["defender"] = { "defender", 0.45 },
|
||||
}
|
||||
692
Moose Development/Moose/Sound/SRS.lua
Normal file
692
Moose Development/Moose/Sound/SRS.lua
Normal 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
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # 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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
408
Moose Development/Moose/Sound/SoundOutput.lua
Normal file
408
Moose Development/Moose/Sound/SoundOutput.lua
Normal 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
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** - Manage user sound.
|
||||
--- **Sound** - Manage user sound.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,7 +16,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.UserSound
|
||||
-- @module Sound.UserSound
|
||||
-- @image Core_Usersound.JPG
|
||||
|
||||
do -- UserSound
|
||||
@@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
self:SetAutoAcceptTasks( true )
|
||||
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
|
||||
self:SetFlashStatus( false )
|
||||
self:SetMessageDuration(10)
|
||||
|
||||
self:HandleEvent( EVENTS.Birth,
|
||||
--- @param #COMMANDCENTER self
|
||||
@@ -682,7 +683,7 @@ end
|
||||
-- @param #string Message The message text.
|
||||
function COMMANDCENTER:MessageToAll( Message )
|
||||
|
||||
self:GetPositionable():MessageToAll( Message, 20, self:GetName() )
|
||||
self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
|
||||
|
||||
end
|
||||
|
||||
@@ -692,7 +693,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
|
||||
function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
|
||||
|
||||
self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() )
|
||||
self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
|
||||
local CCCoalition = self:GetPositionable():GetCoalition()
|
||||
--TODO: Fix coalition bug!
|
||||
|
||||
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
|
||||
self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@@ -795,9 +796,18 @@ end
|
||||
|
||||
--- Let the command center flash a report of the status of the subscribed task to a group.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param Flash #boolean
|
||||
function COMMANDCENTER:SetFlashStatus( Flash )
|
||||
self:F()
|
||||
|
||||
self.FlashStatus = Flash or true
|
||||
|
||||
self.FlashStatus = Flash and true
|
||||
end
|
||||
|
||||
--- Duration a command center message is shown.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param seconds #number
|
||||
function COMMANDCENTER:SetMessageDuration(seconds)
|
||||
self:F()
|
||||
|
||||
self.MessageDuration = 10 or seconds
|
||||
end
|
||||
|
||||
256
Moose Development/Moose/Utilities/STTS.lua
Normal file
256
Moose Development/Moose/Utilities/STTS.lua
Normal 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
|
||||
@@ -50,6 +50,7 @@ BIGSMOKEPRESET = {
|
||||
-- @field #string PersianGulf Persian Gulf map.
|
||||
-- @field #string TheChannel The Channel map.
|
||||
-- @field #string Syria Syria map.
|
||||
-- @field #string MarianaIslands Mariana Islands map.
|
||||
DCSMAP = {
|
||||
Caucasus="Caucasus",
|
||||
NTTR="Nevada",
|
||||
@@ -57,6 +58,7 @@ DCSMAP = {
|
||||
PersianGulf="PersianGulf",
|
||||
TheChannel="TheChannel",
|
||||
Syria="Syria",
|
||||
MarianaIslands="MarianaIslands"
|
||||
}
|
||||
|
||||
|
||||
@@ -680,7 +682,10 @@ function UTILS.IsInSphere( InVec3, Vec3, Radius )
|
||||
return InSphere
|
||||
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)
|
||||
local bn=nil
|
||||
local bd=nil
|
||||
@@ -740,6 +745,21 @@ function UTILS.Split(str, sep)
|
||||
return result
|
||||
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.
|
||||
-- @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.
|
||||
@@ -1162,6 +1182,9 @@ end
|
||||
-- * NTTR +12 (East), year ~ 2011
|
||||
-- * Normandy -10 (West), year ~ 1944
|
||||
-- * 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
|
||||
-- @return #number Declination in degrees.
|
||||
function UTILS.GetMagneticDeclination(map)
|
||||
@@ -1182,6 +1205,8 @@ function UTILS.GetMagneticDeclination(map)
|
||||
declination=-10
|
||||
elseif map==DCSMAP.Syria then
|
||||
declination=5
|
||||
elseif map==DCSMAP.MarianaIslands then
|
||||
declination=2
|
||||
else
|
||||
declination=0
|
||||
end
|
||||
@@ -1310,6 +1335,8 @@ function UTILS.GMTToLocalTimeDifference()
|
||||
return 2 -- This map currently needs +2
|
||||
elseif theatre==DCSMAP.Syria then
|
||||
return 3 -- Damascus is UTC+3 hours
|
||||
elseif theatre==DCSMAP.MarianaIslands then
|
||||
return 10 -- Guam is UTC+10 hours.
|
||||
else
|
||||
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
|
||||
return 0
|
||||
@@ -1477,3 +1504,208 @@ function UTILS.GetOSTime()
|
||||
|
||||
return nil
|
||||
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 tyåe 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(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
|
||||
|
||||
@@ -73,7 +73,7 @@ AIRBASE = {
|
||||
|
||||
--- Enumeration to identify the airbases in the Caucasus region.
|
||||
--
|
||||
-- These are all airbases of Caucasus:
|
||||
-- Airbases of the Caucasus map:
|
||||
--
|
||||
-- * AIRBASE.Caucasus.Gelendzhik
|
||||
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
|
||||
@@ -122,7 +122,7 @@ AIRBASE.Caucasus = {
|
||||
["Beslan"] = "Beslan",
|
||||
}
|
||||
|
||||
--- These are all airbases of Nevada:
|
||||
--- Airbases of the Nevada map:
|
||||
--
|
||||
-- * AIRBASE.Nevada.Creech_AFB
|
||||
-- * AIRBASE.Nevada.Groom_Lake_AFB
|
||||
@@ -141,6 +141,7 @@ AIRBASE.Caucasus = {
|
||||
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
|
||||
-- * AIRBASE.Nevada.Tonopah_Airport
|
||||
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
|
||||
--
|
||||
-- @field Nevada
|
||||
AIRBASE.Nevada = {
|
||||
["Creech_AFB"] = "Creech AFB",
|
||||
@@ -162,7 +163,7 @@ AIRBASE.Nevada = {
|
||||
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield",
|
||||
}
|
||||
|
||||
--- These are all airbases of Normandy:
|
||||
--- Airbases of the Normandy map:
|
||||
--
|
||||
-- * AIRBASE.Normandy.Saint_Pierre_du_Mont
|
||||
-- * AIRBASE.Normandy.Lignerolles
|
||||
@@ -195,6 +196,7 @@ AIRBASE.Nevada = {
|
||||
-- * AIRBASE.Normandy.Funtington
|
||||
-- * AIRBASE.Normandy.Tangmere
|
||||
-- * AIRBASE.Normandy.Ford_AF
|
||||
--
|
||||
-- @field Normandy
|
||||
AIRBASE.Normandy = {
|
||||
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
|
||||
@@ -237,7 +239,7 @@ AIRBASE.Normandy = {
|
||||
["Conches"] = "Conches",
|
||||
}
|
||||
|
||||
--- These are all airbases of the Persion Gulf Map:
|
||||
--- Airbases of the Persion Gulf Map:
|
||||
--
|
||||
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
|
||||
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
|
||||
@@ -268,6 +270,7 @@ AIRBASE.Normandy = {
|
||||
-- * AIRBASE.PersianGulf.Sirri_Island
|
||||
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
|
||||
-- * AIRBASE.PersianGulf.Tunb_Kochak
|
||||
--
|
||||
-- @field PersianGulf
|
||||
AIRBASE.PersianGulf = {
|
||||
["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl",
|
||||
@@ -301,7 +304,7 @@ AIRBASE.PersianGulf = {
|
||||
["Tunb_Kochak"] = "Tunb Kochak",
|
||||
}
|
||||
|
||||
--- These are all airbases of the The Channel Map:
|
||||
--- Airbases of The Channel Map:
|
||||
--
|
||||
-- * AIRBASE.TheChannel.Abbeville_Drucat
|
||||
-- * AIRBASE.TheChannel.Merville_Calonne
|
||||
@@ -326,7 +329,7 @@ AIRBASE.TheChannel = {
|
||||
["High_Halden"] = "High Halden",
|
||||
}
|
||||
|
||||
--- Airbases of Syria
|
||||
--- Airbases of the Syria map:
|
||||
--
|
||||
-- * AIRBASE.Syria.Kuweires
|
||||
-- * AIRBASE.Syria.Marj_Ruhayyil
|
||||
@@ -336,41 +339,53 @@ AIRBASE.TheChannel = {
|
||||
-- * AIRBASE.Syria.Incirlik
|
||||
-- * AIRBASE.Syria.Damascus
|
||||
-- * AIRBASE.Syria.Bassel_Al_Assad
|
||||
-- * AIRBASE.Syria.Rosh_Pina
|
||||
-- * AIRBASE.Syria.Aleppo
|
||||
-- * AIRBASE.Syria.Qabr_as_Sitt
|
||||
-- * AIRBASE.Syria.Al_Qusayr
|
||||
-- * AIRBASE.Syria.Wujah_Al_Hajar
|
||||
-- * AIRBASE.Syria.Al_Dumayr
|
||||
-- * AIRBASE.Syria.Gazipasa
|
||||
-- * AIRBASE.Syria.Ru_Convoy_4
|
||||
-- * AIRBASE.Syria.Hatay
|
||||
-- * AIRBASE.Syria.Nicosia
|
||||
-- * AIRBASE.Syria.Pinarbashi
|
||||
-- * AIRBASE.Syria.Paphos
|
||||
-- * AIRBASE.Syria.Kingsfield
|
||||
-- * AIRBASE.Syria.Thalah
|
||||
-- * AIRBASE.Syria.Haifa
|
||||
-- * AIRBASE.Syria.Khalkhalah
|
||||
-- * AIRBASE.Syria.Megiddo
|
||||
-- * AIRBASE.Syria.Lakatamia
|
||||
-- * AIRBASE.Syria.Rayak
|
||||
-- * AIRBASE.Syria.Larnaca
|
||||
-- * AIRBASE.Syria.Mezzeh
|
||||
-- * AIRBASE.Syria.King_Hussein_Air_College
|
||||
-- * AIRBASE.Syria.Jirah
|
||||
-- * AIRBASE.Syria.Gecitkale
|
||||
-- * AIRBASE.Syria.Akrotiri
|
||||
-- * AIRBASE.Syria.Naqoura
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.CVN_71
|
||||
-- * AIRBASE.Syria.Sayqal
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Shayrat
|
||||
-- * AIRBASE.Syria.Taftanaz
|
||||
-- * AIRBASE.Syria.H4
|
||||
-- * AIRBASE.Syria.King_Hussein_Air_College
|
||||
-- * AIRBASE.Syria.Rene_Mouawad
|
||||
-- * AIRBASE.Syria.Jirah
|
||||
-- * AIRBASE.Syria.Ramat_David
|
||||
-- * AIRBASE.Syria.Qabr_as_Sitt
|
||||
-- * AIRBASE.Syria.Minakh
|
||||
-- * AIRBASE.Syria.Adana_Sakirpasa
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_South
|
||||
-- * AIRBASE.Syria.Hama
|
||||
-- * AIRBASE.Syria.Al_Qusayr
|
||||
-- * AIRBASE.Syria.Palmyra
|
||||
-- * AIRBASE.Syria.Hama
|
||||
-- * AIRBASE.Syria.Ercan
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_South
|
||||
-- * AIRBASE.Syria.Tabqa
|
||||
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
|
||||
-- * AIRBASE.Syria.An_Nasiriyah
|
||||
-- * AIRBASE.Syria.Abu_al_Duhur
|
||||
-- * AIRBASE.Syria.H4
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.Rosh_Pina
|
||||
-- * AIRBASE.Syria.Sayqal
|
||||
-- * AIRBASE.Syria.Shayrat
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Tha_lah
|
||||
-- * AIRBASE.Syria.Naqoura
|
||||
--
|
||||
-- @field Syria
|
||||
--@field Syria
|
||||
AIRBASE.Syria={
|
||||
["Kuweires"]="Kuweires",
|
||||
["Marj_Ruhayyil"]="Marj Ruhayyil",
|
||||
@@ -380,39 +395,71 @@ AIRBASE.Syria={
|
||||
["Incirlik"]="Incirlik",
|
||||
["Damascus"]="Damascus",
|
||||
["Bassel_Al_Assad"]="Bassel Al-Assad",
|
||||
["Rosh_Pina"]="Rosh Pina",
|
||||
["Aleppo"]="Aleppo",
|
||||
["Qabr_as_Sitt"]="Qabr as Sitt",
|
||||
["Al_Qusayr"]="Al Qusayr",
|
||||
["Wujah_Al_Hajar"]="Wujah Al Hajar",
|
||||
["Al_Dumayr"]="Al-Dumayr",
|
||||
["Gazipasa"]="Gazipasa",
|
||||
["Ru_Convoy_4"]="Ru Convoy-4",
|
||||
["Hatay"]="Hatay",
|
||||
["Nicosia"]="Nicosia",
|
||||
["Pinarbashi"]="Pinarbashi",
|
||||
["Paphos"]="Paphos",
|
||||
["Kingsfield"]="Kingsfield",
|
||||
["Thalah"]="Tha'lah",
|
||||
["Haifa"]="Haifa",
|
||||
["Khalkhalah"]="Khalkhalah",
|
||||
["Megiddo"]="Megiddo",
|
||||
["Lakatamia"]="Lakatamia",
|
||||
["Rayak"]="Rayak",
|
||||
["Larnaca"]="Larnaca",
|
||||
["Mezzeh"]="Mezzeh",
|
||||
["King_Hussein_Air_College"]="King Hussein Air College",
|
||||
["Jirah"]="Jirah",
|
||||
["Gecitkale"]="Gecitkale",
|
||||
["Akrotiri"]="Akrotiri",
|
||||
["Naqoura"]="Naqoura",
|
||||
["Gaziantep"]="Gaziantep",
|
||||
["Sayqal"]="Sayqal",
|
||||
["Tiyas"]="Tiyas",
|
||||
["Shayrat"]="Shayrat",
|
||||
["Taftanaz"]="Taftanaz",
|
||||
["H4"]="H4",
|
||||
["King_Hussein_Air_College"]="King Hussein Air College",
|
||||
["Rene_Mouawad"]="Rene Mouawad",
|
||||
["Jirah"]="Jirah",
|
||||
["Ramat_David"]="Ramat David",
|
||||
["Qabr_as_Sitt"]="Qabr as Sitt",
|
||||
["Minakh"]="Minakh",
|
||||
["Adana_Sakirpasa"]="Adana Sakirpasa",
|
||||
["Marj_as_Sultan_South"]="Marj as Sultan South",
|
||||
["Hama"]="Hama",
|
||||
["Al_Qusayr"]="Al Qusayr",
|
||||
["Palmyra"]="Palmyra",
|
||||
["Hama"]="Hama",
|
||||
["Ercan"]="Ercan",
|
||||
["Marj_as_Sultan_South"]="Marj as Sultan South",
|
||||
["Tabqa"]="Tabqa",
|
||||
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
|
||||
["An_Nasiriyah"]="An Nasiriyah",
|
||||
["Abu_al_Duhur"]="Abu al-Duhur",
|
||||
["H4"]="H4",
|
||||
["Gaziantep"]="Gaziantep",
|
||||
["Rosh_Pina"]="Rosh Pina",
|
||||
["Sayqal"]="Sayqal",
|
||||
["Shayrat"]="Shayrat",
|
||||
["Tiyas"]="Tiyas",
|
||||
["Tha_lah"]="Tha'lah",
|
||||
["Naqoura"]="Naqoura",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Airbases of the Mariana Islands map:
|
||||
--
|
||||
-- * AIRBASE.MarianaIslands.Rota_Intl
|
||||
-- * AIRBASE.MarianaIslands.Andersen_AFB
|
||||
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
|
||||
-- * AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- * AIRBASE.MarianaIslands.Tinian_Intl
|
||||
-- * AIRBASE.MarianaIslands.Olf_Orote
|
||||
--
|
||||
--@field MarianaIslands
|
||||
AIRBASE.MarianaIslands={
|
||||
["Rota_Intl"]="Rota Intl",
|
||||
["Andersen_AFB"]="Andersen AFB",
|
||||
["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl",
|
||||
["Saipan_Intl"]="Saipan Intl",
|
||||
["Tinian_Intl"]="Tinian Intl",
|
||||
["Olf_Orote"]="Olf Orote",
|
||||
}
|
||||
|
||||
|
||||
@@ -1372,7 +1419,8 @@ function AIRBASE:GetRunwayData(magvar, mark)
|
||||
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
|
||||
name==AIRBASE.PersianGulf.Dubai_Intl or
|
||||
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
|
||||
name==AIRBASE.PersianGulf.Kish_International_Airport then
|
||||
name==AIRBASE.PersianGulf.Kish_International_Airport or
|
||||
name==AIRBASE.MarianaIslands.Andersen_AFB then
|
||||
|
||||
-- 1-->4, 2-->3, 3-->2, 4-->1
|
||||
exception=1
|
||||
|
||||
@@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch)
|
||||
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
|
||||
|
||||
--do -- Smoke
|
||||
--
|
||||
----- Signal a flare at the position of the GROUP.
|
||||
|
||||
@@ -580,6 +580,18 @@ function UNIT:GetAmmo()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Sets the Unit's Internal Cargo Mass, in kg
|
||||
-- @param #UNIT self
|
||||
-- @param #number mass to set cargo to
|
||||
-- @return #UNIT self
|
||||
function UNIT:SetUnitInternalCargo(mass)
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
if DCSUnit then
|
||||
trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has.
|
||||
-- @param #UNIT self
|
||||
-- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles.
|
||||
|
||||
@@ -4,10 +4,11 @@ Utilities/Utils.lua
|
||||
Utilities/Enums.lua
|
||||
Utilities/Profiler.lua
|
||||
Utilities/Templates.lua
|
||||
Utilities/STTS.lua
|
||||
|
||||
Core/Base.lua
|
||||
Core/Beacon.lua
|
||||
Core/UserFlag.lua
|
||||
Core/UserSound.lua
|
||||
Core/Report.lua
|
||||
Core/Scheduler.lua
|
||||
Core/ScheduleDispatcher.lua
|
||||
@@ -22,9 +23,6 @@ Core/Point.lua
|
||||
Core/Velocity.lua
|
||||
Core/Message.lua
|
||||
Core/Fsm.lua
|
||||
Core/Radio.lua
|
||||
Core/RadioQueue.lua
|
||||
Core/RadioSpeech.lua
|
||||
Core/Spawn.lua
|
||||
Core/SpawnStatic.lua
|
||||
Core/Timer.lua
|
||||
@@ -76,6 +74,8 @@ Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
Ops/RescueHelo.lua
|
||||
Ops/ATIS.lua
|
||||
Ops/CTLD.lua
|
||||
Ops/CSAR.lua
|
||||
|
||||
AI/AI_Balancer.lua
|
||||
AI/AI_Air.lua
|
||||
@@ -114,6 +114,13 @@ Actions/Act_Route.lua
|
||||
Actions/Act_Account.lua
|
||||
Actions/Act_Assist.lua
|
||||
|
||||
Sound/UserSound.lua
|
||||
Sound/SoundOutput.lua
|
||||
Sound/Radio.lua
|
||||
Sound/RadioQueue.lua
|
||||
Sound/RadioSpeech.lua
|
||||
Sound/SRS.lua
|
||||
|
||||
Tasking/CommandCenter.lua
|
||||
Tasking/Mission.lua
|
||||
Tasking/Task.lua
|
||||
|
||||
Reference in New Issue
Block a user