mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
1267 lines
43 KiB
Lua
1267 lines
43 KiB
Lua
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Features:
|
|
--
|
|
-- * Provide radio functionality to broadcast radio transmissions.
|
|
-- * Provide beacon functionality to assist pilots.
|
|
--
|
|
-- The Radio contains 2 classes : RADIO and BEACON
|
|
--
|
|
-- What are radio communications in DCS?
|
|
--
|
|
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
|
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
|
--
|
|
-- How to supply DCS my own Sound Files?
|
|
--
|
|
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
|
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
|
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
|
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
|
|
--
|
|
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
|
--
|
|
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
|
|
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
|
|
--
|
|
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
|
|
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
|
|
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
|
-- you won't hear/be able to use the TACAN beacon informations.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
|
--
|
|
-- @module Core.Radio
|
|
-- @image Core_Radio.JPG
|
|
|
|
|
|
--- Models the radio capability.
|
|
--
|
|
-- ## RADIO usage
|
|
--
|
|
-- There are 3 steps to a successful radio transmission.
|
|
--
|
|
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
|
|
-- * Then, you will **set the relevant parameters** to the transmission (see below),
|
|
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
|
|
--
|
|
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
|
|
--
|
|
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
|
|
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
|
|
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
|
|
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
|
|
--
|
|
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
|
|
--
|
|
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
|
|
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
|
|
--
|
|
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
|
|
--
|
|
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
|
|
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
|
|
--
|
|
-- What is this power thing?
|
|
--
|
|
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
|
|
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
|
|
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
|
-- * This an automated DCS calculation you have no say on,
|
|
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
|
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
|
--
|
|
-- @type RADIO
|
|
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
|
-- @field #string FileName Name of the sound file played.
|
|
-- @field #number Frequency Frequency of the transmission in Hz.
|
|
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
|
|
-- @field #string Subtitle Subtitle of the transmission.
|
|
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
|
|
-- @field #number Power Power of the antenna is Watts.
|
|
-- @field #boolean Loop Transmission is repeated (default true).
|
|
-- @field #string alias Name of the radio transmitter.
|
|
-- @extends Core.Base#BASE
|
|
RADIO = {
|
|
ClassName = "RADIO",
|
|
FileName = "",
|
|
Frequency = 0,
|
|
Modulation = radio.modulation.AM,
|
|
Subtitle = "",
|
|
SubtitleDuration = 0,
|
|
Power = 100,
|
|
Loop = false,
|
|
alias=nil,
|
|
}
|
|
|
|
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
|
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
|
|
-- @param #RADIO self
|
|
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
|
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
|
|
function RADIO:New(Positionable)
|
|
|
|
-- Inherit base
|
|
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
|
self:F(Positionable)
|
|
|
|
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
|
self.Positionable = Positionable
|
|
return self
|
|
end
|
|
|
|
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
|
return nil
|
|
end
|
|
|
|
--- Set alias of the transmitter.
|
|
-- @param #RADIO self
|
|
-- @param #string alias Name of the radio transmitter.
|
|
-- @return #RADIO self
|
|
function RADIO:SetAlias(alias)
|
|
self.alias=tostring(alias)
|
|
return self
|
|
end
|
|
|
|
--- Get alias of the transmitter.
|
|
-- @param #RADIO self
|
|
-- @return #string Name of the transmitter.
|
|
function RADIO:GetAlias()
|
|
return tostring(self.alias)
|
|
end
|
|
|
|
--- Set the file name for the radio transmission.
|
|
-- @param #RADIO self
|
|
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
|
-- @return #RADIO self
|
|
function RADIO:SetFileName(FileName)
|
|
self:F2(FileName)
|
|
|
|
if type(FileName) == "string" then
|
|
|
|
if FileName:find(".ogg") or FileName:find(".wav") then
|
|
if not FileName:find("l10n/DEFAULT/") then
|
|
FileName = "l10n/DEFAULT/" .. FileName
|
|
end
|
|
|
|
self.FileName = FileName
|
|
return self
|
|
end
|
|
end
|
|
|
|
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
|
return self
|
|
end
|
|
|
|
--- Set the frequency for the radio transmission.
|
|
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
|
-- @param #RADIO self
|
|
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz.
|
|
-- @return #RADIO self
|
|
function RADIO:SetFrequency(Frequency)
|
|
self:F2(Frequency)
|
|
|
|
if type(Frequency) == "number" then
|
|
|
|
-- If frequency is in range
|
|
if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
|
|
|
-- Convert frequency from MHz to Hz
|
|
self.Frequency = Frequency * 1000000
|
|
|
|
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
|
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
|
|
|
local commandSetFrequency={
|
|
id = "SetFrequency",
|
|
params = {
|
|
frequency = self.Frequency,
|
|
modulation = self.Modulation,
|
|
}
|
|
}
|
|
|
|
self:T2(commandSetFrequency)
|
|
self.Positionable:SetCommand(commandSetFrequency)
|
|
end
|
|
|
|
return self
|
|
end
|
|
end
|
|
|
|
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
|
|
return self
|
|
end
|
|
|
|
--- Set AM or FM modulation of the radio transmitter.
|
|
-- @param #RADIO self
|
|
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
|
|
-- @return #RADIO self
|
|
function RADIO:SetModulation(Modulation)
|
|
self:F2(Modulation)
|
|
if type(Modulation) == "number" then
|
|
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
|
self.Modulation = Modulation
|
|
return self
|
|
end
|
|
end
|
|
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
|
return self
|
|
end
|
|
|
|
--- Check validity of the power passed and sets RADIO.Power
|
|
-- @param #RADIO self
|
|
-- @param #number Power Power in W.
|
|
-- @return #RADIO self
|
|
function RADIO:SetPower(Power)
|
|
self:F2(Power)
|
|
|
|
if type(Power) == "number" then
|
|
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
|
else
|
|
self:E({"Power is invalid. Power unchanged.", self.Power})
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- Set message looping on or off.
|
|
-- @param #RADIO self
|
|
-- @param #boolean Loop If true, message is repeated indefinitely.
|
|
-- @return #RADIO self
|
|
function RADIO:SetLoop(Loop)
|
|
self:F2(Loop)
|
|
if type(Loop) == "boolean" then
|
|
self.Loop = Loop
|
|
return self
|
|
end
|
|
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
|
return self
|
|
end
|
|
|
|
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
|
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
|
-- @param #RADIO self
|
|
-- @param #string Subtitle
|
|
-- @param #number SubtitleDuration in s
|
|
-- @return #RADIO self
|
|
-- @usage
|
|
-- -- create the broadcaster and attaches it a RADIO
|
|
-- local MyUnit = UNIT:FindByName("MyUnit")
|
|
-- local MyUnitRadio = MyUnit:GetRadio()
|
|
--
|
|
-- -- add a subtitle for the next transmission, which will be up for 10s
|
|
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
|
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
|
self:F2({Subtitle, SubtitleDuration})
|
|
if type(Subtitle) == "string" then
|
|
self.Subtitle = Subtitle
|
|
else
|
|
self.Subtitle = ""
|
|
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
|
end
|
|
if type(SubtitleDuration) == "number" then
|
|
self.SubtitleDuration = SubtitleDuration
|
|
else
|
|
self.SubtitleDuration = 0
|
|
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
|
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
|
-- but it will work with a UNIT or a GROUP anyway.
|
|
-- Only the #RADIO and the Filename are mandatory
|
|
-- @param #RADIO self
|
|
-- @param #string FileName Name of the sound file that will be transmitted.
|
|
-- @param #number Frequency Frequency in MHz.
|
|
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
|
|
-- @param #number Power Power in W.
|
|
-- @return #RADIO self
|
|
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
|
self:F({FileName, Frequency, Modulation, Power})
|
|
|
|
self:SetFileName(FileName)
|
|
if Frequency then self:SetFrequency(Frequency) end
|
|
if Modulation then self:SetModulation(Modulation) end
|
|
if Power then self:SetPower(Power) end
|
|
if Loop then self:SetLoop(Loop) end
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
|
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
|
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
|
-- Only the RADIO and the Filename are mandatory.
|
|
-- @param #RADIO self
|
|
-- @param #string FileName Name of sound file.
|
|
-- @param #string Subtitle Subtitle to be displayed with sound file.
|
|
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
|
|
-- @param #number Frequency Frequency in MHz.
|
|
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
|
|
-- @param #boolean Loop If true, loop message.
|
|
-- @return #RADIO self
|
|
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
|
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
|
|
|
-- Set file name.
|
|
self:SetFileName(FileName)
|
|
|
|
-- Set modulation AM/FM.
|
|
if Modulation then
|
|
self:SetModulation(Modulation)
|
|
end
|
|
|
|
-- Set frequency.
|
|
if Frequency then
|
|
self:SetFrequency(Frequency)
|
|
end
|
|
|
|
-- Set subtitle.
|
|
if Subtitle then
|
|
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
|
end
|
|
|
|
-- Set Looping.
|
|
if Loop then
|
|
self:SetLoop(Loop)
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- Broadcast the transmission.
|
|
-- * The Radio has to be populated with the new transmission before broadcasting.
|
|
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
|
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
|
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
|
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
|
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
|
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
|
-- @param #RADIO self
|
|
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
|
|
-- @return #RADIO self
|
|
function RADIO:Broadcast(viatrigger)
|
|
self:F({viatrigger=viatrigger})
|
|
|
|
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
|
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
|
self:T("Broadcasting from a UNIT or a GROUP")
|
|
|
|
local commandTransmitMessage={
|
|
id = "TransmitMessage",
|
|
params = {
|
|
file = self.FileName,
|
|
duration = self.SubtitleDuration,
|
|
subtitle = self.Subtitle,
|
|
loop = self.Loop,
|
|
}}
|
|
|
|
self:T3(commandTransmitMessage)
|
|
self.Positionable:SetCommand(commandTransmitMessage)
|
|
else
|
|
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
|
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
|
self:T("Broadcasting from a POSITIONABLE")
|
|
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
|
|
--- Stops a transmission
|
|
-- This function is especially usefull to stop the broadcast of looped transmissions
|
|
-- @param #RADIO self
|
|
-- @return #RADIO self
|
|
function RADIO:StopBroadcast()
|
|
self:F()
|
|
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
|
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
|
|
|
local commandStopTransmission={id="StopTransmission", params={}}
|
|
|
|
self.Positionable:SetCommand(commandStopTransmission)
|
|
else
|
|
-- Else, we use the appropriate singleton funciton
|
|
trigger.action.stopRadioTransmission(tostring(self.ID))
|
|
end
|
|
return self
|
|
end
|
|
|
|
|
|
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
|
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
|
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
|
-- attach to a cargo crate, for exemple.
|
|
--
|
|
-- ## AA TACAN Beacon usage
|
|
--
|
|
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
|
-- Use @#BEACON:StopAATACAN}() to stop it.
|
|
--
|
|
-- ## General Purpose Radio Beacon usage
|
|
--
|
|
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
|
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
|
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
|
--
|
|
-- @type BEACON
|
|
-- @field #string ClassName Name of the class "BEACON".
|
|
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
|
-- @extends Core.Base#BASE
|
|
BEACON = {
|
|
ClassName = "BEACON",
|
|
Positionable = nil,
|
|
}
|
|
|
|
--- Beacon types supported by DCS.
|
|
-- @type BEACON.Type
|
|
-- @field #number NULL
|
|
-- @field #number VOR
|
|
-- @field #number DME
|
|
-- @field #number VOR_DME
|
|
-- @field #number TACAN
|
|
-- @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 NAUTICAL_HOMER
|
|
-- @field #number ICLS
|
|
BEACON.Type={
|
|
NULL = 0,
|
|
VOR = 1,
|
|
DME = 2,
|
|
VOR_DME = 3,
|
|
TACAN = 4,
|
|
VORTAC = 5,
|
|
RSBN = 32,
|
|
BROADCAST_STATION = 1024,
|
|
HOMER = 8,
|
|
AIRPORT_HOMER = 4104,
|
|
AIRPORT_HOMER_WITH_MARKER = 4136,
|
|
ILS_FAR_HOMER = 16408,
|
|
ILS_NEAR_HOMER = 16456,
|
|
ILS_LOCALIZER = 16640,
|
|
ILS_GLIDESLOPE = 16896,
|
|
NAUTICAL_HOMER = 32776,
|
|
ICLS = 131584,
|
|
}
|
|
|
|
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
|
-- @type BEACON.System
|
|
-- @field #number PAR_10
|
|
-- @field #number RSBN_5
|
|
-- @field #number TACAN
|
|
-- @field #number TACAN_TANKER
|
|
-- @field #number ILS_LOCALIZER (This is the one to be used for AA TACAN Tanker!)
|
|
-- @field #number ILS_GLIDESLOPE
|
|
-- @field #number BROADCAST_STATION
|
|
BEACON.System={
|
|
PAR_10 = 1,
|
|
RSBN_5 = 2,
|
|
TACAN = 3,
|
|
TACAN_TANKER = 4,
|
|
ILS_LOCALIZER = 5,
|
|
ILS_GLIDESLOPE = 6,
|
|
BROADCAST_STATION = 7,
|
|
}
|
|
|
|
--- 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
|
|
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:TACAN(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:T({"TACAN BEACON started!"})
|
|
|
|
-- 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
|
|
|
|
--- Manages radio transmissions.
|
|
--
|
|
-- @type RADIOQUEUE
|
|
-- @field #string ClassName Name of the class "RADIOQUEUE".
|
|
-- @field #string lid ID for dcs.log.
|
|
-- @field #number frequency The radio frequency in Hz.
|
|
-- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM.
|
|
-- @field Core.Scheduler#SCHEDULER scheduler The scheduler.
|
|
-- @field #string RQid The radio queue scheduler ID.
|
|
-- @field #table queue The queue of transmissions.
|
|
-- @field #number Tlast Time (abs) when the last transmission finished.
|
|
-- @field Core.Point#COORDINATE sendercoord Coordinate from where transmissions are broadcasted.
|
|
-- @field #number sendername Name of the sending unit or static.
|
|
-- @field Wrapper.Positionable#POSITIONABLE positionable The positionable to broadcast the message.
|
|
-- @field #number power Power of radio station in Watts. Default 100 W.
|
|
-- @field #table numbers Table of number transmission parameters.
|
|
-- @extends Core.Base#BASE
|
|
RADIOQUEUE = {
|
|
ClassName = "RADIOQUEUE",
|
|
lid=nil,
|
|
frequency=nil,
|
|
modulation=nil,
|
|
scheduler=nil,
|
|
RQid=nil,
|
|
queue={},
|
|
Tlast=nil,
|
|
sendercoord=nil,
|
|
sendername=nil,
|
|
positionable=nil,
|
|
power=100,
|
|
numbers={},
|
|
}
|
|
|
|
--- Radio queue transmission data.
|
|
-- @type RADIOQUEUE.Transmission
|
|
-- @field #string filename Name of the file to be transmitted.
|
|
-- @field #string path Path in miz file where the file is located.
|
|
-- @field #number duration Duration in seconds.
|
|
-- @field #number Tstarted Mission time (abs) in seconds when the transmission started.
|
|
-- @field #boolean isplaying If true, transmission is currently playing.
|
|
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
|
|
|
|
|
|
--- 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.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:New(frequency, modulation)
|
|
|
|
-- Inherit base
|
|
local self=BASE:Inherit(self, BASE:New()) -- Core.Radio#RADIOQUEUE
|
|
|
|
self.lid="RADIOQUEUE | "
|
|
|
|
if frequency==nil then
|
|
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
|
|
return nil
|
|
end
|
|
|
|
-- Frequency in Hz.
|
|
self.frequency=frequency*1000000
|
|
|
|
-- Modulation.
|
|
self.modulation=modulation or radio.modulation.AM
|
|
|
|
-- Scheduler
|
|
self.scheduler=SCHEDULER:New()
|
|
|
|
return self
|
|
end
|
|
|
|
--- Start the radio queue.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #number delay (Optional) Delay in seconds, before the radio queue is started. Default 1 sec.
|
|
-- @param #number dt (Optional) Time step in seconds for checking the queue. Default 0.01 sec.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:Start(delay, dt)
|
|
|
|
delay=delay or 1
|
|
|
|
dt=dt or 0.01
|
|
|
|
self:I(self.lid..string.format("Starting RADIOQUEUE in %.1f seconds with interval dt=%.3f seconds.", delay, dt))
|
|
|
|
self.RQid=self.scheduler:Schedule(self, self._CheckRadioQueue, {}, delay, dt)
|
|
|
|
return self
|
|
end
|
|
|
|
--- Stop the radio queue. Stop scheduler and delete queue.
|
|
-- @param #RADIOQUEUE self
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:Stop()
|
|
self:I(self.lid.."Stopping RADIOQUEUE.")
|
|
self.scheduler:Stop(self.RQid)
|
|
self.queue={}
|
|
return self
|
|
end
|
|
|
|
--- Set coordinate from where the transmission is broadcasted.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param Core.Point#COORDINATE coordinate Coordinate of the sender.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:SetSenderCoordinate(coordinate)
|
|
self.sendercoord=coordinate
|
|
return self
|
|
end
|
|
|
|
--- Set name of unit or static from which transmissions are made.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #string name Name of the unit or static used for transmissions.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:SetSenderUnitName(name)
|
|
self.sendername=name
|
|
return self
|
|
end
|
|
|
|
--- Set parameters of a digit.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #number digit The digit 0-9.
|
|
-- @param #string filename The name of the sound file.
|
|
-- @param #number duration The duration of the sound file in seconds.
|
|
-- @param #string path The directory within the miz file where the sound is located. Default "l10n/DEFAULT/".
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:SetDigit(digit, filename, duration, path)
|
|
|
|
local transmission={} --#RADIOQUEUE.Transmission
|
|
transmission.filename=filename
|
|
transmission.duration=duration
|
|
transmission.path=path or "l10n/DEFAULT/"
|
|
|
|
-- Convert digit to string in case it is given as a number.
|
|
if type(digit)=="number" then
|
|
digit=tostring(digit)
|
|
end
|
|
|
|
-- Set transmission.
|
|
self.numbers[digit]=transmission
|
|
|
|
return self
|
|
end
|
|
|
|
--- Add a transmission to the radio queue.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:AddTransmission(transmission)
|
|
self:F({transmission=transmission})
|
|
|
|
-- Init.
|
|
transmission.isplaying=false
|
|
transmission.Tstarted=nil
|
|
|
|
-- Add to queue.
|
|
table.insert(self.queue, transmission)
|
|
|
|
return self
|
|
end
|
|
|
|
--- Add a transmission to the radio queue.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
|
|
-- @param #number duration Duration in seconds the file lasts.
|
|
-- @param #number path Directory path inside the miz file where the sound file is located. Default "l10n/DEFAULT/".
|
|
-- @param #number tstart Start time (abs) seconds. Default now.
|
|
-- @param #number interval Interval in seconds after the last transmission finished.
|
|
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
|
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval)
|
|
|
|
-- Sanity checks.
|
|
if not filename then
|
|
self:E(self.lid.."ERROR: No filename specified.")
|
|
return nil
|
|
end
|
|
if type(filename)~="string" then
|
|
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
|
|
return nil
|
|
end
|
|
|
|
if not duration then
|
|
self:E(self.lid.."ERROR: No duration of transmission specified.")
|
|
return nil
|
|
end
|
|
if type(duration)~="number" then
|
|
self:E(self.lid.."ERROR: Duration specified is NOT a number.")
|
|
return nil
|
|
end
|
|
|
|
|
|
local transmission={} --#RADIOQUEUE.Transmission
|
|
transmission.filename=filename
|
|
transmission.duration=duration
|
|
transmission.path=path or "l10n/DEFAULT/"
|
|
transmission.Tplay=tstart or timer.getAbsTime()
|
|
|
|
-- Add transmission to queue.
|
|
self:AddTransmission(transmission)
|
|
|
|
return self
|
|
end
|
|
|
|
--- Convert a number (as string) into a radio transmission.
|
|
-- E.g. for board number or headings.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #string number Number string, e.g. "032" or "183".
|
|
-- @param #number delay Delay before transmission in seconds.
|
|
-- @param #number interval Interval between the next call.
|
|
-- @return #number Duration of the call in seconds.
|
|
function RADIOQUEUE:Number2Transmission(number, delay, interval)
|
|
|
|
--- Split string into characters.
|
|
local function _split(str)
|
|
local chars={}
|
|
for i=1,#str do
|
|
local c=str:sub(i,i)
|
|
table.insert(chars, c)
|
|
end
|
|
return chars
|
|
end
|
|
|
|
-- Split string into characters.
|
|
local numbers=_split(number)
|
|
|
|
local wait=0
|
|
for i=1,#numbers do
|
|
|
|
-- Current number
|
|
local n=numbers[i]
|
|
|
|
-- Radio call.
|
|
local transmission=UTILS.DeepCopy(self.numbers[n]) --#RADIOQUEUE.Transmission
|
|
|
|
transmission.Tplay=timer.getAbsTime()+(delay or 0)
|
|
|
|
if interval and i==1 then
|
|
transmission.interval=interval
|
|
end
|
|
|
|
self:AddTransmission(transmission)
|
|
|
|
-- Add up duration of the number.
|
|
wait=wait+transmission.duration
|
|
end
|
|
|
|
-- Return the total duration of the call.
|
|
return wait
|
|
end
|
|
|
|
|
|
--- Broadcast radio message.
|
|
-- @param #RADIOQUEUE self
|
|
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
|
function RADIOQUEUE:Broadcast(transmission)
|
|
|
|
-- Get unit sending the transmission.
|
|
local sender=self:_GetRadioSender()
|
|
|
|
-- Construct file name.
|
|
local filename=string.format("%s%s", transmission.path, transmission.filename)
|
|
|
|
-- Create subtitle for transmission.
|
|
--local subtitle=self:_RadioSubtitle(radio, call, loud)
|
|
|
|
if sender then
|
|
|
|
-- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message.
|
|
self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName()))
|
|
|
|
-- Command to set the Frequency for the transmission.
|
|
local commandFrequency={
|
|
id="SetFrequency",
|
|
params={
|
|
frequency=self.frequency, -- Frequency in Hz.
|
|
modulation=self.modulation,
|
|
}}
|
|
|
|
-- Command to tranmit the call.
|
|
local commandTransmit={
|
|
id = "TransmitMessage",
|
|
params = {
|
|
file=filename,
|
|
duration=transmission.subduration or 5,
|
|
subtitle=transmission.subtitle or "",
|
|
loop=false,
|
|
}}
|
|
|
|
-- Set commend for frequency
|
|
sender:SetCommand(commandFrequency)
|
|
|
|
-- Set command for radio transmission.
|
|
sender:SetCommand(commandTransmit)
|
|
|
|
else
|
|
|
|
-- Broadcasting from carrier. No subtitle possible. Need to send messages to players.
|
|
self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission()."))
|
|
|
|
-- Position from where to transmit.
|
|
local vec3=nil
|
|
|
|
-- Try to get positon from sender unit/static.
|
|
if self.sendername then
|
|
local coord=self:_GetRadioSenderCoord()
|
|
if coord then
|
|
vec3=coord:GetVec3()
|
|
end
|
|
end
|
|
|
|
-- Try to get fixed positon.
|
|
if self.sendercoord and not vec3 then
|
|
vec3=self.sendercoord:GetVec3()
|
|
end
|
|
|
|
-- Transmit via trigger.
|
|
if vec3 then
|
|
trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
--- Check radio queue for transmissions to be broadcasted.
|
|
-- @param #RADIOQUEUE self
|
|
function RADIOQUEUE:_CheckRadioQueue()
|
|
|
|
-- Check if queue is empty.
|
|
if #self.queue==0 then
|
|
return
|
|
end
|
|
|
|
-- Get current abs time.
|
|
local time=timer.getAbsTime()
|
|
|
|
local playing=false
|
|
local next=nil --#RADIOQUEUE.Transmission
|
|
local remove=nil
|
|
for i,_transmission in ipairs(self.queue) do
|
|
local transmission=_transmission --#RADIOQUEUE.Transmission
|
|
|
|
-- Check if transmission time has passed.
|
|
if time>=transmission.Tplay then
|
|
|
|
-- Check if transmission is currently playing.
|
|
if transmission.isplaying then
|
|
|
|
-- Check if transmission is finished.
|
|
if time>=transmission.Tstarted+transmission.duration then
|
|
|
|
-- Transmission over.
|
|
transmission.isplaying=false
|
|
|
|
-- Remove ith element in queue.
|
|
remove=i
|
|
|
|
-- Store time last transmission finished.
|
|
self.Tlast=time
|
|
|
|
else -- still playing
|
|
|
|
-- Transmission is still playing.
|
|
playing=true
|
|
|
|
end
|
|
|
|
else -- not playing yet
|
|
|
|
local Tlast=self.Tlast
|
|
|
|
if transmission.interval==nil then
|
|
|
|
-- Not playing ==> this will be next.
|
|
if next==nil then
|
|
next=transmission
|
|
end
|
|
|
|
else
|
|
|
|
if Tlast==nil or time-Tlast>=transmission.interval then
|
|
next=transmission
|
|
else
|
|
|
|
end
|
|
end
|
|
|
|
-- We got a transmission or one with an interval that is not due yet. No need for anything else.
|
|
if next or Tlast then
|
|
break
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Transmission not due yet.
|
|
|
|
end
|
|
end
|
|
|
|
-- Found a new transmission.
|
|
if next~=nil and not playing then
|
|
self:Broadcast(next)
|
|
next.isplaying=true
|
|
next.Tstarted=time
|
|
end
|
|
|
|
-- Remove completed calls from queue.
|
|
if remove then
|
|
table.remove(self.queue, remove)
|
|
end
|
|
|
|
end
|
|
|
|
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
|
-- @param #RADIOQUEUE self
|
|
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive.
|
|
function RADIOQUEUE:_GetRadioSender()
|
|
|
|
-- Check if we have a sending aircraft.
|
|
local sender=nil --Wrapper.Unit#UNIT
|
|
|
|
-- Try the general default.
|
|
if self.sendername then
|
|
-- First try to find a unit
|
|
sender=UNIT:FindByName(self.sendername)
|
|
|
|
-- Check that sender is alive and an aircraft.
|
|
if sender and sender:IsAlive() and sender:IsAir() then
|
|
return sender
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
|
-- @param #RADIOQUEUE self
|
|
-- @return Core.Point#COORDINATE Coordinate of the sender unit.
|
|
function RADIOQUEUE:_GetRadioSenderCoord()
|
|
|
|
local vec3=nil
|
|
|
|
-- Try the general default.
|
|
if self.sendername then
|
|
|
|
-- First try to find a unit
|
|
local sender=UNIT:FindByName(self.sendername)
|
|
|
|
-- Check that sender is alive and an aircraft.
|
|
if sender and sender:IsAlive() then
|
|
return sender:GetCoodinate()
|
|
end
|
|
|
|
-- Now try a static.
|
|
local sender=STATIC:FindByName(self.sendername)
|
|
|
|
-- Check that sender is alive and an aircraft.
|
|
if sender then
|
|
return sender:GetCoodinate()
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|