diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 2a17e4c13..3f7096beb 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() @@ -2534,7 +2543,7 @@ function AIRBOSS:AddRecoveryWindow( starttime, stoptime, case, holdingoffset, tu return self end if Tstop <= Tnow then - self:I( string.format( "WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.", UTILS.SecondsToClock( Tstop ), UTILS.SecondsToClock( Tnow ) ) ) + string.format( "WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.", UTILS.SecondsToClock( Tstop ), UTILS.SecondsToClock( Tnow ) ) return self end @@ -2847,7 +2856,7 @@ function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) --Check if V/STOL Carrier if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then - + -- allow a larger GSE for V/STOL operations --Pene Testing self.gle._max=_max or 0.7 self.gle.High=High or 1.4 @@ -2864,7 +2873,7 @@ function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) self.gle.Low=Low or -0.6 self.gle.LOW=LOW or -0.9 end - + return self end @@ -2884,7 +2893,7 @@ function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, --Check if V/STOL Carrier -- Pene testing if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then - + -- V/STOL Values -- allow a larger LUE for V/STOL operations self.lue._max=_max or 1.8 self.lue._min=_min or -1.8 @@ -2905,7 +2914,7 @@ function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, self.lue.RightMed=RightMed or 2.0 self.lue.RIGHT=RIGHT or 3.0 end - + return self end @@ -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 @@ -3156,7 +3258,7 @@ function AIRBOSS:SoundCheckLSO( delay ) end -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) end end @@ -3191,7 +3293,7 @@ function AIRBOSS:SoundCheckMarshal( delay ) end -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) end end @@ -3268,7 +3370,7 @@ function AIRBOSS:SetExtraVoiceOversAI(status) self.xtVoiceOversAI=status return self end - + --- Do not handle AI aircraft. -- @param #AIRBOSS self -- @return #AIRBOSS self @@ -3383,9 +3485,9 @@ end -- @param #string Host Host. Default `"127.0.0.1"`. -- @return #AIRBOSS self function AIRBOSS:SetFunkManOn(Port, Host) - + self.funkmanSocket=SOCKET:New(Port, Host) - + return self end @@ -3582,7 +3684,7 @@ function AIRBOSS:onafterStatus( From, Event, To ) if i == 0 then text = text .. " none" end - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Check for collision. if collision then @@ -5357,6 +5459,7 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B + local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C -- Return values. local alt @@ -5822,63 +5925,30 @@ function AIRBOSS:_ScanCarrierZone() -- Get aircraft type name. local actype = group:GetTypeName() + -- Create a new flight group if knownflight then -- Debug output. self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.", groupname, actype)) -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and self.handleai then + if knownflight.ai and knownflight.flag == -100 and self.handleai then - -- Defines if AI group should be handled by the airboss. - local iscarriersquad=true + local putintomarshal = false - -- Check if AI group is part of the group set if a set was defined. - if self.squadsetAI then - local group=self.squadsetAI:FindGroup(groupname) - if group then - iscarriersquad=true + -- Get flight group. + local flight = _DATABASE:GetOpsGroup( groupname ) + + if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then + if flight.ishelo then else - iscarriersquad=false + putintomarshal = true end + flight.airboss = self end - -- Check if group was explicitly excluded. - if self.excludesetAI then - local group=self.excludesetAI:FindGroup(groupname) - if group then - iscarriersquad=false - end - - end - - - -- Get distance to carrier. - local dist=knownflight.group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - - -- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0. - local closein=knownflight.dist0-dist - - -- Debug info. - self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein))) - - -- Is this group the tanker? - local istanker=self.tanker and self.tanker.tanker:GetName()==groupname - - -- Is this group the AWACS? - local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname - - -- Send tanker to marshal stack? - local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true - - -- Send AWACS to marhsal stack? - local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true - - -- Put flight into Marshal. - local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and istanker==false and isawacs==false - -- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. - if putintomarshal or tanker2marshal or awacs2marshal then + if putintomarshal then -- Get the next free stack for current recovery case. @@ -6401,7 +6471,7 @@ function AIRBOSS:_LandAI( flight ) -- Aircraft speed when flying the pattern. local Speed = UTILS.KnotsToKmph( 200 ) - if flight.actype == AIRBOSS.AircraftCarrier.HORNET + if flight.actype == AIRBOSS.AircraftCarrier.HORNET or flight.actype == AIRBOSS.AircraftCarrier.FA18C or flight.actype == AIRBOSS.AircraftCarrier.RHINOE or flight.actype == AIRBOSS.AircraftCarrier.RHINOF @@ -6890,7 +6960,7 @@ function AIRBOSS:_GetFreeStack( ai, case, empty ) end - self:I( self.lid .. string.format( "Returning free stack %s", tostring( nfree ) ) ) + self:T( self.lid .. string.format( "Returning free stack %s", tostring( nfree ) ) ) return nfree end @@ -7824,7 +7894,7 @@ function AIRBOSS:_RemoveFlight( flight, completely ) -- Remove player from players table. local playerdata = self.players[flight.name] if playerdata then - self:I( self.lid .. string.format( "Removing player %s completely.", flight.name ) ) + self:T( self.lid .. string.format( "Removing player %s completely.", flight.name ) ) self.players[flight.name] = nil end @@ -8139,7 +8209,7 @@ end -- @param Core.Event#EVENTDATA EventData function AIRBOSS:OnEventBirth( EventData ) self:F3( { eventbirth = EventData } ) - + -- Nil checks. if EventData == nil then self:E( self.lid .. "ERROR: EventData=nil in event BIRTH!" ) @@ -8151,9 +8221,9 @@ function AIRBOSS:OnEventBirth( EventData ) self:E( EventData ) return end - + if EventData.IniObjectCategory ~= Object.Category.UNIT then return end - + local _unitName = EventData.IniUnitName local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) @@ -9231,9 +9301,9 @@ function AIRBOSS:_DirtyUp( playerData ) self:_PlayerHint( playerData ) -- Radio call "Say/Fly needles". Delayed by 10/15 seconds. - if playerData.actype == AIRBOSS.AircraftCarrier.HORNET - or playerData.actype == AIRBOSS.AircraftCarrier.F14A - or playerData.actype == AIRBOSS.AircraftCarrier.F14B + if playerData.actype == AIRBOSS.AircraftCarrier.HORNET + or playerData.actype == AIRBOSS.AircraftCarrier.F14A + or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER @@ -9830,24 +9900,24 @@ function AIRBOSS:_Groove( playerData ) return end - end - + end + -- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)--- Need to think more about this. - + --if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then -- Get groove time --local vSlow=groovedata.time - -- If too slow wave off. + -- If too slow wave off. --if vSlow >75 then - + -- LSO Wave off! --self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) --playerData.Tlso=timer.getTime() - + -- Player was waved Off --playerData.waveoff=true --return - --end + --end --end -- Groovedata step. @@ -10045,7 +10115,7 @@ function AIRBOSS:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData ) waveoff = true end - -- Too slow or too fast? Only for pros. + -- Too slow or too fast? Only for pros. if playerData.difficulty == AIRBOSS.Difficulty.HARD and playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then -- Get aircraft specific AoA values. Not for AV-8B due to transition to Stable Hover. @@ -10201,7 +10271,7 @@ function AIRBOSS:_GetSternCoord() elseif case==2 or case==1 then -- V/Stol: Translate 8 meters port. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) - end + end elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then -- Stennis: translate 7 meters starboard wrt Final bearing. self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7, FB + 90, true, true ) @@ -10350,7 +10420,7 @@ function AIRBOSS:_Trapped( playerData ) -- Get current wire (estimate). This now based on the position where the player comes to a standstill which should reflect the trapped wire better. local dcorr = 100 - if playerData.actype == AIRBOSS.AircraftCarrier.HORNET + if playerData.actype == AIRBOSS.AircraftCarrier.HORNET or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then @@ -10875,7 +10945,7 @@ function AIRBOSS:_GetZoneAbeamLandingSpot() -- Coordinate array. Pene Testing extended Abeam landing spot V/STOL. local p={} - + -- Points. p[1] = S:Translate( 15, FB ):Translate( 15, FB + 90 ) -- Top-Right p[2] = S:Translate( -45, FB ):Translate( 15, FB + 90 ) -- Bottom-Right @@ -11323,7 +11393,7 @@ function AIRBOSS:_GetOptLandingCoordinate() -- set Case III V/STOL abeam landing spot over deck -- Pene Testing if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then - + if case==3 then -- Landing coordinate. @@ -11331,7 +11401,7 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Altitude 120ft -- is this corect for Case III? self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) - + elseif case==2 or case==1 then -- Landing 100 ft abeam, 120 ft alt. @@ -11341,7 +11411,7 @@ function AIRBOSS:_GetOptLandingCoordinate() self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) end - + else -- Ideally we want to land between 2nd and 3rd wire. @@ -11485,9 +11555,49 @@ 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 ) + --self:T("windfrom="..windfrom.." vwind="..vwind) + + vwind = vwind + adjustDegreesForWindSpeed(vwind) + + --self:T("windfrom="..windfrom.." (c)vwind="..vwind) + -- Actually, we want the runway in the wind. local intowind = windfrom - self.carrierparam.rwyangle @@ -11936,7 +12046,7 @@ function AIRBOSS:_LSOgrade( playerData ) local nS=count(G, '%(') local nN=N-nS-nL local nNv=Nv-nS-nL - + -- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false @@ -12053,7 +12163,7 @@ function AIRBOSS:_LSOgrade( playerData ) elseif not playerData.hover and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then ------------------------------- - -- AV-8B not cleared to land -- -- Landing clearence is carrier from LC to Landing + -- AV-8B not cleared to land -- -- Landing clearence is carrier from LC to Landing ------------------------------- if playerData.landed then -- AIRBOSS wants your balls! @@ -13421,8 +13531,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 @@ -14625,61 +14735,144 @@ 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 + if call.subtitle ~= nil and string.len(call.subtitle) > 1 then - end + 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 - -- Append radio click sound at the end of the transmission. - if click then - self:RadioTransmission( radio, self[caller].CLICK, false, delay ) + 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:T(self.lid..text) + local srstext = self:_GetNiceSRSText(text) + self.SRSQ:NewTransmission(srstext, call.duration, self.SRS, nil, 0.1, nil, call.subtitle, call.subduration, frequency, modulation, gender, culture, voice, volume, radio.alias) + end end end +--- 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. -- @param #AIRBOSS self -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. @@ -14923,6 +15116,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 @@ -14934,7 +15160,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:T({sender,receiver,message}) if playerData and message and message ~= "" then -- Default duration. @@ -14957,49 +15183,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:T(self.lid..text) + self:T({sender,frequency,modulation,voice}) + local srstext = self:_GetNiceSRSText(text) + self.SRSQ:NewTransmission(srstext,duration,self.SRS,nil,0.1,nil,nil,nil,frequency,modulation,gender,culture,voice,nil,sender) end - -- Text message to player client. if playerData.client then MESSAGE:New( text, duration, sender, clear ):ToClient( playerData.client ) @@ -15283,13 +15551,13 @@ end -- @param #string modex Tail number. function AIRBOSS:_MarshallInboundCall(unit, modex) - -- Calculate + -- Calculate local vectorCarrier = self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate()) local bearing = UTILS.Round(unit:GetCoordinate():GetAngleDegrees( vectorCarrier ), 0) local distance = UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0) local angels = UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0) local state = UTILS.Round(self:_GetFuelState(unit)/1000,1) - + -- Pilot: "Marshall, [modex], marking mom's [bearing] for [distance], angels [XX], state [X.X]" local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f", modex, bearing, distance, angels, state) -- Debug message. @@ -15368,7 +15636,7 @@ function AIRBOSS:_LSOCallAircraftBall( modex, nickname, fuelstate ) local text = string.format( "%s Ball, %.1f.", nickname, fuelstate ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Nickname UPPERCASE. local NICKNAME = nickname:upper() @@ -15404,7 +15672,7 @@ function AIRBOSS:_MarshalCallGasAtTanker( modex ) local text = string.format( "Bingo fuel! Going for gas at the recovery tanker." ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call to display complete subtitle. @@ -15428,7 +15696,7 @@ function AIRBOSS:_MarshalCallGasAtDivert( modex, divertname ) local text = string.format( "Bingo fuel! Going for gas at divert field %s.", divertname ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call to display complete subtitle. local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex ) @@ -15450,7 +15718,7 @@ function AIRBOSS:_MarshalCallRecoveryStopped( case ) local text = string.format( "Case %d recovery ops are stopped. Deck is closed.", case ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call to display complete subtitle. local call = self:_NewRadioCall( self.MarshalCall.CASE, "AIRBOSS", text, self.Tmessage, "99" ) @@ -15491,7 +15759,7 @@ function AIRBOSS:_MarshalCallRecoveryPausedResumedAt( clock ) local text = string.format( "aircraft recovery is paused and will be resumed at %s.", clock ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call with full subtitle. local call = self:_NewRadioCall( self.MarshalCall.RECOVERYPAUSEDRESUMED, "AIRBOSS", text, self.Tmessage, "99" ) @@ -15518,7 +15786,7 @@ function AIRBOSS:_MarshalCallClearedForRecovery( modex, case ) local text = string.format( "you're cleared for Case %d recovery.", case ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call with full subtitle. local call = self:_NewRadioCall( self.MarshalCall.CLEAREDFORRECOVERY, "MARSHAL", text, self.Tmessage, modex ) @@ -15556,7 +15824,7 @@ function AIRBOSS:_MarshalCallNewFinalBearing( FB ) local text = string.format( "new final bearing %03d°.", FB ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call with full subtitle. local call = self:_NewRadioCall( self.MarshalCall.NEWFB, "AIRBOSS", text, self.Tmessage, "99" ) @@ -15579,7 +15847,7 @@ function AIRBOSS:_MarshalCallCarrierTurnTo( hdg ) local text = string.format( "carrier is now starting turn to heading %03d°.", hdg ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call with full subtitle. local call = self:_NewRadioCall( self.MarshalCall.CARRIERTURNTOHEADING, "AIRBOSS", text, self.Tmessage, "99" ) @@ -15610,7 +15878,7 @@ function AIRBOSS:_MarshalCallStackFull( modex, nwaiting ) end -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call with full subtitle. local call = self:_NewRadioCall( self.MarshalCall.STACKFULL, "AIRBOSS", text, self.Tmessage, modex ) @@ -15681,7 +15949,7 @@ function AIRBOSS:_MarshalCallArrived( modex, case, brc, altitude, charlie, qfe ) local text = string.format( "Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s. Altimeter %.2f. Report see me.", case, brc, angels, charlie, qfe ) -- Debug message. - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Create new call to display complete subtitle. local casecall = self:_NewRadioCall( self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex ) @@ -16116,8 +16384,8 @@ function AIRBOSS:_RequestMarshal( _unitName ) -- Voice over of inbound call (regardless of airboss rejecting it or not) if self.xtVoiceOvers then self:_MarshallInboundCall(_unit, playerData.onboard) - end - + end + -- Check if player is in CCA local inCCA = playerData.unit:IsInZone( self.zoneCCA ) @@ -16338,7 +16606,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 @@ -16365,12 +16633,12 @@ function AIRBOSS:_RequestCommence( _unitName ) local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then - + -- Voice over of Commencing call (regardless of Airboss will rejected or not) if self.xtVoiceOvers then self:_CommencingCall(_unit, playerData.onboard) end - + -- Check if unit is in CCA. local text = "" local cleared = false @@ -17093,7 +17361,7 @@ function AIRBOSS:_DisplayCarrierInfo( _unitname ) state = "Deck closed" end if self.turning then - state = state .. " (turning currently)" + state = state .. " (currently turning)" end -- Message text.