diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 99bb0ebae..3cc61f949 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -152,12 +152,16 @@ -- @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". -- @field #AIRBOSS.Radio MarshalRadio Radio for carrier calls. -- @field #number MarshalFreq Marshal radio frequency in MHz. -- @field #string MarshalModu Marshal radio modulation "AM" or "FM". +-- @field #AIRBOSS.Radio AirbossRadio Radio for carrier calls. +-- @field #number AirbossFreq Airboss radio frequency in MHz. +-- @field #string AirbossModu Airboss radio modulation "AM" or "FM". -- @field #number TowerFreq Tower radio frequency in MHz. -- @field Core.Scheduler#SCHEDULER radiotimer Radio queue scheduler. -- @field Core.Zone#ZONE_UNIT zoneCCA Carrier controlled area (CCA), i.e. a zone of 50 NM radius around the carrier. @@ -1731,6 +1735,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 +1751,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.3.0" +AIRBOSS.version = "1.3.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1876,6 +1884,7 @@ function AIRBOSS:New( carriername, alias ) -- Set up Airboss radio. self:SetMarshalRadio() + self:SetAirbossRadio() -- Set up LSO radio. self:SetLSORadio() @@ -3043,12 +3052,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, Airboss Culture) Culture, defaults to "en-US". +-- @param #string Gender (Optional, Airboss Gender) Gender, e.g. "male" or "female". Defaults to "male". +-- @param #string Voice (Optional, Airboss Voice) 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.AirbossRadio.frequency + local Modulation = self.AirbossRadio.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.AirbossRadio.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,16 +3116,62 @@ 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 ---- Set carrier radio frequency and modulation. Default frequency is 305 MHz AM. +--- Set Airboss radio frequency and modulation. Default frequency is Tower frequency. +-- @param #AIRBOSS self +-- @param #number Frequency (Optional) Frequency in MHz. Default frequency is Tower frequency. +-- @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 +-- @usage +-- -- Set single frequency +-- myairboss:SetAirbossRadio(127.5,"AM",MSRS.Voices.Google.Standard.en_GB_Standard_F) +-- +-- -- Set multiple frequencies, note you **need** to pass one modulation per frequency given! +-- myairboss:SetAirbossRadio({127.5,243},{radio.modulation.AM,radio.modulation.AM},MSRS.Voices.Google.Standard.en_GB_Standard_F) +function AIRBOSS:SetAirbossRadio( Frequency, Modulation, Voice, Gender, Culture ) + + self.AirbossFreq = Frequency or self:_GetTowerFrequency() or 127.5 + Modulation = Modulation or "AM" + + if type(Modulation) == "table" then + self.AirbossModu = Modulation + else + if Modulation == "FM" then + self.AirbossModu = radio.modulation.FM + else + self.AirbossModu = radio.modulation.AM + end + end + + self.AirbossRadio = {} -- #AIRBOSS.Radio + self.AirbossRadio.frequency = self.AirbossFreq + self.AirbossRadio.modulation = self.AirbossModu + self.AirbossRadio.alias = "AIRBOSS" + self.AirbossRadio.voice = Voice + self.AirbossRadio.gender = Gender or "male" + self.AirbossRadio.culture = Culture or "en-US" + + return self +end + +--- Set Marshal radio frequency and modulation. Default frequency is 305 MHz AM. -- @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 +3186,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 @@ -11448,8 +11550,42 @@ end -- @return #number Carrier heading in degrees. function AIRBOSS:GetHeadingIntoWind( magnetic, coord ) + local function adjustDegreesForWindSpeed(windSpeed) + local degreesAdjustment = 0 + -- the windspeeds are in m/s + + -- +0 degrees at 15m/s = 37kts + -- +0 degrees at 14m/s = 35kts + -- +0 degrees at 13m/s = 33kts + -- +4 degrees at 12m/s = 31kts + -- +4 degrees at 11m/s = 29kts + -- +4 degrees at 10m/s = 27kts + -- +4 degrees at 9m/s = 27kts + -- +4 degrees at 8m/s = 27kts + -- +8 degrees at 7m/s = 27kts + -- +8 degrees at 6m/s = 27kts + -- +8 degrees at 5m/s = 26kts + -- +20 degrees at 4m/s = 26kts + -- +20 degrees at 3m/s = 26kts + -- +30 degrees at 2m/s = 26kts 1s + + if windSpeed > 0 and windSpeed < 3 then + degreesAdjustment = 30 + elseif windSpeed >= 3 and windSpeed < 5 then + degreesAdjustment = 20 + elseif windSpeed >= 5 and windSpeed < 8 then + degreesAdjustment = 8 + elseif windSpeed >= 8 and windSpeed < 13 then + degreesAdjustment = 4 + elseif windSpeed >= 13 then + degreesAdjustment = 0 + end + + return degreesAdjustment + end + -- Get direction the wind is blowing from. This is where we want to go. - local windfrom, vwind = self:GetWind( nil, nil, coord ) + local windfrom, vwind = self:GetWind( nil, nil, coord ) + adjustDegreesForWindSpeed(vwind) -- Actually, we want the runway in the wind. local intowind = windfrom - self.carrierparam.rwyangle @@ -13384,8 +13520,8 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) -- Wind speed. local _, vwind = self:GetWind() - -- Speed of carrier in m/s but at least 2 knots. - local vtot = math.max( vdeck - vwind, UTILS.KnotsToMps( 2 ) ) + -- Speed of carrier in m/s but at least 4 knots. + local vtot = math.max( vdeck - vwind, UTILS.KnotsToMps( 4 ) ) -- Distance to travel local dist = vtot * time @@ -14588,59 +14724,140 @@ 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 + + -- 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 == "AIRBOSS" then + frequency = self.AirbossRadio.frequency + modulation = self.AirbossRadio.modulation + voice = self.AirbossRadio.voice + gender = self.AirbossRadio.gender + culture = self.AirbossRadio.culture + end + + if radio.alias == "MARSHAL" 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 - 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 ) + if not radio.alias then + -- TODO - what freq to use here? + frequency = self.AirbossRadio.frequency + modulation = self.AirbossRadio.modulation + 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(self.lid..text) + local srstext = self:_GetNiceSRSText(text) + 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. @@ -14886,6 +15103,39 @@ function AIRBOSS:_RadioFilename( call, loud, channel ) return filename end +--- Format text into SRS friendly string +-- @param #AIRBOSS self +-- @param #string text +-- @return #string text +function AIRBOSS:_GetNiceSRSText(text) + text = string.gsub(text,"================================\n","") + text = string.gsub(text,"||","parallel") + text = string.gsub(text,"==","perpendicular") + text = string.gsub(text,"BRC","Base recovery") + --text = string.gsub(text,"#","Number") + text = string.gsub(text,"%((%a+)%)","Morse %1") + text = string.gsub(text,"°C","° Celsius") + text = string.gsub(text,"°"," degrees") + text = string.gsub(text," FB "," Final bearing ") + text = string.gsub(text," ops"," operations ") + text = string.gsub(text," kts"," knots") + text = string.gsub(text,"TACAN","Tackan") + text = string.gsub(text,"ICLS","I.C.L.S.") + text = string.gsub(text,"LSO","L.S.O.") + text = string.gsub(text,"inHg","inches of Mercury") + text = string.gsub(text,"QFE","Q.F.E.") + text = string.gsub(text,"hPa","hecto pascal") + text = string.gsub(text," NM"," nautical miles") + text = string.gsub(text," ft"," feet") + text = string.gsub(text,"A/C","aircraft") + text = string.gsub(text,"(#[%a%d%p%s]+)\n","") + text = string.gsub(text,"%.000"," dot zero") + text = string.gsub(text,"00"," double zero") + text = string.gsub(text," 0 "," zero " ) + text = string.gsub(text,"\n","; ") + return text +end + --- Send text message to player client. -- Message format will be "SENDER: RECCEIVER, MESSAGE". -- @param #AIRBOSS self @@ -14897,7 +15147,7 @@ end -- @param #boolean clear If true, clear screen from previous messages. -- @param #number delay Delay in seconds, before the message is displayed. function AIRBOSS:MessageToPlayer( playerData, message, sender, receiver, duration, clear, delay ) - + self:I({sender,receiver,message}) if playerData and message and message ~= "" then -- Default duration. @@ -14920,49 +15170,91 @@ 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 - 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 = self.MarshalRadio.voice + local gender = self.MarshalRadio.gender + local culture = self.MarshalRadio.culture + + if not sender then sender = "AIRBOSS" 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 + if string.find(sender,"AIRBOSS" ) then + frequency = self.AirbossRadio.frequency + modulation = self.AirbossRadio.modulation + voice = self.AirbossRadio.voice + gender = self.AirbossRadio.gender + culture = self.AirbossRadio.culture + 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 + --if sender == "MARSHAL" then + --voice = self.MarshalRadio.voice + --gender = self.MarshalRadio.gender + --culture = self.MarshalRadio.culture + --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 + 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 = self.AirbossRadio.frequency + --modulation = self.AirbossRadio.modulation + --sender = "AIRBOSS" + 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 ) + self:I(self.lid..text) + self:I({sender,frequency,modulation,voice}) + local srstext = self:_GetNiceSRSText(text) + self.SRSQ:NewTransmission(srstext,duration,self.SRS,tstart,0.1,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,sender) end - -- Text message to player client. if playerData.client then MESSAGE:New( text, duration, sender, clear ):ToClient( playerData.client ) @@ -16301,7 +16593,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/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 3c967a15e..00fb6ddbd 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -114,6 +114,9 @@ -- @field #number NcarriersMax Max number of required carrier assets. -- @field Core.Zone#ZONE transportDeployZone Deploy zone of an OPSTRANSPORT. -- @field Core.Zone#ZONE transportDisembarkZone Disembark zone of an OPSTRANSPORT. +-- @param #table carrierCategories Transport group categories. +-- @field #table carrierAttributes Generalized attribute(s) of transport assets. +-- @field #table carrierProperties DCS attribute(s) of transport assets. -- -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. @@ -3056,15 +3059,18 @@ end -- @param #number NcarriersMin Number of carriers *at least* required. Default 1. -- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. -- @param Core.Zone#ZONE DisembarkZone Zone where assets are disembarked to. +-- @param #table Categories Group categories. +-- @param #table Attributes Generalizes group attributes. +-- @param #table Properties DCS attributes. -- @return #AUFTRAG self -function AUFTRAG:SetRequiredTransport(DeployZone, NcarriersMin, NcarriersMax, DisembarkZone) +function AUFTRAG:SetRequiredTransport(DeployZone, NcarriersMin, NcarriersMax, DisembarkZone, Categories, Attributes, Properties) -- OPS transport from pickup to deploy zone. self.transportDeployZone=DeployZone self.transportDisembarkZone=DisembarkZone -- Set required carriers. - self:SetRequiredCarriers(NcarriersMin, NcarriersMax) + self:SetRequiredCarriers(NcarriersMin, NcarriersMax, Categories, Attributes, Properties) return self end @@ -3115,8 +3121,11 @@ end -- @param #AUFTRAG self -- @param #number NcarriersMin Number of carriers *at least* required. Default 1. -- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @param #table Categories Group categories. +-- @param #table Attributes Group attributes. See `GROUP.Attribute.` +-- @param #table Properties DCS attributes. -- @return #AUFTRAG self -function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax) +function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax, Categories, Attributes, Properties) self.NcarriersMin=NcarriersMin or 1 @@ -3127,6 +3136,10 @@ function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax) self.NcarriersMax=self.NcarriersMin end + self.carrierCategories = UTILS.EnsureTable(Categories, true) + self.carrierAttributes = UTILS.EnsureTable(Attributes, true) + self.carrierProperties = UTILS.EnsureTable(Properties, true) + return self end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 16b9d43e1..0af40ed2d 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1508,7 +1508,7 @@ function COMMANDER:CheckMissionQueue() local Transport=nil local Legions=mission.transportLegions or self.legions - TransportAvail, Transport=LEGION.AssignAssetsForTransport(self, Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone) + TransportAvail, Transport=LEGION.AssignAssetsForTransport(self, Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone, mission.carrierCategories, mission.carrierAttributes, mission.carrierProperties) -- Add opstransport to mission. if TransportAvail and Transport then @@ -1702,8 +1702,7 @@ function COMMANDER:RecruitAssetsForMission(Mission) local cohort=_cohort --Ops.Cohort#COHORT -- Check if cohort can perform transport to target. - --TODO: Option to filter transport carrier asset categories, attributes and/or properties. - local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Categories, Attributes, Properties, nil, TargetVec2) + local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Mission.carrierCategories, Mission.carrierAttributes, Mission.carrierProperties, nil, TargetVec2) -- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos. if can and (MaxWeight==nil or cohort.cargobayLimit>MaxWeight) then diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index d05f456f2..15cad88be 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -714,7 +714,7 @@ function LEGION:CheckMissionQueue() local Legions=mission.transportLegions or {self} -- Assign carrier assets for transport. - TransportAvail, Transport=self:AssignAssetsForTransport(Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone) + TransportAvail, Transport=self:AssignAssetsForTransport(Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone, mission.carrierCategories, mission.carrierAttributes, mission.carrierProperties) end -- Add opstransport to mission. @@ -2252,8 +2252,7 @@ function LEGION:RecruitAssetsForMission(Mission) local cohort=_cohort --Ops.Cohort#COHORT -- Check if cohort can perform transport to target. - --TODO: Option to filter transport carrier asset categories, attributes and/or properties. - local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Categories, Attributes, Properties, nil, TargetVec2) + local can=LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Mission.carrierCategories, Mission.carrierAttributes, Mission.carrierProperties, nil, TargetVec2) -- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos. if can and (MaxWeight==nil or cohort.cargobayLimit>MaxWeight) then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 1da630eb6..a69e5f627 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1291,8 +1291,8 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) -- Convert to knots. vwind=UTILS.MpsToKnots(vwind) - -- Speed of carrier relative to wind but at least 2 knots. - local speed=math.max(IntoWind.Speed-vwind, 2) + -- Speed of carrier relative to wind but at least 4 knots. + local speed=math.max(IntoWind.Speed-vwind, 4) -- Debug info. self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f Vwind=%.1f Vtot=%.1f knots, Tstart=%d Tstop=%d", IntoWind.Heading, speed, vwind, speed+vwind, IntoWind.Tstart, IntoWind.Tstop)) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 50fc27582..a7c127b1f 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1536,7 +1536,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.60" +PLAYERTASKCONTROLLER.version="0.1.60a" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -2195,8 +2195,10 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) - if EventData.IniUnitName then - self:_BuildMenus(CLIENT:FindByName(EventData.IniUnitName)) + if EventData.IniPlayerName then + self.PlayerMenu[EventData.IniPlayerName] = nil + --self:_BuildMenus(CLIENT:FindByName(EventData.IniUnitName)) + self:_BuildMenus(CLIENT:FindByPlayerName(EventData.IniPlayerName)) end end end @@ -3460,7 +3462,14 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess) self:T(self.lid.."_BuildMenus") - + + if self.MenuBuildLocked and (timer.getAbsTime() - self.MenuBuildLocked < 2) then + self:ScheduleOnce(2,self._BuildMenus,self,Client,enforced,fromsuccess) + return self + else + self.MenuBuildLocked = timer.getAbsTime() + end + local clients = self.ClientSet:GetAliveSet() local joinorabort = false local timedbuild = false @@ -3653,6 +3662,7 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess) end end end + self.MenuBuildLocked = false return self end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index fd35395bb..7caaec953 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1364,6 +1364,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 @@ -1443,8 +1448,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 @@ -1479,6 +1489,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) @@ -1492,7 +1507,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/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 4b4f41450..66aca8d82 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -426,7 +426,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Al_Dumayr -- * AIRBASE.Syria.Gazipasa -- * AIRBASE.Syria.Hatay --- * AIRBASE.Syria.Nicosia +-- * AIRBASE.Syria.Nicosia [Deactivated by ED as of June/2023] -- * AIRBASE.Syria.Pinarbashi -- * AIRBASE.Syria.Paphos -- * AIRBASE.Syria.Kingsfield @@ -491,7 +491,7 @@ AIRBASE.Syria={ ["Al_Dumayr"]="Al-Dumayr", ["Gazipasa"]="Gazipasa", ["Hatay"]="Hatay", - ["Nicosia"]="Nicosia", + --["Nicosia"]="Nicosia", ["Pinarbashi"]="Pinarbashi", ["Paphos"]="Paphos", ["Kingsfield"]="Kingsfield", 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 diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index a1e47f766..571e0d48c 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -239,19 +239,18 @@ end function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then --- local status, vec3 = pcall( --- function() --- local vec3 = DCSPositionable:getPoint() --- return vec3 --- end --- ) + --local status, vec3 = pcall( + -- function() + -- local vec3 = DCSPositionable:getPoint() + -- return vec3 + --end + --) local vec3 = DCSPositionable:getPoint() - return vec3 --- if status then --- return vec3 --- else --- self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } ) --- end + --if status then + return vec3 + --else + --self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } ) + --end end -- ERROR! self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } ) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index cdc5d63d6..cd6769fc2 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -703,15 +703,15 @@ function UNIT:GetAmmo() self:F2( self.UnitName ) local DCSUnit = self:GetDCSObject() if DCSUnit then --- local status, unitammo = pcall( --- function() --- local UnitAmmo = DCSUnit:getAmmo() --- return UnitAmmo --- end --- ) --- if status then --- return unitammo --- end + --local status, unitammo = pcall( + -- function() + -- local UnitAmmo = DCSUnit:getAmmo() + -- return UnitAmmo + --end + --) + --if status then + --return unitammo + --end local UnitAmmo = DCSUnit:getAmmo() return UnitAmmo end