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