From 1aedcf1ae466cd09a4a433173028e9a327c5785d Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 9 Sep 2019 08:19:48 +0200 Subject: [PATCH 1/2] Radio Queue --- .../Moose/AI/AI_A2G_Dispatcher.lua | 33 +- .../Moose/AI/AI_Air_Dispatcher.lua | 3 + .../Moose/AI/AI_Escort_Dispatcher.lua | 1 + Moose Development/Moose/Core/Radio.lua | 466 ----------------- Moose Development/Moose/Core/RadioQueue.lua | 483 ++++++++++++++++++ Moose Development/Moose/Core/Settings.lua | 70 ++- Moose Development/Moose/Modules.lua | 1 + .../Moose/Tasking/DetectionManager.lua | 50 +- Moose Development/Moose/Wrapper/Unit.lua | 42 +- 9 files changed, 667 insertions(+), 482 deletions(-) create mode 100644 Moose Development/Moose/Core/RadioQueue.lua diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 32a2a5a9e..e03bb740a 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -1133,7 +1133,7 @@ do -- AI_A2G_DISPATCHER self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - self:__Start( 1 ) + self:__Start( 1 ) return self end @@ -3508,15 +3508,16 @@ do -- AI_A2G_DISPATCHER self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, nil, DefenderGrouping ) function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Birth", Defender:GetName()}) + self:F({"Defender Takeoff", Defender:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", Squadron, Defender ) Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -3526,10 +3527,11 @@ do -- AI_A2G_DISPATCHER self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", Squadron, Defender ) end Dispatcher:ClearDefenderTaskTarget( Defender ) @@ -3540,9 +3542,10 @@ do -- AI_A2G_DISPATCHER self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", Squadron, Defender ) Dispatcher:ClearDefenderTaskTarget( Defender ) end @@ -3568,9 +3571,10 @@ do -- AI_A2G_DISPATCHER self:GetParent(self).onafterHome( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", Squadron, Defender ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) @@ -3620,6 +3624,7 @@ do -- AI_A2G_DISPATCHER --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) @@ -3627,16 +3632,16 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!" ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", Squadron, Defender ) Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end function Fsm:onafterEngageRoute( Defender, From, Event, To, AttackSetUnit ) self:F({"Engage Route", Defender:GetName()}) - self:GetParent(self).onafterEngageRoute( self, Defender, From, Event, To, AttackSetUnit ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) @@ -3644,8 +3649,9 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( Defender ) ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( Defender ), "Moving_on_to_ground_target.wav", 3, "A2G/", Squadron, Defender ) end + self:GetParent(self).onafterEngageRoute( self, Defender, From, Event, To, AttackSetUnit ) end function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) @@ -3653,13 +3659,14 @@ do -- AI_A2G_DISPATCHER --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local FirstUnit = AttackSetUnit:GetFirst() if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( Defender ) ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( Defender ), "Engaging_ground_target.wav", 3, "A2G/", DefenderUnitName ) end end @@ -3667,9 +3674,10 @@ do -- AI_A2G_DISPATCHER self:F({"Defender RTB", Defender:GetName()}) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", Squadron, Defender ) self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) @@ -3698,9 +3706,10 @@ do -- AI_A2G_DISPATCHER self:GetParent(self).onafterHome( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() + local DefenderUnitName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", Squadron, Defender ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index c802521dd..ac912f819 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -3289,6 +3289,9 @@ do self:Patrol( SquadronName, PatrolTaskType ) end + + + end diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua index 8615573fb..7b85afe07 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua @@ -3,6 +3,7 @@ -- ## Features: -- -- -- * Provides the facilities to trigger escorts when players join flight slots. +-- * -- -- === -- diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index b6c13185c..bd3b5e669 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -797,470 +797,4 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode) 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 diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua new file mode 100644 index 000000000..8ee0d7749 --- /dev/null +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -0,0 +1,483 @@ +--- **Core** - Queues Radio Transmissions. +-- +-- === +-- +-- ## Features: +-- +-- * Managed Radio Transmissions. +-- +-- === +-- +-- ### Authors: funkyfranky +-- +-- @module Core.RadioQueue +-- @image Core_Radio.JPG + +--- 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()) -- #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 + self:E("Sending") + self:E( { filename = filename, vec3 = vec3, modulation = self.modulation, frequency = self.frequency, power = self.power } ) + 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:GetCoordinate() + end + + -- Now try a static. + local sender=STATIC:FindByName(self.sendername) + + -- Check that sender is alive and an aircraft. + if sender then + return sender:GetCoordinate() + end + + end + + return nil +end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 1f504271d..fd1aca413 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -193,7 +193,20 @@ -- -- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds. -- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds. --- +-- +-- ## 3.5) **Era** of the battle +-- +-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare. +-- Therefore, there are 4 era that are defined within the settings: +-- +-- - **WWII** era: Use for warfare with equipment during the world war II time. +-- - **Korea** era: Use for warfare with equipment during the Korea war time. +-- - **Cold War** era: Use for warfare with equipment during the cold war time. +-- - **Modern** era: Use for warfare with modern equipment in the 2000s. +-- +-- There are different API defined that you can use with the _SETTINGS object to configure your mission script to work in one of the 4 era: +-- @{#SETTINGS.SetEraWWII}(), @{#SETTINGS.SetEraKorea}(), @{#SETTINGS.SetEraCold}(), @{#SETTINGS.SetEraModern}() +-- -- === -- -- @field #SETTINGS @@ -202,6 +215,19 @@ SETTINGS = { ShowPlayerMenu = true, } +SETTINGS.__Enum = {} + +--- @type SETTINGS.__Enum.Era +-- @field #number WWII +-- @field #number Korea +-- @field #number Cold +-- @field #number Modern +SETTINGS.__Enum.Era = { + WWII = 1, + Korea = 2, + Cold = 3, + Modern = 4, +} do -- SETTINGS @@ -223,6 +249,7 @@ do -- SETTINGS self:SetMessageTime( MESSAGE.Type.Information, 30 ) self:SetMessageTime( MESSAGE.Type.Overview, 60 ) self:SetMessageTime( MESSAGE.Type.Update, 15 ) + self:SetEraModern() return self else local Settings = _DATABASE:GetPlayerSettings( PlayerName ) @@ -838,6 +865,47 @@ do -- SETTINGS end end + + --- Configures the era of the mission to be WWII. + -- @param #SETTINGS self + -- @return #SETTINGS self + function SETTINGS:SetEraWWII() + + self.Era = SETTINGS.__Enum.Era.WWII + + end + + --- Configures the era of the mission to be Korea. + -- @param #SETTINGS self + -- @return #SETTINGS self + function SETTINGS:SetEraKorea() + + self.Era = SETTINGS.__Enum.Era.Korea + + end + + + --- Configures the era of the mission to be Cold war. + -- @param #SETTINGS self + -- @return #SETTINGS self + function SETTINGS:SetEraCold() + + self.Era = SETTINGS.__Enum.Era.Cold + + end + + + --- Configures the era of the mission to be Modern war. + -- @param #SETTINGS self + -- @return #SETTINGS self + function SETTINGS:SetEraModern() + + self.Era = SETTINGS.__Enum.Era.Modern + + end + + + end diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index a5e12f901..eb0d8f61e 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -20,6 +20,7 @@ __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) +__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 9d26e4d08..47824aa77 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -254,11 +254,46 @@ do -- DETECTION MANAGER end + --- Get the command center to communicate actions to the players. + -- @param #DETECTION_MANAGER self + -- @return Tasking.CommandCenter#COMMANDCENTER The command center. + function DETECTION_MANAGER:GetCommandCenter() + + return self.CC + end + + --- Set the frequency of communication and the mode of communication for voice overs. + -- @param #DETECTION_MANAGER self + -- @param #number RadioFrequency The frequency of communication. + -- @param #number RadioModulation The modulation of communication. + -- @param #number RadioPower The power in Watts of communication. + function DETECTION_MANAGER:SetRadioFrequency( RadioFrequency, RadioModulation, RadioPower ) + + self.RadioFrequency = RadioFrequency + self.RadioModulation = RadioModulation or radio.modulation.AM + self.RadioPower = RadioPower or 100 + + if self.RadioQueue then + self.RadioQueue:Stop() + end + + self.RadioQueue = nil + + self.RadioQueue = RADIOQUEUE:New( self.RadioFrequency, self.RadioModulation ) + self.RadioQueue.power = self.RadioPower + self.RadioQueue:Start( 0.5 ) + end + --- Send an information message to the players reporting to the command center. -- @param #DETECTION_MANAGER self -- @param #string Message The message to be sent. + -- @param #string SoundFile The name of the sound file .wav or .ogg. + -- @param #number SoundDuration The duration of the sound. + -- @param #string SoundPath The path pointing to the folder in the mission file. + -- @param #table Squadron The squadron. + -- @param Wrapper.Unit#UNIT Defender The defender sending the message. -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Message ) + function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, Squadron, Defender ) self:F( { Message = Message } ) @@ -269,6 +304,19 @@ do -- DETECTION MANAGER end end + -- Here we handle the transmission of the voice over. + -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. + if SoundFile then + local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE + if Defender and Defender:IsAlive() then + RadioQueue:SetSenderUnitName( Defender:GetName() ) + else + -- Use the airbase of the squadron as the coordinate of send. + RadioQueue:SetSenderCoordinate( Squadron.Airbase:GetCoordinate() ) + end + RadioQueue:NewTransmission( SoundFile, SoundDuration, SoundPath ) + end + return self end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 6e28f3323..6987e5ad5 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -696,7 +696,9 @@ end --- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: +-- Depending on the era and the type of unit, the following threat levels are foreseen: +-- +-- **Modern**: -- -- * Threat level 0: Unit is unarmed. -- * Threat level 1: Unit is infantry. @@ -709,13 +711,49 @@ end -- * Threat level 8: Unit is a Short Range SAM, radar guided. -- * Threat level 9: Unit is a Medium Range SAM, radar guided. -- * Threat level 10: Unit is a Long Range SAM, radar guided. +-- +-- **Cold**: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 10: Unit is a Medium Range SAM, radar guided. +-- +-- **Korea**: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 5: Unit is a tank. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 10: Unit is a Short Range SAM, radar guided. +-- +-- **WWII**: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 5: Unit is a tank. +-- * Threat level 7: Unit is FLAK. +-- * Threat level 10: Unit is AAA. +-- +-- -- @param #UNIT self function UNIT:GetThreatLevel() local ThreatLevel = 0 local ThreatText = "" - + local Descriptor = self:GetDesc() if Descriptor then From 1fd9cbec1f452b03941211af33f54391946cbf11 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 9 Sep 2019 11:17:45 +0200 Subject: [PATCH 2/2] - Added A2G voice overs to some of the basic events during defender flight. More to come, like multiple languages, and also more voice overs concerning some of the more detailed events, like: - Damage - Firing - Enemy location - Callsigns - Numbers for distance and degrees. --- .../Moose/AI/AI_A2G_Dispatcher.lua | 255 ++++++++++-------- Moose Development/Moose/Core/Database.lua | 1 + .../Moose/Tasking/DetectionManager.lua | 13 +- 3 files changed, 145 insertions(+), 124 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index e03bb740a..0d74a86d2 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3496,95 +3496,123 @@ do -- AI_A2G_DISPATCHER local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - local Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - Fsm:Start() + local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) + AI_A2G_Fsm:SetDispatcher( self ) + AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) + AI_A2G_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) + AI_A2G_Fsm:Start() - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, nil, DefenderGrouping ) + self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, nil, DefenderGrouping ) - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Takeoff", Defender:GetName()}) + function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"Defender Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", Squadron, Defender ) - Fsm:Patrol() -- Engage on the TargetSetUnit + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end - function Fsm:onafterPatrolRoute( Defender, From, Event, To ) - self:F({"Defender PatrolRoute", Defender:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) + self:F({"Defender PatrolRoute", DefenderGroup:GetName()}) + self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", Squadron, Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", DefenderGroup ) end - Dispatcher:ClearDefenderTaskTarget( Defender ) + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", Squadron, Defender ) + self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + + if Squadron then + local FirstUnit = AttackSetUnit:GetFirst() + local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE + + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) + end + end - Dispatcher:ClearDefenderTaskTarget( Defender ) + function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) + --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + local FirstUnit = AttackSetUnit:GetFirst() + if FirstUnit then + local Coordinate = FirstUnit:GetCoordinate() + + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) + end + end + + function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) + self:F({"Defender RTB", DefenderGroup:GetName()}) + self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) + self:F({"Defender LostControl", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + if DefenderGroup:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) + self:F({"Defender Home", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", Squadron, Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() + Dispatcher:ResourcePark( Squadron, DefenderGroup ) end end end @@ -3609,117 +3637,112 @@ do -- AI_A2G_DISPATCHER local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() + local AI_A2G_Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE + AI_A2G_Fsm:SetDispatcher( self ) + AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) + AI_A2G_Fsm:Start() - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, AttackerDetection, DefenderGrouping ) + self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, AttackerDetection, DefenderGrouping ) - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Birth", Defender:GetName()}) + function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"Defender Birth", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", Squadron, Defender ) - Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end - function Fsm:onafterEngageRoute( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) + function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( Defender ), "Moving_on_to_ground_target.wav", 3, "A2G/", Squadron, Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) end - self:GetParent(self).onafterEngageRoute( self, Defender, From, Event, To, AttackSetUnit ) + self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end - function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) + function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) local FirstUnit = AttackSetUnit:GetFirst() if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( Defender ), "Engaging_ground_target.wav", 3, "A2G/", DefenderUnitName ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) end end - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) + function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) + self:F({"Defender RTB", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", Squadron, Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - Dispatcher:ClearDefenderTaskTarget( Defender ) + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) + self:F({"Defender LostControl", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) --Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + if DefenderGroup:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) + self:F({"Defender Home", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local DefenderUnitName = Defender:GetName() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", Squadron, Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() + Dispatcher:ResourcePark( Squadron, DefenderGroup ) end end end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 3a4dccefd..4463defbc 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -187,6 +187,7 @@ end function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then + self:I( { "Add UNIT:", DCSUnitName } ) local UnitRegister = UNIT:Register( DCSUnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 47824aa77..8bec92065 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -290,10 +290,9 @@ do -- DETECTION MANAGER -- @param #string SoundFile The name of the sound file .wav or .ogg. -- @param #number SoundDuration The duration of the sound. -- @param #string SoundPath The path pointing to the folder in the mission file. - -- @param #table Squadron The squadron. - -- @param Wrapper.Unit#UNIT Defender The defender sending the message. + -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, Squadron, Defender ) + function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, DefenderGroup ) self:F( { Message = Message } ) @@ -308,11 +307,9 @@ do -- DETECTION MANAGER -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. if SoundFile then local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE - if Defender and Defender:IsAlive() then - RadioQueue:SetSenderUnitName( Defender:GetName() ) - else - -- Use the airbase of the squadron as the coordinate of send. - RadioQueue:SetSenderCoordinate( Squadron.Airbase:GetCoordinate() ) + local DefenderUnit = DefenderGroup:GetUnit(1) + if DefenderUnit and DefenderUnit:IsAlive() then + RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) end RadioQueue:NewTransmission( SoundFile, SoundDuration, SoundPath ) end