mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Sound update
This commit is contained in:
parent
fcd75a34eb
commit
4c3d44a63b
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 )
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- GLOBALS: The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
|
||||
--- Register zones.
|
||||
_DATABASE:_RegisterZones()
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs (Check <DCS install folder>/Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)")
|
||||
|
||||
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 commend out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
end
|
||||
@ -3,8 +3,10 @@ __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/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
@ -110,7 +112,7 @@ __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/SoundFile.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' )
|
||||
|
||||
@ -88,6 +88,8 @@
|
||||
-- @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.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@ -1100,6 +1102,22 @@ 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`.
|
||||
-- @return #ATIS self
|
||||
function ATIS:SetSTTS(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)
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -1188,15 +1206,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()
|
||||
|
||||
self:__CheckQueue(-120)
|
||||
|
||||
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
|
||||
|
||||
-- Check back in 5 seconds.
|
||||
self:__CheckQueue(-5)
|
||||
|
||||
end
|
||||
|
||||
-- Check back in 5 seconds.
|
||||
self:__CheckQueue(-5)
|
||||
end
|
||||
|
||||
--- Broadcast ATIS radio message.
|
||||
@ -1591,36 +1619,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 +1672,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 +1694,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 +1741,63 @@ 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
|
||||
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)
|
||||
else
|
||||
subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL)
|
||||
subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL)
|
||||
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 +1817,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 +1846,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 +1865,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 +1924,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 +1956,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 +1994,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 +2024,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 +2073,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 +2080,55 @@ 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 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 +2136,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.
|
||||
@ -2103,12 +2170,31 @@ end
|
||||
function ATIS:onafterReport(From, Event, To, Text)
|
||||
self:T(self.lid..string.format("Report:\n%s", Text))
|
||||
|
||||
-- Remove line breaks
|
||||
local text=string.gsub(Text, "[\r\n]", "")
|
||||
env.info("FF: "..text)
|
||||
if self.useSRS and self.msrs then
|
||||
|
||||
local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation)
|
||||
msrs:PlayText(text)
|
||||
-- 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, ";", ". ")
|
||||
env.info("FF: "..text)
|
||||
|
||||
--local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation)
|
||||
--msrs:PlayText(text)
|
||||
|
||||
-- Play text-to-speech report.
|
||||
self.msrs:PlayText(text)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ RADIOQUEUE = {
|
||||
--- 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)
|
||||
@ -245,8 +245,6 @@ end
|
||||
-- @return #RADIOQUEUE.Transmission Radio transmission table.
|
||||
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
|
||||
|
||||
env.info("FF new transmission.")
|
||||
|
||||
-- Sanity checks.
|
||||
if not filename then
|
||||
self:E(self.lid.."ERROR: No filename specified.")
|
||||
@ -288,7 +286,7 @@ end
|
||||
|
||||
--- Create a new transmission and add it to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added.
|
||||
-- @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
|
||||
|
||||
@ -71,7 +71,7 @@ MSRS = {
|
||||
modulations = {},
|
||||
coalition = 0,
|
||||
gender = "female",
|
||||
culture = "en-GB",
|
||||
culture = nil,
|
||||
voice = nil,
|
||||
volume = 1,
|
||||
speed = 1,
|
||||
@ -81,15 +81,19 @@ MSRS = {
|
||||
altitude = nil,
|
||||
}
|
||||
|
||||
--- Counter.
|
||||
_MSRSuuid=0
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.0.2"
|
||||
MSRS.version="0.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
-- TODO: Add functions to add/remove freqs and modulations.
|
||||
-- TODO: Add coordinate.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@ -144,7 +148,7 @@ function MSRS:SetPath(Path)
|
||||
n=n+1
|
||||
end
|
||||
|
||||
self.path=self.path.."/"
|
||||
self.path=self.path --.."/"
|
||||
|
||||
self:I(string.format("SRS path=%s", self:GetPath()))
|
||||
|
||||
@ -225,16 +229,40 @@ end
|
||||
-- @param #string Gender Gender: "male" or "female" (default).
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetGender(Gender)
|
||||
|
||||
self:I("Input gender to "..tostring(Gender))
|
||||
|
||||
Gender=Gender or "female"
|
||||
|
||||
Gender=Gender:lower()
|
||||
self.gender=Gender:lower()
|
||||
|
||||
self.gender=Gender
|
||||
self:I("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
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Transmission Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -250,16 +278,20 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
|
||||
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)
|
||||
|
||||
env.info(string.format("FF PlaySoundfile command=%s", command))
|
||||
-- Debug output.
|
||||
self:I(string.format("MSRS PlaySoundfile command=%s", command))
|
||||
|
||||
-- Execute SRS command.
|
||||
os.execute(command)
|
||||
local x=os.execute(command)
|
||||
|
||||
end
|
||||
|
||||
@ -277,14 +309,17 @@ function MSRS:PlaySoundText(SoundText, Delay)
|
||||
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))
|
||||
|
||||
env.info(string.format("FF PlaySoundfile command=%s", command))
|
||||
-- Debug putput.
|
||||
self:I(string.format("MSRS PlaySoundfile command=%s", command))
|
||||
|
||||
-- Execute SRS command.
|
||||
os.execute(command)
|
||||
local x=os.execute(command)
|
||||
|
||||
end
|
||||
|
||||
@ -302,17 +337,79 @@ function MSRS:PlayText(Text, Delay)
|
||||
self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0)
|
||||
else
|
||||
|
||||
local text=string.format("\"%s\"", Text)
|
||||
|
||||
-- Get command line.
|
||||
local command=self:_GetCommand()
|
||||
|
||||
-- Append text.
|
||||
command=command..string.format(" --text=\"%s\"", tostring(Text))
|
||||
|
||||
env.info(string.format("FF Text command=%s", 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:I(string.format("MSRS TextFile command=%s", command))
|
||||
|
||||
-- Count length of command.
|
||||
local l=string.len(command)
|
||||
|
||||
-- Execute SRS command.
|
||||
local x=os.execute(command)
|
||||
env.info(x)
|
||||
|
||||
end
|
||||
|
||||
@ -338,8 +435,8 @@ end
|
||||
-- @return #string Command.
|
||||
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
|
||||
|
||||
|
||||
local exe=self:GetPath().."DCS-SR-ExternalAudio.exe"
|
||||
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
|
||||
@ -348,19 +445,34 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
culture=culture or self.culture
|
||||
volume=volume or self.volume
|
||||
speed=speed or self.speed
|
||||
port=port or self.port
|
||||
port=port or self.port
|
||||
|
||||
local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed)
|
||||
env.info("FF gender="..tostring(gender))
|
||||
env.info("FF gender="..tostring(self.gender))
|
||||
|
||||
-- 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\"", 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
|
||||
|
||||
if culture then
|
||||
command=command.." --culture="..tostring(culture)
|
||||
end
|
||||
|
||||
env.info("FF command="..command)
|
||||
-- Debug output.
|
||||
self:I("MSRS command="..command)
|
||||
|
||||
return command
|
||||
end
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Add a sound file to the
|
||||
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions
|
||||
-- * Create a SOUNDTEXT object for text-to-speech output
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -12,22 +13,19 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Sound.Soundfile
|
||||
-- @image Sound_Soundfile.png
|
||||
--
|
||||
-- @module Sound.SoundOutput
|
||||
-- @image Sound_SoundOutput.png
|
||||
|
||||
do -- Sound Base
|
||||
|
||||
--- @type SOUNDBASE
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Sound files used by other classes.
|
||||
--- Basic sound output inherited by other classes.
|
||||
--
|
||||
-- # 1. USERFLAG constructor
|
||||
--
|
||||
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
|
||||
-- This class is **not** meant to be used by "ordinary" users.
|
||||
--
|
||||
-- @field #SOUNDBASE
|
||||
SOUNDBASE={
|
||||
@ -40,7 +38,7 @@ do -- Sound Base
|
||||
function SOUNDBASE:New()
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE
|
||||
|
||||
|
||||
|
||||
@ -94,10 +92,11 @@ do -- Sound File
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set path
|
||||
-- Set path.
|
||||
self:SetPath(Path)
|
||||
|
||||
self.duration=Duration or 3
|
||||
-- Set duration.
|
||||
self:SetDuration(Duration)
|
||||
|
||||
-- Debug info:
|
||||
self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path))
|
||||
@ -137,11 +136,11 @@ do -- Sound File
|
||||
|
||||
--- Set sound file name. This must be a .ogg or .mp3 file!
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string FileName Name of the file.
|
||||
-- @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 "HelloWorld.mp3"
|
||||
self.filename=FileName or "Hello World.mp3"
|
||||
return self
|
||||
end
|
||||
|
||||
@ -187,17 +186,27 @@ do -- Text-To-Speech
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string text Text to speak.
|
||||
-- @field #number duration Duration in seconds.
|
||||
-- @field #string gender Gender.
|
||||
-- @field #string voice Voice.
|
||||
-- @field #string culture Culture.
|
||||
-- @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
|
||||
|
||||
|
||||
--- Sound files used by other classes.
|
||||
--- Text-to-speech objects for other classes.
|
||||
--
|
||||
-- # 1. USERFLAG constructor
|
||||
-- # Constructor
|
||||
--
|
||||
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
|
||||
-- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object.
|
||||
--
|
||||
-- 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)
|
||||
--
|
||||
-- @field #SOUNDTEXT
|
||||
SOUNDTEXT={
|
||||
@ -216,8 +225,8 @@ do -- Text-To-Speech
|
||||
|
||||
self:SetText(Text)
|
||||
self:SetDuration(Duration)
|
||||
self:SetGender()
|
||||
self:SetCulture()
|
||||
--self:SetGender()
|
||||
--self:SetCulture()
|
||||
|
||||
-- Debug info:
|
||||
self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
|
||||
@ -258,9 +267,10 @@ do -- Text-To-Speech
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices
|
||||
--- Set to use a specific voice name.
|
||||
-- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Voice
|
||||
-- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetVoice(Voice)
|
||||
|
||||
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
|
||||
@ -4,8 +4,10 @@ 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/Report.lua
|
||||
Core/Scheduler.lua
|
||||
@ -111,7 +113,7 @@ Actions/Act_Account.lua
|
||||
Actions/Act_Assist.lua
|
||||
|
||||
Sound/UserSound.lua
|
||||
Sound/SoundFile.lua
|
||||
Sound/SoundOutput.lua
|
||||
Sound/Radio.lua
|
||||
Sound/RadioQueue.lua
|
||||
Sound/RadioSpeech.lua
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user