From c80213b4cfb0dc60ebed975adeb92e60d8d1cf97 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Jun 2023 18:18:18 +0200 Subject: [PATCH] #AIRBOSS * Initial SRS addition --- Moose Development/Moose/Ops/Airboss.lua | 346 +++++++++++++++++------- Moose Development/Moose/Sound/SRS.lua | 19 +- Moose Development/Moose/Wrapper/Net.lua | 22 +- 3 files changed, 283 insertions(+), 104 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 99bb0ebae..d006f824c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -152,6 +152,7 @@ -- @field #boolean ICLSon Automatic ICLS is activated. -- @field #number ICLSchannel ICLS channel. -- @field #string ICLSmorse ICLS morse code, e.g. "STN". +-- @field #AIRBOSS.Radio PilotRadio Radio for Pilot calls. -- @field #AIRBOSS.Radio LSORadio Radio for LSO calls. -- @field #number LSOFreq LSO radio frequency in MHz. -- @field #string LSOModu LSO radio modulation "AM" or "FM". @@ -1731,6 +1732,10 @@ AIRBOSS.Difficulty = { -- @field #table trapsheet Groove data table recorded every 0.5 seconds. -- @field #boolean trapon If true, save trap sheets. -- @field #string debriefschedulerID Debrief scheduler ID. +-- +-- @field Sound.SRS#MSRS SRS +-- @field Sound.SRS#MSRSQUEUE SRSQ +-- -- @extends #AIRBOSS.FlightGroup --- Main group level radio menu: F10 Other/Airboss. @@ -1743,7 +1748,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.3.0" +AIRBOSS.version = "1.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3043,12 +3048,56 @@ function AIRBOSS:SetBeaconRefresh( TimeInterval ) return self end +--- Set up SRS for usage without sound files +-- @param #AIRBOSS self +-- @param #string PathToSRS Path to SRS folder, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone". +-- @param #number Port Port of the SRS server, defaults to 5002. +-- @param #string Culture (Optional) Culture, defaults to "en-US". +-- @param #string Gender (Optional) Gender, e.g. "male" or "female". Defaults to "male". +-- @param #string Voice (Optional) Set to use a specific voice. Will **override gender and culture** settings. +-- @param #string GoogleCreds (Optional) Path to Google credentials, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourgooglekey.json". +-- @param #number Volume (Optional) E.g. 0.75. Defaults to 1.0 (loudest). +-- @param #table AltBackend (Optional) See MSRS for details. +-- @return #AIRBOSS self +function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volume,AltBackend) + -- SRS + local Frequency = self.MarshalRadio.frequency + local Modulation = self.MarshalRadio.modulation + self.SRS = MSRS:New(PathToSRS,Frequency,Modulation,Volume,AltBackend) + self.SRS:SetCoalition(self:GetCoalition()) + self.SRS:SetCoordinate(self:GetCoordinate()) + self.SRS:SetCulture(Culture or "en-US") + --self.SRS:SetFrequencies(Frequencies) + self.SRS:SetGender(Gender or "male") + self.SRS:SetPath(PathToSRS) + self.SRS:SetPort(Port or 5002) + self.SRS:SetLabel(self.MarshalRadio.alias or "AIRBOSS") + --self.SRS:SetModulations(Modulations) + if GoogleCreds then + self.SRS:SetGoogle(GoogleCreds) + end + if Voice then + self.SRS:SetVoice(Voice) + end + self.SRS:SetVolume(Volume or 1.0) + -- SRSQUEUE + self.SRSQ = MSRSQUEUE:New("AIRBOSS") + self.SRSQ:SetTransmitOnlyWithPlayers(true) + if not self.PilotRadio then + self:SetSRSPilotVoice() + end + return self +end + --- Set LSO radio frequency and modulation. Default frequency is 264 MHz AM. -- @param #AIRBOSS self -- @param #number Frequency (Optional) Frequency in MHz. Default 264 MHz. -- @param #string Modulation (Optional) Modulation, "AM" or "FM". Default "AM". +-- @param #string Voice (Optional) SRS specific voice +-- @param #string Gender (Optional) SRS specific gender +-- @param #string Culture (Optional) SRS specific culture -- @return #AIRBOSS self -function AIRBOSS:SetLSORadio( Frequency, Modulation ) +function AIRBOSS:SetLSORadio( Frequency, Modulation, Voice, Gender, Culture ) self.LSOFreq = (Frequency or 264) Modulation = Modulation or "AM" @@ -3063,6 +3112,9 @@ function AIRBOSS:SetLSORadio( Frequency, Modulation ) self.LSORadio.frequency = self.LSOFreq self.LSORadio.modulation = self.LSOModu self.LSORadio.alias = "LSO" + self.LSORadio.voice = Voice + self.LSORadio.gender = Gender or "male" + self.LSORadio.culture = Culture or "en-US" return self end @@ -3071,8 +3123,11 @@ end -- @param #AIRBOSS self -- @param #number Frequency (Optional) Frequency in MHz. Default 305 MHz. -- @param #string Modulation (Optional) Modulation, "AM" or "FM". Default "AM". +-- @param #string Voice (Optional) SRS specific voice +-- @param #string Gender (Optional) SRS specific gender +-- @param #string Culture (Optional) SRS specific culture -- @return #AIRBOSS self -function AIRBOSS:SetMarshalRadio( Frequency, Modulation ) +function AIRBOSS:SetMarshalRadio( Frequency, Modulation, Voice, Gender, Culture ) self.MarshalFreq = Frequency or 305 Modulation = Modulation or "AM" @@ -3087,6 +3142,9 @@ function AIRBOSS:SetMarshalRadio( Frequency, Modulation ) self.MarshalRadio.frequency = self.MarshalFreq self.MarshalRadio.modulation = self.MarshalModu self.MarshalRadio.alias = "MARSHAL" + self.MarshalRadio.voice = Voice + self.MarshalRadio.gender = Gender or "male" + self.MarshalRadio.culture = Culture or "en-US" return self end @@ -14588,59 +14646,129 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p if radio == nil or call == nil then return end - - -- Create a new radio transmission item. - local transmission = {} -- #AIRBOSS.Radioitem - - transmission.radio = radio - transmission.call = call - transmission.Tplay = timer.getAbsTime() + (delay or 0) - transmission.interval = interval - transmission.isplaying = false - transmission.Tstarted = nil - transmission.loud = loud and call.loud - - -- Player onboard number if sender has one. - if self:_IsOnboard( call.modexsender ) then - self:_Number2Radio( radio, call.modexsender, delay, 0.3, pilotcall ) - end - - -- Play onboard number if receiver has one. - if self:_IsOnboard( call.modexreceiver ) then - self:_Number2Radio( radio, call.modexreceiver, delay, 0.3, pilotcall ) - end - - -- Add transmission to the right queue. - local caller = "" - if radio.alias == "LSO" then - - table.insert( self.RQLSO, transmission ) - - caller = "LSOCall" - - -- Schedule radio queue checks. - if not self.RQLid then - self:T( self.lid .. string.format( "Starting LSO radio queue." ) ) - self.RQLid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQLSO, "LSO" }, 0.02, 0.05 ) + + if not self.SRS then + + -- Create a new radio transmission item. + local transmission = {} -- #AIRBOSS.Radioitem + + transmission.radio = radio + transmission.call = call + transmission.Tplay = timer.getAbsTime() + (delay or 0) + transmission.interval = interval + transmission.isplaying = false + transmission.Tstarted = nil + transmission.loud = loud and call.loud + + -- Player onboard number if sender has one. + if self:_IsOnboard( call.modexsender ) then + self:_Number2Radio( radio, call.modexsender, delay, 0.3, pilotcall ) end - - elseif radio.alias == "MARSHAL" then - - table.insert( self.RQMarshal, transmission ) - - caller = "MarshalCall" - - if not self.RQMid then - self:T( self.lid .. string.format( "Starting Marhal radio queue." ) ) - self.RQMid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQMarshal, "MARSHAL" }, 0.02, 0.05 ) + + -- Play onboard number if receiver has one. + if self:_IsOnboard( call.modexreceiver ) then + self:_Number2Radio( radio, call.modexreceiver, delay, 0.3, pilotcall ) end - + + -- Add transmission to the right queue. + local caller = "" + if radio.alias == "LSO" then + + table.insert( self.RQLSO, transmission ) + + caller = "LSOCall" + + -- Schedule radio queue checks. + if not self.RQLid then + self:T( self.lid .. string.format( "Starting LSO radio queue." ) ) + self.RQLid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQLSO, "LSO" }, 0.02, 0.05 ) + end + + elseif radio.alias == "MARSHAL" then + + table.insert( self.RQMarshal, transmission ) + + caller = "MarshalCall" + + if not self.RQMid then + self:T( self.lid .. string.format( "Starting Marhal radio queue." ) ) + self.RQMid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQMarshal, "MARSHAL" }, 0.02, 0.05 ) + end + + end + + -- Append radio click sound at the end of the transmission. + if click then + self:RadioTransmission( radio, self[caller].CLICK, false, delay ) + end + + else + -- SRS transmission + + local frequency = self.MarshalRadio.frequency + local modulation = self.MarshalRadio.modulation + local voice = nil + local gender = nil + local culture = nil + + if radio.alias == "MARSHAL" or radio.alias == "AIRBOSS" then + voice = self.MarshalRadio.voice + gender = self.MarshalRadio.gender + culture = self.MarshalRadio.culture + end + if radio.alias == "LSO" then + frequency = self.LSORadio.frequency + modulation = self.LSORadio.modulation + voice = self.LSORadio.voice + gender = self.LSORadio.gender + culture = self.LSORadio.culture + end + if pilotcall then + voice = self.PilotRadio.voice + gender = self.PilotRadio.gender + culture = self.PilotRadio.culture + radio.alias = "PILOT" + end + if not radio.alias then + -- TODO - what freq to use here? + frequency = 243 + modulation = radio.modulation.AM + radio.alias = "AIRBOSS" + end + + local volume = nil + + if loud then + volume = 1.0 + end + + --local text = tostring(call.modexreceiver).."; "..radio.alias.."; "..call.subtitle + local text = call.subtitle + self:I(text) + local srstext = string.gsub(text,"\n","; ") + self.SRSQ:NewTransmission(srstext, call.duration, self.SRS, tstart, 0.1, subgroups, call.subtitle, call.subduration, frequency, modulation, gender, culture, voice, volume, radio.alias) end +end - -- Append radio click sound at the end of the transmission. - if click then - self:RadioTransmission( radio, self[caller].CLICK, false, delay ) +--- Set SRS voice for the pilot calls. +-- @param #AIRBOSS self +-- @param #string Voice (Optional) SRS specific voice +-- @param #string Gender (Optional) SRS specific gender +-- @param #string Culture (Optional) SRS specific culture +-- @return #AIRBOSS self +function AIRBOSS:SetSRSPilotVoice( Voice, Gender, Culture ) + + self.PilotRadio = {} -- #AIRBOSS.Radio + self.PilotRadio.alias = "PILOT" + self.PilotRadio.voice = Voice or MSRS.Voices.Microsoft.David + self.PilotRadio.gender = Gender or "male" + self.PilotRadio.culture = Culture or "en-US" + + if (not Voice) and self.SRS and self.SRS.google then + self.PilotRadio.voice = MSRS.Voices.Google.Standard.en_US_Standard_J end + + return self end --- Check if a call needs a subtitle because the complete voice overs are not available. @@ -14920,49 +15048,81 @@ function AIRBOSS:MessageToPlayer( playerData, message, sender, receiver, duratio -- SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) self:ScheduleOnce( delay, self.MessageToPlayer, self, playerData, message, sender, receiver, duration, clear ) else - - -- Wait until previous sound finished. - local wait = 0 - - -- Onboard number to get the attention. - if receiver == playerData.onboard then - - -- Which voice over number to use. - if sender and (sender == "LSO" or sender == "MARSHAL" or sender == "AIRBOSS") then - - -- User sound of board number. - wait = wait + self:_Number2Sound( playerData, sender, receiver ) - + + if not self.SRS then + -- Wait until previous sound finished. + local wait = 0 + + -- Onboard number to get the attention. + if receiver == playerData.onboard then + + -- Which voice over number to use. + if sender and (sender == "LSO" or sender == "MARSHAL" or sender == "AIRBOSS") then + + -- User sound of board number. + wait = wait + self:_Number2Sound( playerData, sender, receiver ) + + end end + + -- Negative. + if string.find( text:lower(), "negative" ) then + local filename = self:_RadioFilename( self.MarshalCall.NEGATIVE, false, "MARSHAL" ) + USERSOUND:New( filename ):ToGroup( playerData.group, wait ) + wait = wait + self.MarshalCall.NEGATIVE.duration + end + + -- Affirm. + if string.find( text:lower(), "affirm" ) then + local filename = self:_RadioFilename( self.MarshalCall.AFFIRMATIVE, false, "MARSHAL" ) + USERSOUND:New( filename ):ToGroup( playerData.group, wait ) + wait = wait + self.MarshalCall.AFFIRMATIVE.duration + end + + -- Roger. + if string.find( text:lower(), "roger" ) then + local filename = self:_RadioFilename( self.MarshalCall.ROGER, false, "MARSHAL" ) + USERSOUND:New( filename ):ToGroup( playerData.group, wait ) + wait = wait + self.MarshalCall.ROGER.duration + end + + -- Play click sound to end message. + if wait > 0 then + local filename = self:_RadioFilename( self.MarshalCall.CLICK ) + USERSOUND:New( filename ):ToGroup( playerData.group, wait ) + end + else + -- SRS transmission + local frequency = self.MarshalRadio.frequency + local modulation = self.MarshalRadio.modulation + local voice = nil + local gender = nil + local culture = nil + + if sender == "MARSHAL" or sender == "AIRBOSS" then + voice = self.MarshalRadio.voice + gender = self.MarshalRadio.gender + culture = self.MarshalRadio.culture + end + if sender == "LSO" then + frequency = self.LSORadio.frequency + modulation = self.LSORadio.modulation + voice = self.LSORadio.voice + gender = self.LSORadio.gender + culture = self.LSORadio.culture + elseif not sender then + -- TODO - what freq to use here? + frequency = 243 + modulation = radio.modulation.AM + sender = "AIRBOSS" + end + + -- local text = tostring(receiver or "").."; "..sender.."; "..text + --local text = sender.."; "..text + self:I(text) + local srstext = string.gsub(text,"\n","; ") + self.SRSQ:NewTransmission(srstext,duration,self.SRS,tstart,0.1,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,sender) end - - -- Negative. - if string.find( text:lower(), "negative" ) then - local filename = self:_RadioFilename( self.MarshalCall.NEGATIVE, false, "MARSHAL" ) - USERSOUND:New( filename ):ToGroup( playerData.group, wait ) - wait = wait + self.MarshalCall.NEGATIVE.duration - end - - -- Affirm. - if string.find( text:lower(), "affirm" ) then - local filename = self:_RadioFilename( self.MarshalCall.AFFIRMATIVE, false, "MARSHAL" ) - USERSOUND:New( filename ):ToGroup( playerData.group, wait ) - wait = wait + self.MarshalCall.AFFIRMATIVE.duration - end - - -- Roger. - if string.find( text:lower(), "roger" ) then - local filename = self:_RadioFilename( self.MarshalCall.ROGER, false, "MARSHAL" ) - USERSOUND:New( filename ):ToGroup( playerData.group, wait ) - wait = wait + self.MarshalCall.ROGER.duration - end - - -- Play click sound to end message. - if wait > 0 then - local filename = self:_RadioFilename( self.MarshalCall.CLICK ) - USERSOUND:New( filename ):ToGroup( playerData.group, wait ) - end - -- Text message to player client. if playerData.client then MESSAGE:New( text, duration, sender, clear ):ToClient( playerData.client ) @@ -16301,7 +16461,7 @@ function AIRBOSS:_RequestSpinning( _unitName ) -- Some advice. if playerData.difficulty == AIRBOSS.Difficulty.EASY then local text = "Climb to 1200 feet and proceed to the initial again." - self:MessageToPlayer( playerData, text, "INSTRUCTOR", "" ) + self:MessageToPlayer( playerData, text, "AIRBOSS", "" ) end return diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 66e942b90..62f2f1c2f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1347,6 +1347,11 @@ MSRSQUEUE = { -- @field #number interval Interval in seconds before next transmission. -- @field #boolean TransmitOnlyWithPlayers If true, only transmit if there are alive Players. -- @field Core.Set#SET_CLIENT PlayerSet PlayerSet created when TransmitOnlyWithPlayers == true +-- @field #string gender Voice gender +-- @field #string culture Voice culture +-- @field #string voice Voice if any +-- @field #number volume Volume +-- @field #string label Label to be used --- Create a new MSRSQUEUE object for a given radio frequency/modulation. -- @param #MSRSQUEUE self @@ -1426,8 +1431,13 @@ end -- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. -- @param #number frequency Radio frequency if other than MSRS default. -- @param #number modulation Radio modulation if other then MSRS default. +-- @param #string gender Gender of the voice +-- @param #string culture Culture of the voice +-- @param #string voice Specific voice +-- @param #number volume Volume setting +-- @param #string label Label to be used -- @return #MSRSQUEUE.Transmission Radio transmission table. -function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation) +function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then @@ -1462,6 +1472,11 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr else transmission.subduration=0 --nil end + transmission.gender = gender + transmission.culture = culture + transmission.voice = voice + transmission.gender = volume + transmission.label = label -- Add transmission to queue. self:AddTransmission(transmission) @@ -1475,7 +1490,7 @@ end function MSRSQUEUE:Broadcast(transmission) if transmission.frequency then - transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, Gender, Culture, Voice, Volume, Label) + transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label) else transmission.msrs:PlayText(transmission.text) end diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua index f42c1c1ee..289d13155 100644 --- a/Moose Development/Moose/Wrapper/Net.lua +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -35,6 +35,7 @@ do -- @field #number id -- @field #number side -- @field #number slot +-- @field #numner timestamp --- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) -- with some added FSM functions and options to block/unblock players in MP environments. @@ -205,7 +206,7 @@ function NET:_EventHandler(EventData) -- Joining if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then - self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid) + self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id) -- Check for blockages local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot) @@ -213,15 +214,18 @@ function NET:_EventHandler(EventData) -- block pilot local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' ) else - self.KnownPilots[name] = { - name = name, - ucid = ucid, - id = PlayerID, - side = PlayerSide, - slot = PlayerSlot, - } local client = CLIENT:FindByPlayerName(name) or data.IniUnit - self:__PlayerJoined(1,client,name) + if not self.KnownPilots[name] or (self.KnownPilots[name] and TNow-self.KnownPilots[name].timestamp > 3) then + self:__PlayerJoined(1,client,name) + self.KnownPilots[name] = { + name = name, + ucid = ucid, + id = PlayerID, + side = PlayerSide, + slot = PlayerSlot, + timestamp = TNow, + } + end return self end end