From 5050371e0d8dc6c83ae6d17c17cf30073d68e702 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 12 Feb 2019 16:26:44 +0100 Subject: [PATCH 01/11] AB v0.9.8w --- Moose Development/Moose/Ops/Airboss.lua | 1328 +++++++++++------------ 1 file changed, 624 insertions(+), 704 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 53d87a30c..f7446f23d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -201,6 +201,8 @@ -- @field #boolean despawnshutdown Despawn group after engine shutdown. -- @field #number Tbeacon Last time the beacons were refeshed. -- @field #number dTbeacon Time interval to refresh the beacons. Default 5 minutes. +-- @field #AIRBOSS.LSOCalls LSOCall Radio voice overs of the LSO. +-- @field #AIRBOSS.MarshalCalls MarshalCall Radio voice over of the Marshal/Airboss. -- @extends Core.Fsm#FSM --- Be the boss! @@ -998,6 +1000,8 @@ AIRBOSS = { despawnshutdown= nil, dTbeacon = nil, Tbeacon = nil, + LSOCall = nil, + MarshalCall = nil, } --- Aircraft types capable of landing on carrier (human+AI). @@ -1164,7 +1168,7 @@ AIRBOSS.GroovePos={ -- @field #string sender Sender of the message (optional). Default radio alias. --- LSO radio calls. --- @type AIRBOSS.LSOCall +-- @type AIRBOSS.LSOCalls -- @field #AIRBOSS.RadioCall RADIOCHECK "Paddles, radio check" call. -- @field #AIRBOSS.RadioCall RIGHTFORLINEUP "Right for line up" call. -- @field #AIRBOSS.RadioCall COMELEFT "Come left" call. @@ -1200,276 +1204,6 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N9 "Nine" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. -AIRBOSS.LSOCall={ - RADIOCHECK={ - file="LSO-RadioCheck", - suffix="ogg", - loud=false, - subtitle="Paddles, radio check", - duration=1.1, - subduration=5, - }, - RIGHTFORLINEUP={ - file="LSO-RightForLineup", - suffix="ogg", - loud=true, - subtitle="Right for line up", - duration=0.80, - subduration=1, - }, - COMELEFT={ - file="LSO-ComeLeft", - suffix="ogg", - loud=true, - subtitle="Come left", - duration=0.60, - subduration=1, - }, - HIGH={ - file="LSO-High", - suffix="ogg", - loud=true, - subtitle="You're high", - duration=0.65, - subduration=1, - }, - LOW={ - file="LSO-Low", - suffix="ogg", - loud=true, - subtitle="You're low", - duration=0.50, - subduration=1, - }, - POWER={ - file="LSO-Power", - suffix="ogg", - loud=true, - subtitle="Power", - duration=0.50, --0.45 was too short - subduration=1, - }, - SLOW={ - file="LSO-Slow", - suffix="ogg", - loud=true, - subtitle="You're slow", - duration=0.65, - subduration=1, - }, - FAST={ - file="LSO-Fast", - suffix="ogg", - loud=true, - subtitle="You're fast", - duration=0.7, - subduration=1, - }, - CALLTHEBALL={ - file="LSO-CallTheBall", - suffix="ogg", - loud=false, - subtitle="Call the ball", - duration=0.6, - subduration=2, - }, - ROGERBALL={ - file="LSO-RogerBall", - suffix="ogg", - loud=false, - subtitle="Roger ball", - duration=0.7, - subduration=2, - }, - WAVEOFF={ - file="LSO-WaveOff", - suffix="ogg", - loud=false, - subtitle="Wave off", - duration=0.6, - subduration=5, - }, - BOLTER={ - file="LSO-BolterBolter", - suffix="ogg", - loud=false, - subtitle="Bolter, Bolter", - duration=0.75, - subduration=5, - }, - LONGINGROOVE={ - file="LSO-LongInTheGroove", - suffix="ogg", - loud=false, - subtitle="You're long in the groove", - duration=1.2, - subduration=5, - }, - FOULDECK={ - file="LSO-FoulDeck", - suffix="ogg", - loud=false, - subtitle="Foul deck", - duration=0.62, - subduration=5, - }, - DEPARTANDREENTER={ - file="LSO-DepartAndReenter", - suffix="ogg", - loud=false, - subtitle="Depart and re-enter", - duration=1.1, - subduration=5, - }, - PADDLESCONTACT={ - file="LSO-PaddlesContact", - suffix="ogg", - loud=false, - subtitle="Paddles, contact", - duration=1.0, - subduration=5, - }, - WELCOMEABOARD={ - file="LSO-WelcomeAboard", - suffix="ogg", - loud=false, - subtitle="Welcome aboard", - duration=1.0, - subduration=5, - }, - EXPECTHEAVYWAVEOFF={ - file="LSO-ExpectHeavyWaveoff", - suffix="ogg", - loud=false, - subtitle="Expect heavy waveoff", - duration=1.2, - subduration=5, - }, - EXPECTSPOT75={ - file="LSO-ExpectSpot75", - suffix="ogg", - loud=false, - subtitle="Expect spot 7.5", - duration=2.0, - subduration=5, - }, - CLEAREDTOLAND={ - file="LSO-ClearedToLand", - suffix="ogg", - loud=false, - subtitle="Cleared to land", - duration=1.0, - subduration=5, - }, - CHECK={ - file="LSO-Check", - suffix="ogg", - loud=false, - subtitle="Check", - duration=0.45, - subduration=2.5, - }, - STABILIZED={ - file="LSO-Stabilized", - suffix="ogg", - loud=false, - subtitle="Stabilized", - duration=0.9, - subduration=5, - }, - IDLE={ - file="LSO-Idle", - suffix="ogg", - loud=false, - subtitle="Idle", - duration=0.45, - subduration=5, - }, - N0={ - file="LSO-N0", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N1={ - file="LSO-N1", - suffix="ogg", - loud=false, - subtitle="", - duration=0.25, - }, - N2={ - file="LSO-N2", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N3={ - file="LSO-N3", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N4={ - file="LSO-N4", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N5={ - file="LSO-N5", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N6={ - file="LSO-N6", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N7={ - file="LSO-N7", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N8={ - file="LSO-N8", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N9={ - file="LSO-N9", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - CLICK={ - file="AIRBOSS-RadioClick", - suffix="ogg", - loud=false, - subtitle="", - duration=0.35, - }, - NOISE={ - file="AIRBOSS-Noise", - suffix="ogg", - loud=false, - subtitle="", - duration=3.6, - }, -} --- Marshal radio calls. -- @type AIRBOSS.MarshalCall @@ -1488,117 +1222,14 @@ AIRBOSS.LSOCall={ -- @field #AIRBOSS.RadioCall N9 "Nine" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. -AIRBOSS.MarshalCall={ - RADIOCHECK={ - file="MARSHAL-RadioCheck", - suffix="ogg", - loud=false, - subtitle="Radio check", - duration=1.1, - subduration=5, - }, - SAYNEEDLES={ - file="MARSHAL-SayNeedles", - suffix="ogg", - loud=false, - subtitle="Say needles", - duration=0.9, - subduration=5, - }, - FLYNEEDLES={ - file="MARSHAL-FlyYourNeedles", - suffix="ogg", - loud=false, - subtitle="Fly your needles", - duration=0.9, - subduration=5, - }, - -- TODO: Other voice overs for marshal. - N0={ - file="LSO-N0", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N1={ - file="LSO-N1", - suffix="ogg", - loud=false, - subtitle="", - duration=0.25, - }, - N2={ - file="LSO-N2", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N3={ - file="LSO-N3", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N4={ - file="LSO-N4", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N5={ - file="LSO-N5", - suffix="ogg", - loud=false, - subtitle="", - duration=0.39, - }, - N6={ - file="LSO-N6", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N7={ - file="LSO-N7", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, - }, - N8={ - file="LSO-N8", - suffix="ogg", - loud=false, - subtitle="", - duration=0.37, - }, - N9={ - file="LSO-N9", - suffix="ogg", - loud=false, - subtitle="", - duration=0.40, --0.38 too short - }, - CLICK={ - file="AIRBOSS-RadioClick", - suffix="ogg", - loud=false, - subtitle="", - duration=0.35, - }, - NOISE={ - file="AIRBOSS-Noise", - suffix="ogg", - loud=false, - subtitle="", - duration=3.6, - }, -} + +--- Message for player. +-- @type AIRBOSS.PlayerMessage +-- @field string message Message text. +-- @field #string sender Sender of the message. +-- @field #string receiver Receiver of the message. +-- @field #number duration +-- @field #boolean clear --- Difficulty level. -- @type AIRBOSS.Difficulty @@ -1722,6 +1353,7 @@ AIRBOSS.Difficulty={ -- @field #number finalscore Final score if points are averaged over multiple passes. -- @field #boolean valid If true, player made a valid approach. Is set true on start of Groove X. -- @field #boolean subtitles If true, display subtitles of radio messages. +-- @field #table messages Table of messages. -- @extends #AIRBOSS.FlightGroup --- Main group level radio menu: F10 Other/Airboss. @@ -1734,7 +1366,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="0.9.7" +AIRBOSS.version="0.9.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2063,22 +1695,10 @@ function AIRBOSS:New(carriername, alias) SCHEDULER:New(nil, flareme, {}, 1, 3, nil, 180) end - -- If calls should be part of self and individual for different carriers. - --[[ - -- Init default sound files. - for _name,_sound in pairs(AIRBOSS.LSOCall) do - local sound=_sound --#AIRBOSS.RadioCall - local text=string.format() - sound.subtitle=1 - sound.loud=1 - --self.radiocall[_name]=sound - end - ]] - -- Debug: if false then local text="Playing default sound files:" - for _name,_call in pairs(AIRBOSS.LSOCall) do + for _name,_call in pairs(self.LSOCall) do local call=_call --#AIRBOSS.RadioCall -- Debug text. @@ -3151,7 +2771,7 @@ function AIRBOSS:_CheckAIStatus() if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then -- Paddles: Call the ball! - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.CALLTHEBALL, false, 0) + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, false, 0) -- Pilot: "405, Hornet Ball, 3.2" -- TODO: Voice over. @@ -3162,7 +2782,7 @@ function AIRBOSS:_CheckAIStatus() MESSAGE:New(string.format("%s, %s", element.onboard, text), 15, "DEBUG"):ToAllIf(self.Debug) -- Paddles: Roger ball after 3 seconds. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.ROGERBALL, false, 6) + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 6) -- Flight element called the ball. element.ballcall=true @@ -3869,6 +3489,411 @@ function AIRBOSS:_InitTarawa() end + +--- Init voice over radio transmission call. +-- @param #AIRBOSS self +function AIRBOSS:_InitVoiceOvers() + + self.LSOCall={ + RADIOCHECK={ + file="LSO-RadioCheck", + suffix="ogg", + loud=false, + subtitle="Paddles, radio check", + duration=1.1, + subduration=5, + }, + RIGHTFORLINEUP={ + file="LSO-RightForLineup", + suffix="ogg", + loud=true, + subtitle="Right for line up", + duration=0.80, + subduration=1, + }, + COMELEFT={ + file="LSO-ComeLeft", + suffix="ogg", + loud=true, + subtitle="Come left", + duration=0.60, + subduration=1, + }, + HIGH={ + file="LSO-High", + suffix="ogg", + loud=true, + subtitle="You're high", + duration=0.65, + subduration=1, + }, + LOW={ + file="LSO-Low", + suffix="ogg", + loud=true, + subtitle="You're low", + duration=0.50, + subduration=1, + }, + POWER={ + file="LSO-Power", + suffix="ogg", + loud=true, + subtitle="Power", + duration=0.50, --0.45 was too short + subduration=1, + }, + SLOW={ + file="LSO-Slow", + suffix="ogg", + loud=true, + subtitle="You're slow", + duration=0.65, + subduration=1, + }, + FAST={ + file="LSO-Fast", + suffix="ogg", + loud=true, + subtitle="You're fast", + duration=0.7, + subduration=1, + }, + CALLTHEBALL={ + file="LSO-CallTheBall", + suffix="ogg", + loud=false, + subtitle="Call the ball", + duration=0.6, + subduration=2, + }, + ROGERBALL={ + file="LSO-RogerBall", + suffix="ogg", + loud=false, + subtitle="Roger ball", + duration=0.7, + subduration=2, + }, + WAVEOFF={ + file="LSO-WaveOff", + suffix="ogg", + loud=false, + subtitle="Wave off", + duration=0.6, + subduration=5, + }, + BOLTER={ + file="LSO-BolterBolter", + suffix="ogg", + loud=false, + subtitle="Bolter, Bolter", + duration=0.75, + subduration=5, + }, + LONGINGROOVE={ + file="LSO-LongInTheGroove", + suffix="ogg", + loud=false, + subtitle="You're long in the groove", + duration=1.2, + subduration=5, + }, + FOULDECK={ + file="LSO-FoulDeck", + suffix="ogg", + loud=false, + subtitle="Foul deck", + duration=0.62, + subduration=5, + }, + DEPARTANDREENTER={ + file="LSO-DepartAndReenter", + suffix="ogg", + loud=false, + subtitle="Depart and re-enter", + duration=1.1, + subduration=5, + }, + PADDLESCONTACT={ + file="LSO-PaddlesContact", + suffix="ogg", + loud=false, + subtitle="Paddles, contact", + duration=1.0, + subduration=5, + }, + WELCOMEABOARD={ + file="LSO-WelcomeAboard", + suffix="ogg", + loud=false, + subtitle="Welcome aboard", + duration=1.0, + subduration=5, + }, + EXPECTHEAVYWAVEOFF={ + file="LSO-ExpectHeavyWaveoff", + suffix="ogg", + loud=false, + subtitle="Expect heavy waveoff", + duration=1.2, + subduration=5, + }, + EXPECTSPOT75={ + file="LSO-ExpectSpot75", + suffix="ogg", + loud=false, + subtitle="Expect spot 7.5", + duration=2.0, + subduration=5, + }, + CLEAREDTOLAND={ + file="LSO-ClearedToLand", + suffix="ogg", + loud=false, + subtitle="Cleared to land", + duration=1.0, + subduration=5, + }, + CHECK={ + file="LSO-Check", + suffix="ogg", + loud=false, + subtitle="Check", + duration=0.45, + subduration=2.5, + }, + STABILIZED={ + file="LSO-Stabilized", + suffix="ogg", + loud=false, + subtitle="Stabilized", + duration=0.9, + subduration=5, + }, + IDLE={ + file="LSO-Idle", + suffix="ogg", + loud=false, + subtitle="Idle", + duration=0.45, + subduration=5, + }, + N0={ + file="LSO-N0", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N1={ + file="LSO-N1", + suffix="ogg", + loud=false, + subtitle="", + duration=0.25, + }, + N2={ + file="LSO-N2", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N3={ + file="LSO-N3", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N4={ + file="LSO-N4", + suffix="ogg", + loud=false, + subtitle="", + duration=0.39, + }, + N5={ + file="LSO-N5", + suffix="ogg", + loud=false, + subtitle="", + duration=0.39, + }, + N6={ + file="LSO-N6", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N7={ + file="LSO-N7", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N8={ + file="LSO-N8", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N9={ + file="LSO-N9", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + CLICK={ + file="AIRBOSS-RadioClick", + suffix="ogg", + loud=false, + subtitle="", + duration=0.35, + }, + NOISE={ + file="AIRBOSS-Noise", + suffix="ogg", + loud=false, + subtitle="", + duration=3.6, + }, + } + + self.MarshalCall={ + RADIOCHECK={ + file="MARSHAL-RadioCheck", + suffix="ogg", + loud=false, + subtitle="Radio check", + duration=1.1, + subduration=5, + }, + SAYNEEDLES={ + file="MARSHAL-SayNeedles", + suffix="ogg", + loud=false, + subtitle="Say needles", + duration=0.9, + subduration=5, + }, + FLYNEEDLES={ + file="MARSHAL-FlyYourNeedles", + suffix="ogg", + loud=false, + subtitle="Fly your needles", + duration=0.9, + subduration=5, + }, + -- TODO: Other voice overs for marshal. + N0={ + file="LSO-N0", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N1={ + file="LSO-N1", + suffix="ogg", + loud=false, + subtitle="", + duration=0.25, + }, + N2={ + file="LSO-N2", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N3={ + file="LSO-N3", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N4={ + file="LSO-N4", + suffix="ogg", + loud=false, + subtitle="", + duration=0.39, + }, + N5={ + file="LSO-N5", + suffix="ogg", + loud=false, + subtitle="", + duration=0.39, + }, + N6={ + file="LSO-N6", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N7={ + file="LSO-N7", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, + }, + N8={ + file="LSO-N8", + suffix="ogg", + loud=false, + subtitle="", + duration=0.37, + }, + N9={ + file="LSO-N9", + suffix="ogg", + loud=false, + subtitle="", + duration=0.40, --0.38 too short + }, + CLICK={ + file="AIRBOSS-RadioClick", + suffix="ogg", + loud=false, + subtitle="", + duration=0.35, + }, + NOISE={ + file="AIRBOSS-Noise", + suffix="ogg", + loud=false, + subtitle="", + duration=3.6, + }, + } + +end + +--- Init voice over radio transmission call. +-- @param #AIRBOSS self +-- @param #AIRBOSS.RadioCall radiocall LSO or Marshal radio call object. +-- @param #number duration Duration of the voice over in seconds. +-- @param #string subtitle (Optional) Subtitle to be displayed along with voice over. +-- @param #number subduration (Optional) Duration how long the subtitle is displayed. +-- @param #string filename (Optional) Name of the voice over sound file. +-- @param #string suffix (Optional) Extention of file. Default ".ogg". +function AIRBOSS:SetVoiceOver(radiocall, duration, subtitle, subduration, filename, suffix) + radiocall.duration=duration + radiocall.subtitle=subtitle or radiocall.subtitle + radiocall.file=filename + radiocall.suffix=".ogg" +end + --- Get optimal aircraft AoA parameters.. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -3961,14 +3986,6 @@ function AIRBOSS:_AoAUnit2Deg(playerData, aoaunits) -- A-4E-C source code suggests a imple factor of 1/2 for conversion. degrees=0.5*aoaunits - --[[ - -- Assuming same conversion as for the Tomcat. Indexer also goes from 0-30 units. Maybe it is right, maybe not. - local a=1.9/5.5 - local a=1/3 - local b=8.0-17.5*a - degrees=a*aoaunits+b - ]] - end return degrees @@ -4006,17 +4023,6 @@ function AIRBOSS:_AoADeg2Units(playerData, degrees) -- A-4E source code suggests a simple factor of two as conversion. aoaunits=2*degrees - - --[[ - -- Assuming same conversion as for the Tomcat. Indexer also goes from 0-30 units. Maybe it is right, maybe not. - aoaunits=30/50*degrees+10 - --aoaunits=2*degrees - aoaunits=(degrees-1.406)*5.5/1.9 - - local a=1.9/5.5 - local a=1/3 - local b=8.0-17.5*a - ]] end @@ -5639,6 +5645,9 @@ function AIRBOSS:_NewPlayer(unitname) -- Number of passes done by player in this slot. playerData.passes=0 --playerData.passes or 0 + -- Messages for player. + playerData.messages={} + -- Debriefing tables. playerData.lastdebrief=playerData.lastdebrief or {} @@ -6223,10 +6232,11 @@ function AIRBOSS:_CheckPlayerStatus() -- Player unit. local unit=playerData.unit - -- Check if unit is alive and in air. - if unit:IsAlive() then + -- Check if unit is alive. + if unit and unit:IsAlive() then -- Check if player is in carrier controlled area (zone with R=50 NM around the carrier). + -- TODO: This might cause problems if the CCA is set to be very small! if unit:IsInZone(self.zoneCCA) then -- Display aircraft attitude and other parameters as message text. @@ -6658,7 +6668,7 @@ function AIRBOSS:OnEventLand(EventData) if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Power "Idle". - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.IDLE, false, 1) + self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1) -- Next step debrief. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.DEBRIEF) @@ -7322,27 +7332,9 @@ function AIRBOSS:_Platform(playerData) -- Check if we are in zone. if inzone then - -- Debug message. - MESSAGE:New("Platform step reached", 5, "DEBUG"):ToAllIf(self.Debug) - - -- Get optimal altitiude. - local altitude, aoa, distance, speed =self:_GetAircraftParameters(playerData) - - -- Get altitude hint. - local hintAlt=self:_AltitudeCheck(playerData, altitude) - - -- Get altitude hint. - local hintSpeed=self:_SpeedCheck(playerData, speed) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - - -- Altitude and speed hint. - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintSpeed) - - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end - + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) + -- Next step: depends. local nextstep if math.abs(self.holdingoffset)>0 and playerData.case>1 then @@ -7377,21 +7369,16 @@ function AIRBOSS:_ArcInTurn(playerData) local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case)) if inzone then + + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) - -- Debug message. - MESSAGE:New("Arc Turn In step reached", 5, "DEBUG"):ToAllIf(self.Debug) - - -- Get optimal altitiude. - local altitude, aoa, distance, speed=self:_GetAircraftParameters(playerData) - - -- Get speed hint. - local hintSpeed=self:_SpeedCheck(playerData, speed) - -- Message to player. if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - -- Hint speed. - local hint=string.format("%s\n%s", playerData.step, hintSpeed) + -- Hint + -- TODO Add as special case to playerhint function + local hint="" -- Hint turn and set TACAN. if playerData.difficulty==AIRBOSS.Difficulty.EASY then @@ -7425,23 +7412,10 @@ function AIRBOSS:_ArcOutTurn(playerData) -- Check if we are inside the moving zone. local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case)) - --if self:_CheckLimits(X, Z, self.DirtyUp) then if inzone then - - -- Debug message. - MESSAGE:New("Arc Turn Out step reached", 5, "DEBUG"):ToAllIf(self.Debug) - - -- Get optimal altitiude. - local altitude, aoa, distance, speed=self:_GetAircraftParameters(playerData) - - -- Get speed hint. - local hintSpeed=self:_SpeedCheck(playerData, speed) - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - local hint=string.format("%s\n%s", playerData.step, hintSpeed) - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) -- Next step: local nextstep @@ -7470,25 +7444,16 @@ function AIRBOSS:_DirtyUp(playerData) local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) if inzone then - - -- Debug message. - MESSAGE:New("Dirty up step reached", 5, "DEBUG"):ToAllIf(self.Debug) - - -- Get optimal altitiude. - local altitude, aoa, distance, speed=self:_GetAircraftParameters(playerData) - - -- Get altitude hint. - local hintAlt, debrief=self:_AltitudeCheck(playerData, altitude) - -- Get speed hint. - -- TODO: Not sure if we already need to be onspeed AoA at this point? - local hintSpeed=self:_SpeedCheck(playerData, speed) + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) -- Message to player. if playerData.difficulty~=AIRBOSS.Difficulty.HARD then -- Hint alt and speed. - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintSpeed) + -- TODO Add to playerhint function as special case. + local hint="" -- Dirty up. if playerData.difficulty==AIRBOSS.Difficulty.EASY then @@ -7504,8 +7469,8 @@ function AIRBOSS:_DirtyUp(playerData) -- Radio call "Say/Fly needles". Delayed by 10/15 seconds. if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then - local callsay=self:_NewRadioCall(AIRBOSS.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard) - local callfly=self:_NewRadioCall(AIRBOSS.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard) + local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard) + local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard) self:RadioTransmission(self.MarshalRadio, callsay, false, 40) self:RadioTransmission(self.MarshalRadio, callfly, false, 45) end @@ -7536,24 +7501,13 @@ function AIRBOSS:_Bullseye(playerData) -- Check if player is in zone and flying roughly in the right direction. if inzone and math.abs(relheading)<60 then - -- Debug message. - MESSAGE:New("Bullseye step reached", 5, "DEBUG"):ToAllIf(self.Debug) - - -- Get optimal altitiude. - local altitude, aoa, distance, speed=self:_GetAircraftParameters(playerData) - - -- Get altitude hint. - local hintAlt=self:_AltitudeCheck(playerData, altitude) - - -- Get altitude hint. - local hintAoA=self:_AoACheck(playerData, aoa) + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) -- Message to player. + -- TODO: Add to playerhint as special case. if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - - -- Hint alt and aoa. - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintAoA) - + local hint="" -- Hint follow the needles. if playerData.difficulty==AIRBOSS.Difficulty.EASY then if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then @@ -7566,7 +7520,7 @@ function AIRBOSS:_Bullseye(playerData) -- LSO expect spot 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.EXPECTSPOT75) + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75) end -- Next step: Groove Call the ball. @@ -7607,6 +7561,92 @@ function AIRBOSS:_BolterPattern(playerData) end end +--- Display hint to player. +-- @param #AIRBOSS self +-- @param #AIRBOSS.PlayerData playerData Player data table. +-- @param #number delay Delay before playing sound messages. Default 0 sec. +function AIRBOSS:_PlayerHint(playerData, delay) + + -- No hint for the pros. + if playerData.difficulty==AIRBOSS.Difficulty.HARD then + return + end + + -- Get optimal altitude, distance and speed. + local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) + + -- Get altitude hint. + local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData, alt) + + -- Get speed hint. + local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData, speed) + + -- Get AoA hint. + local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData, aoa) + + -- Get distance to the boat hint. + local hintDist,debriefDist,callDist=self:_DistCheck(playerData, dist) + + -- Message to player. + local hint="" + if hintAlt then + hint=hint..hintAlt.."\n" + end + if hintSpeed then + hint=hint..hintSpeed.."\n" + end + if hintAoA then + hint=hint..hintAoA.."\n" + end + if hintDist then + hint=hint..hintDist.."\n" + end + + -- Debriefing text. + local debrief="" + if debriefAlt then + debrief=debrief.."\n-"..debriefAlt + end + if debriefSpeed then + debrief=debrief.."\n-"..debriefSpeed + end + if debriefAoA then + debrief=debrief.."\n-"..debriefAoA + end + if debriefDist then + debrief=debrief.."\n-"..debriefDist + end + + -- Add step to debriefing. + if debrief~="" then + self:_AddToDebrief(playerData, debrief) + end + + delay=delay or 0 + if callAlt then + self:Sound2Player(playerData, self.LSORadio, callAlt, false, delay) + delay=delay+callAlt.duration+0.5 + end + if callSpeed then + self:Sound2Player(playerData, self.LSORadio, callSpeed, false, delay) + delay=delay+callSpeed.duration+0.5 + end + if callAoA then + self:Sound2Player(playerData, self.LSORadio, callAoA, false, delay) + delay=delay+callAoA.duration+0.5 + end + if callDist then + self:Sound2Player(playerData, self.LSORadio, callDist, false, delay) + delay=delay+callDist.duration+0.5 + end + + -- Message to player. + if hint~="" then + local text=string.format("%s%s", playerData.step, hint) + self:MessageToPlayer(playerData, hint, "AIRBOSS", "") + end +end + --- Break entry for case I/II recoveries. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -7624,21 +7664,9 @@ function AIRBOSS:_BreakEntry(playerData) -- Check if we are in front of the boat (diffX > 0). if self:_CheckLimits(X, Z, self.BreakEntry) then - -- Get optimal altitude, distance and speed. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) - -- Get altitude hint. - local hintAlt=self:_AltitudeCheck(playerData, alt) - - -- Get speed hint. - local hintSpeed=self:_SpeedCheck(playerData, speed) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintSpeed) - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end - -- Next step: Early Break. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.EARLYBREAK) @@ -7670,32 +7698,8 @@ function AIRBOSS:_Break(playerData, part) -- Check limits. if self:_CheckLimits(X, Z, breakpoint) then - -- Get optimal altitude, distance and speed. - local altitude=self:_GetAircraftParameters(playerData) - - -- Grade altitude. - local hint, debrief=self:_AltitudeCheck(playerData, altitude) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - - -- Hint alt. - local hint=string.format("%s %s", playerData.step, hint) - - --[[ - -- Hint dirty up. - if playerData.difficult==AIRBOSS.Difficulty.EASY and part==AIRBOSS.PatternStep.LATEBREAK then - if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nDirty up! Gear down, flaps down. Check hook down." - end - end - ]] - - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end - - -- Debrief - self:_AddToDebrief(playerData, debrief) + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) -- Next step: Late Break or Abeam. local nextstep @@ -7729,8 +7733,8 @@ function AIRBOSS:_CheckForLongDownwind(playerData) if X90 and self:_CheckLimits(X, Z, self.Wake) then -- Message to player. self:MessageToPlayer(playerData, "You are already at the wake and have not passed the 90. Turn faster next time!", "LSO") - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.DEPARTANDREENTER) + self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER) playerData.patternwo=true -- Debrief. self:_AddToDebrief(playerData, "Overshoot at wake - Pattern Waveoff!") @@ -7877,28 +7842,10 @@ function AIRBOSS:_Wake(playerData) -- Right behind the wake of the carrier dZ>0. if self:_CheckLimits(X, Z, self.Wake) then - - -- Get optimal altitude, distance and speed. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) - -- Grade altitude. - local hintAlt, debriefAlt=self:_AltitudeCheck(playerData, alt) - - -- Grade AoA. - local hintAoA, debriefAoA=self:_AoACheck(playerData, aoa) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintAoA) - self:MessageToPlayer(playerData, hint, "LSO", "") - end - - -- Debrief. - local debrief=string.format("%s\n%s", debriefAlt, debriefAoA) - - -- Add to debrief. - self:_AddToDebrief(playerData, debrief) - + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) + -- Next step: Final. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.FINAL) @@ -7931,25 +7878,9 @@ function AIRBOSS:_Final(playerData) -- Check if player is in +-4 deg cone and flying towards the runway. if math.abs(lineup)<=4 then - -- Get optimal altitude, distance and speed. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) + -- Hint for player about altitude, AoA etc. + self:_PlayerHint(playerData) - -- Grade altitude. - local hintAlt, debriefAlt=self:_AltitudeCheck(playerData, alt) - - -- AoA feed back - local hintAoA, debriefAoA=self:_AoACheck(playerData, aoa) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - local hint=string.format("%s\n%s\n%s", playerData.step, hintAlt, hintAoA) - self:MessageToPlayer(playerData, hint, "LSO", "") - end - - -- Add to debrief. - local debrief=string.format("%s\n%s", debriefAlt, debriefAoA) - self:_AddToDebrief(playerData, debrief) - -- Gather pilot data. local groovedata={} --#AIRBOSS.GrooveData groovedata.Step=playerData.step @@ -8036,13 +7967,13 @@ function AIRBOSS:_Groove(playerData) if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX then -- LSO "Call the ball" call. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.CALLTHEBALL) + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL) playerData.Tlso=timer.getTime() -- Pilot "405, Hornet Ball, 3.2". Output should come from pilot. -- LSO "Roger ball" call in three seconds. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.ROGERBALL, false, 3) + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 3) -- Store data. playerData.groove.XX=groovedata @@ -8120,7 +8051,7 @@ function AIRBOSS:_Groove(playerData) if playerData.unit:IsInZone(ZoneALS) and stable then -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.CLEAREDTOLAND) + self:RadioTransmission(self.LSORadio, self.LSOCall.CLEAREDTOLAND) -- Next step: Level cross. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_LC) @@ -8148,7 +8079,7 @@ function AIRBOSS:_Groove(playerData) -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. if playerData.unit:IsInZone(ZoneLS) and stable and playerData.warning==false then - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.STABILIZED) + self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED) playerData.warning=true end @@ -8173,7 +8104,7 @@ function AIRBOSS:_Groove(playerData) self:T3(self.lid..string.format("Waveoff distance rho=%.1f m", rho)) -- LSO Wave off! - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.WAVEOFF) + self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF) playerData.Tlso=timer.getTime() -- Player was waved off! @@ -8458,8 +8389,8 @@ function AIRBOSS:_CheckFoulDeck(playerData) self:_AddToDebrief(playerData, text) -- Foul deck + wave off radio message. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.FOULDECK, false, 1) - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.WAVEOFF, false, 1.2) + self:RadioTransmission(self.LSORadio, self.LSOCall.FOULDECK, false, 1) + self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2) -- Player hint for flight students. if playerData.difficulty~=AIRBOSS.Difficulty.HARD then @@ -8638,7 +8569,7 @@ function AIRBOSS:_Trapped(playerData) -- Check if we passed all wires. if wire>4 and v>10 and not playerData.warning then -- Looks like we missed the wires ==> Bolter! - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.BOLTER) + self:RadioTransmission(self.LSORadio, self.LSOCall.BOLTER) playerData.warning=true end @@ -9834,20 +9765,20 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) --TODO: introduce GSE enumerator values. if glideslopeError>1.5 then -- "You're high!" - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.HIGH, true) - advice=advice+AIRBOSS.LSOCall.HIGH.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true) + advice=advice+self.LSOCall.HIGH.duration elseif glideslopeError>0.8 then -- "You're high." - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.HIGH, false) - advice=advice+AIRBOSS.LSOCall.HIGH.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, false) + advice=advice+self.LSOCall.HIGH.duration elseif glideslopeError<-0.9 then -- "Power!" - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.POWER, true) - advice=advice+AIRBOSS.LSOCall.POWER.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.POWER, true) + advice=advice+self.LSOCall.POWER.duration elseif glideslopeError<-0.6 then -- "Power." - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.POWER, false) - advice=advice+AIRBOSS.LSOCall.POWER.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.POWER, false) + advice=advice+self.LSOCall.POWER.duration else -- "Good altitude." end @@ -9857,20 +9788,20 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) -- TODO: introduce LUE enumerator values. if lineupError<-3 then -- "Come left!" - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.COMELEFT, true) - advice=advice+AIRBOSS.LSOCall.COMELEFT.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.COMELEFT, true) + advice=advice+self.LSOCall.COMELEFT.duration elseif lineupError<-1 then -- "Come left." - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.COMELEFT, false) - advice=advice+AIRBOSS.LSOCall.COMELEFT.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.COMELEFT, false) + advice=advice+self.LSOCall.COMELEFT.duration elseif lineupError>3 then -- "Right for lineup!" - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.RIGHTFORLINEUP, true) - advice=advice+AIRBOSS.LSOCall.RIGHTFORLINEUP.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true) + advice=advice+self.LSOCall.RIGHTFORLINEUP.duration elseif lineupError>1 then -- "Right for lineup." - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.RIGHTFORLINEUP, false) - advice=advice+AIRBOSS.LSOCall.RIGHTFORLINEUP.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false) + advice=advice+self.LSOCall.RIGHTFORLINEUP.duration else -- "Good lineup." end @@ -9885,26 +9816,26 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then if AOA>acaoa.SLOW then -- "Your're slow!" - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.SLOW, true) - advice=advice+AIRBOSS.LSOCall.SLOW.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, true) + advice=advice+self.LSOCall.SLOW.duration --S=underline("SLO") elseif AOA>acaoa.Slow then -- "Your're slow." - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.SLOW, false) - advice=advice+AIRBOSS.LSOCall.SLOW.duration + self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, false) + advice=advice+self.LSOCall.SLOW.duration --S="SLO" elseif AOA>acaoa.OnSpeedMax then -- No call. --S=little("SLO") elseif AOAbadscore then --hint=string.format("You're high.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.HIGH, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.HIGH, nil, nil, 5) loud=true elseif _error>lowscore then --hint= string.format("You're slightly high.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.HIGH, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.HIGH, nil, nil, 5) elseif _error<-badscore then --hint=string.format("You're low. ") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.LOW, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.LOW, nil, nil, 5) loud=true elseif _error<-lowscore then --hint=string.format("You're slightly low.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.LOW, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.LOW, nil, nil, 5) else hint=string.format("Good altitude.") end @@ -10464,11 +10396,9 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt) if playerData.difficulty==AIRBOSS.Difficulty.EASY then -- Also inform students about the optimal altitude. hint=hint..string.format("Optimal altitude is %d ft.", UTILS.MetersToFeet(altopt)) - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -- We keep it short normally. hint="" - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. hint="" @@ -10477,7 +10407,7 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt) -- Debrief text. local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.", UTILS.MetersToFeet(altitude), _error, UTILS.MetersToFeet(altopt)) - return hint, debrief + return hint, debrief,radiocall end --- Evaluate player's distance to the boat at checkpoint. @@ -10486,6 +10416,7 @@ end -- @param #number optdist Optimal distance in meters. -- @return #string Feedback message text. -- @return #string Debriefing text. +-- @#AIRBOSS.RadioCall Distance radio call. Not implemented yet. function AIRBOSS:_DistanceCheck(playerData, optdist) if optdist==nil then @@ -10528,7 +10459,7 @@ function AIRBOSS:_DistanceCheck(playerData, optdist) -- Debriefing text. local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance), _error, UTILS.MetersToNM(optdist)) - return hint, debrief + return hint, debrief, nil end --- Score for correct AoA. @@ -10537,6 +10468,7 @@ end -- @param #number optaoa Optimal AoA. -- @return #string Feedback message text or easy and normal difficulty level or nil for hard. -- @return #string Debriefing text. +-- @return #AIRBOSS.RadioCall Radio call. function AIRBOSS:_AoACheck(playerData, optaoa) if optaoa==nil then @@ -10563,11 +10495,11 @@ function AIRBOSS:_AoACheck(playerData, optaoa) local loud=false if aoa>=aircraftaoa.SLOW then --hint="Your're slow!" - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.SLOW, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, nil, nil, 5) loud=true elseif aoa>=aircraftaoa.Slow then --hint="Your're slow." - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.SLOW, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, nil, nil, 5) elseif aoa>=aircraftaoa.OnSpeedMax then hint="Your're a little slow." elseif aoa>=aircraftaoa.OnSpeedMin then @@ -10576,10 +10508,10 @@ function AIRBOSS:_AoACheck(playerData, optaoa) hint="You're a little fast." elseif aoa>=aircraftaoa.FAST then --hint="Your're fast." - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.FAST, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.FAST, nil, nil, 5) else --hint="You're fast!" - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.FAST, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.FAST, nil, nil, 5) loud=true end @@ -10599,7 +10531,7 @@ function AIRBOSS:_AoACheck(playerData, optaoa) -- Debriefing text. local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.", self:_AoADeg2Units(playerData, aoa), _error, self:_AoADeg2Units(playerData, optaoa)) - return hint, debrief + return hint, debrief,radiocall end --- Evaluate player's speed. @@ -10608,6 +10540,7 @@ end -- @param #number speedopt Optimal speed in m/s. -- @return #string Feedback text. -- @return #string Debriefing text. +-- @return #AIRBOSS.RadioCall Radio call. function AIRBOSS:_SpeedCheck(playerData, speedopt) if speedopt==nil then @@ -10630,17 +10563,17 @@ function AIRBOSS:_SpeedCheck(playerData, speedopt) local loud=false if _error>badscore then --hint=string.format("You're fast.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.FAST, "AIRBOSS", nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", nil, 5) loud=true elseif _error>lowscore then --hint= string.format("You're slightly fast.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.FAST, "AIRBOSS", nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", nil, 5) elseif _error<-badscore then --hint=string.format("You're slow.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.SLOW, "AIRBOSS", nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", nil, 5) elseif _error<-lowscore then --hint=string.format("You're slightly slow.") - radiocall=self:_NewRadioCall(AIRBOSS.LSOCall.SLOW, "AIRBOSS", nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", nil, 5) loud=true else hint=string.format("Good speed.") @@ -10661,7 +10594,7 @@ function AIRBOSS:_SpeedCheck(playerData, speedopt) -- Debrief text. local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.", UTILS.MpsToKnots(speed), _error, UTILS.MpsToKnots(speedopt)) - return hint, debrief + return hint, debrief, radiocall end --- Append text to debriefing. @@ -10846,7 +10779,7 @@ function AIRBOSS:_Debrief(playerData) else -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, AIRBOSS.LSOCall.WELCOMEABOARD) + self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) -- Airboss talkto! local text=string.format("the deck was fouled but landed anyway. Airboss wants to talk to you!") @@ -10869,7 +10802,7 @@ function AIRBOSS:_Debrief(playerData) else -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, AIRBOSS.LSOCall.WELCOMEABOARD) + self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) -- Airboss talkto! local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!") @@ -10899,7 +10832,7 @@ function AIRBOSS:_Debrief(playerData) if not playerData.unit:InAir() then -- Welcome aboard! - self:Sound2Player(playerData, self.LSORadio, AIRBOSS.LSOCall.WELCOMEABOARD) + self:Sound2Player(playerData, self.LSORadio, self.LSOCall.WELCOMEABOARD) end @@ -12108,7 +12041,7 @@ end -- @return #boolean If true, call needs a subtitle. function AIRBOSS:_NeedsSubtitle(call) -- Currently we play the noise file. - if call.file==AIRBOSS.MarshalCall.NOISE.file or call.file==AIRBOSS.LSOCall.NOISE.file then + if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then return true else return false @@ -12344,8 +12277,7 @@ end -- @param #number duration Display message duration. Default 10 seconds. -- @param #boolean clear If true, clear screen from previous messages. -- @param #number delay Delay in seconds, before the message is displayed. --- @param #boolean soundoff If true, do not play boad number message. -function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay, soundoff) +function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay) if playerData and message and message~="" then @@ -12366,10 +12298,10 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear, 0, soundoff}, delay) + SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) else - if receiver==playerData.onboard and not soundoff then + if receiver==playerData.onboard then -- Sound only to player group. if sender and (sender=="LSO" or sender=="MARSHAL" or sender=="AIRBOSS") then @@ -12378,7 +12310,7 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration local wait=self:_Number2Sound(playerData, sender, receiver) -- Play click sound to end message. - local filename=self:_RadioFilename(AIRBOSS.MarshalCall.CLICK) + local filename=self:_RadioFilename(self.MarshalCall.CLICK) USERSOUND:New(filename):ToGroup(playerData.group, wait) end @@ -12387,7 +12319,7 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration -- Text message to player client. if playerData.client then MESSAGE:New(text, duration, sender, clear):ToClient(playerData.client) - end + end end @@ -12407,7 +12339,7 @@ end function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, delay) -- Create new (fake) radio call to show the subtitile. - local call=self:_NewRadioCall(AIRBOSS.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender) + local call=self:_NewRadioCall(self.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender) -- Dummy radio transmission to display subtitle only to those who tuned in. self:RadioTransmission(self.LSORadio, call, false, delay) @@ -12426,7 +12358,7 @@ end function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, delay) -- Create new (fake) radio call to show the subtitile. - local call=self:_NewRadioCall(AIRBOSS.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender) + local call=self:_NewRadioCall(self.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender) -- Dummy radio transmission to display subtitle only to those who tuned in. self:RadioTransmission(self.MarshalRadio, call, false, delay) @@ -12934,19 +12866,7 @@ function AIRBOSS:_RequestSpinning(_unitName) end if playerData.difficulty==AIRBOSS.Difficulty.EASY then text=text.." Climb to 1200 feet and proceed to the initial." - end - - -- Set step for section members. - --[[ - for _,sec in pairs(playerData.section) do - local sectionmember=sec --#AIRBOSS.PlayerData - self:_SetPlayerStep(sectionmember, AIRBOSS.PatternStep.SPINNING) - if sectionmember.difficulty~=AIRBOSS.Difficulty.HARD then - text="Spin it!" - self:MessageToPlayer(playerData, text, "MARSHAL") - end end - ]] end @@ -14196,7 +14116,7 @@ function AIRBOSS:_LSORadioCheck(_unitName) local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then -- Broadcase LSO radio check message on LSO radio. - self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.RADIOCHECK) + self:RadioTransmission(self.LSORadio, self.LSOCall.RADIOCHECK) end end end @@ -14215,7 +14135,7 @@ function AIRBOSS:_MarshalRadioCheck(_unitName) local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then -- Broadcase Marshal radio check message on Marshal radio. - self:RadioTransmission(self.MarshalRadio, AIRBOSS.MarshalCall.RADIOCHECK) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RADIOCHECK) end end end From aba0c19215b69f03d5580ff8dc575f39738bf2e6 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 13 Feb 2019 00:15:31 +0100 Subject: [PATCH 02/11] AB v0.9.8wip --- Moose Development/Moose/Ops/Airboss.lua | 143 +++++++++++------------- 1 file changed, 66 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index f7446f23d..a94006562 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -331,7 +331,7 @@ -- -- ### Request Refueling -- --- If a recovery taker has been set up via the @{#AIRBOSS.SetRecoveryTanker}, the player can request refueling at any time. If currently in the marshal stack, the stack above will collapse. +-- If a recovery tanker has been set up via the @{#AIRBOSS.SetRecoveryTanker}, the player can request refueling at any time. If currently in the marshal stack, the stack above will collapse. -- The player will be informed if the tanker is currently busy or going RTB to refuel itself at its home base. Once the re-fueling is complete, the player has to re-register to the marshal stack. -- -- ### Spinning @@ -1587,6 +1587,9 @@ function AIRBOSS:New(carriername, alias) return nil end + -- Init voice over files. + self:_InitVoiceOvers() + ------------------- -- Debug Section -- ------------------- @@ -7372,28 +7375,6 @@ function AIRBOSS:_ArcInTurn(playerData) -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - - -- Hint - -- TODO Add as special case to playerhint function - local hint="" - - -- Hint turn and set TACAN. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - -- Get inverse magnetic radial without offset ==> FB for Case II or BRC for Case III. - local radial=self:GetRadial(playerData.case, true, false, true) - local turn="right" - if self.holdingoffset<0 then - turn="left" - end - hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) - end - - -- Message to player. - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end -- Next step: Arc Out Turn. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.ARCOUT) @@ -7447,25 +7428,6 @@ function AIRBOSS:_DirtyUp(playerData) -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) - - -- Message to player. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - - -- Hint alt and speed. - -- TODO Add to playerhint function as special case. - local hint="" - - -- Dirty up. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nFAF! Checks completed. Nozzles 50°." - else - hint=hint.."\nDirty up! Hook, gear and flaps down." - end - end - - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end -- Radio call "Say/Fly needles". Delayed by 10/15 seconds. if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then @@ -7504,20 +7466,6 @@ function AIRBOSS:_Bullseye(playerData) -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) - -- Message to player. - -- TODO: Add to playerhint as special case. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then - local hint="" - -- Hint follow the needles. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then - hint=hint..string.format("Intercept glideslope and follow the needles.") - end - end - - self:MessageToPlayer(playerData, hint, "MARSHAL", "") - end - -- LSO expect spot 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75) @@ -7585,21 +7533,21 @@ function AIRBOSS:_PlayerHint(playerData, delay) local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData, aoa) -- Get distance to the boat hint. - local hintDist,debriefDist,callDist=self:_DistCheck(playerData, dist) + local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData, dist) -- Message to player. local hint="" if hintAlt then - hint=hint..hintAlt.."\n" + hint=hint.."\n"..hintAlt end if hintSpeed then - hint=hint..hintSpeed.."\n" + hint=hint.."\n"..hintSpeed end if hintAoA then - hint=hint..hintAoA.."\n" + hint=hint.."\n"..hintAoA end if hintDist then - hint=hint..hintDist.."\n" + hint=hint.."\n"..hintDist end -- Debriefing text. @@ -7640,6 +7588,43 @@ function AIRBOSS:_PlayerHint(playerData, delay) delay=delay+callDist.duration+0.5 end + -- ARC IN info. + if playerData.step==AIRBOSS.PatternStep.ARCIN then + + -- Hint turn and set TACAN. + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + -- Get inverse magnetic radial without offset ==> FB for Case II or BRC for Case III. + local radial=self:GetRadial(playerData.case, true, false, true) + local turn="right" + if self.holdingoffset<0 then + turn="left" + end + hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) + end + + end + + -- DIRTUP additonal info. + if playerData.step==AIRBOSS.PatternStep.DIRTYUP then + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + hint=hint.."\nFAF! Checks completed. Nozzles 50°." + else + hint=hint.."\nDirty up! Hook, gear and flaps down." + end + end + end + + -- BULLSEYE additonal info. + if playerData.step==AIRBOSS.PatternStep.BULLSEYE then + -- Hint follow the needles. + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then + hint=hint..string.format("\nIntercept glideslope and follow the needles.") + end + end + end + -- Message to player. if hint~="" then local text=string.format("%s%s", playerData.step, hint) @@ -7776,7 +7761,7 @@ function AIRBOSS:_Abeam(playerData) end -- Hint for player about altitude, AoA etc. - self:_PlayerHint(playerData) + self:_PlayerHint(playerData, 3) -- Next step: ninety. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.NINETY) @@ -8398,10 +8383,14 @@ function AIRBOSS:_CheckFoulDeck(playerData) self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3) end - -- Set player parameters for foul deck + -- Set player parameters for foul deck. playerData.fouldeckwo=true + + -- Debrief. playerData.step=AIRBOSS.PatternStep.DEBRIEF playerData.warning=nil + + -- Pass would be invalid if the player lands. playerData.valid=false -- Send a message to the player that blocks the runway. @@ -12019,18 +12008,18 @@ function AIRBOSS:RadioTransmission(radio, call, loud, delay) end -- Append radio click sound at the end of the transmission. - if call~=AIRBOSS[caller].CLICK and - call~=AIRBOSS[caller].N0 and - call~=AIRBOSS[caller].N1 and - call~=AIRBOSS[caller].N2 and - call~=AIRBOSS[caller].N3 and - call~=AIRBOSS[caller].N4 and - call~=AIRBOSS[caller].N5 and - call~=AIRBOSS[caller].N6 and - call~=AIRBOSS[caller].N7 and - call~=AIRBOSS[caller].N8 and - call~=AIRBOSS[caller].N9 then - self:RadioTransmission(radio, AIRBOSS[caller].CLICK, false, delay) + if call~=self[caller].CLICK and + call~=self[caller].N0 and + call~=self[caller].N1 and + call~=self[caller].N2 and + call~=self[caller].N3 and + call~=self[caller].N4 and + call~=self[caller].N5 and + call~=self[caller].N6 and + call~=self[caller].N7 and + call~=self[caller].N8 and + call~=self[caller].N9 then + self:RadioTransmission(radio, self[caller].CLICK, false, delay) end end @@ -12497,7 +12486,7 @@ function AIRBOSS:_Number2Sound(playerData, sender, number, delay) local N=string.format("N%s", n) -- Radio call. - local call=AIRBOSS[Sender][N] --#AIRBOSS.RadioCall + local call=self[Sender][N] --#AIRBOSS.RadioCall -- Create file name. --local filename=string.format("%s.%s", call.file, call.suffix) @@ -12555,7 +12544,7 @@ function AIRBOSS:_Number2Radio(radio, number, delay) local N=string.format("N%s", n) -- Radio call. - local call=AIRBOSS[Sender][N] --#AIRBOSS.RadioCall + local call=self[Sender][N] --#AIRBOSS.RadioCall -- Transmit. self:RadioTransmission(radio, call, false, delay) From b5b6d398aef741cbe8f023e55e7c86dd6ab7666a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 13 Feb 2019 16:46:15 +0100 Subject: [PATCH 03/11] AB 0.9.8w --- Moose Development/Moose/Functional/RAT.lua | 21 +++- Moose Development/Moose/Ops/Airboss.lua | 106 +++++++++++++++++---- 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index dc5563546..5a3d9919a 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -150,6 +150,7 @@ -- @field #number parkingscanradius Radius in meters until which parking spots are scanned for obstacles like other units, statics or scenery. -- @field #boolean parkingscanscenery If true, area around parking spots is scanned for scenery objects. Default is false. -- @field #boolean parkingverysafe If true, parking spots are considered as non-free until a possible aircraft has left and taken off. Default false. +-- @field #boolean despawnair If true, aircraft are despawned when they reach their destination zone. Default. -- @extends Core.Spawn#SPAWN --- Implements an easy to use way to randomly fill your map with AI aircraft. @@ -428,6 +429,7 @@ RAT={ parkingscanradius=40, -- Scan radius. parkingscanscenery=false, -- Scan parking spots for scenery obstacles. parkingverysafe=false, -- Very safe option. + despawnair=true, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -546,7 +548,7 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.3.5", + version = "2.3.6", print = true, } @@ -1098,6 +1100,14 @@ function RAT:SetParkingSpotSafeOFF() return self end +--- Aircraft that reach their destination zone are not despawned. They will probably go the the nearest airbase and try to land. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetDespawnAirOFF() + self.despawnair=false + return self +end + --- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air. -- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines. -- @param #RAT self @@ -3604,13 +3614,18 @@ function RAT:Status(message, forID) local text=string.format("Flight %s will be despawned NOW!", self.alias) self:T(RAT.id..text) - -- Despawn old group. + + -- Respawn group if (not self.norespawn) and (not self.respawn_after_takeoff) then local idx=self:GetSpawnIndexFromGroup(group) local coord=group:GetCoordinate() self:_Respawn(idx, coord, 0) end - self:_Despawn(group, 0) + + -- Despawn old group. + if self.despawnair then + self:_Despawn(group, 0) + end end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index a94006562..fde40b486 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1206,7 +1206,7 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall NOISE Static noise sound. --- Marshal radio calls. --- @type AIRBOSS.MarshalCall +-- @type AIRBOSS.MarshalCalls -- @field #AIRBOSS.RadioCall RADIOCHECK "Radio check" call. -- @field #AIRBOSS.RadioCall SAYNEEDLES "Say needles" call. -- @field #AIRBOSS.RadioCall FLYNEEDLES "Fly your needles" call. @@ -1222,6 +1222,15 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N9 "Nine" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. +-- @field #AIRBOSS.RadioCall CASE "Case" call. +-- @field #AIRBOSS.RadioCall EXPECTED "Expected" call. +-- @field #AIRBOSS.RadioCall BRC "BRC" call. +-- @field #AIRBOSS.RadioCall CHARLIETIME "Charlie Time" call. +-- @field #AIRBOSS.RadioCall REPORTSEEME "Report see me" call. +-- @field #AIRBOSS.RadioCall HOLDAT "Hold at" call. +-- @field #AIRBOSS.RadioCall ANGELS "Angels" call. +-- @field #AIRBOSS.RadioCall ALTIMETER "Altimeter" call. +-- @field #AIRBOSS.RadioCall POINT "Point" call. --- Message for player. -- @type AIRBOSS.PlayerMessage @@ -3488,8 +3497,8 @@ function AIRBOSS:_InitTarawa() self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. self.BreakLate.LimitXmax= nil self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 - self.BreakLate.LimitZmax= nil - + self.BreakLate.LimitZmax= nil + end @@ -3793,72 +3802,71 @@ function AIRBOSS:_InitVoiceOvers() duration=0.9, subduration=5, }, - -- TODO: Other voice overs for marshal. N0={ - file="LSO-N0", + file="MARSHAL-N0", suffix="ogg", loud=false, subtitle="", duration=0.40, }, N1={ - file="LSO-N1", + file="MARSHAL-N1", suffix="ogg", loud=false, subtitle="", duration=0.25, }, N2={ - file="LSO-N2", + file="MARSHAL-N2", suffix="ogg", loud=false, subtitle="", duration=0.37, }, N3={ - file="LSO-N3", + file="MARSHAL-N3", suffix="ogg", loud=false, subtitle="", duration=0.37, }, N4={ - file="LSO-N4", + file="MARSHAL-N4", suffix="ogg", loud=false, subtitle="", duration=0.39, }, N5={ - file="LSO-N5", + file="MARSHAL-N5", suffix="ogg", loud=false, subtitle="", duration=0.39, }, N6={ - file="LSO-N6", + file="MARSHAL-N6", suffix="ogg", loud=false, subtitle="", duration=0.40, }, N7={ - file="LSO-N7", + file="MARSHAL-N7", suffix="ogg", loud=false, subtitle="", duration=0.40, }, N8={ - file="LSO-N8", + file="MARSHAL-N8", suffix="ogg", loud=false, subtitle="", duration=0.37, }, N9={ - file="LSO-N9", + file="MARSHAL-N9", suffix="ogg", loud=false, subtitle="", @@ -5146,11 +5154,14 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) -- Marshal message. - local text=string.format("Case %d, expected BRC %03d°, hold at %d. Expected Charlie Time %s.\n", flight.case, brc, alt, tostring(Ccharlie)) - text=text..string.format("Altimeter %.2f. Report see me.", P) + --local text=string.format("Case %d, expected BRC %03d°, hold at %d. Expected Charlie Time %s.\n", flight.case, brc, alt, tostring(Ccharlie)) + --text=text..string.format("Altimeter %.2f. Report see me.", P) -- Message to all players. - self:MessageToMarshal(text, "MARSHAL", flight.onboard) + --self:MessageToMarshal(text, "MARSHAL", flight.onboard) + + -- Combined marshal call. + self:_MarshalCallArrived(flight.onboard, flight.case, brc, alt, Ccharlie, P) -- Hint about TACAN bearing. if self.TACANon and (not flight.ai) and flight.difficulty==AIRBOSS.Difficulty.EASY then @@ -5895,7 +5906,7 @@ function AIRBOSS:_CheckSectionRecovered(flight) -- Check all elements of the lead flight group. for _,_element in pairs(lead.elements) do - local element=_element --#AIROBSS.FlightElement + local element=_element --#AIRBOSS.FlightElement if not element.recovered then return false end @@ -12557,6 +12568,65 @@ function AIRBOSS:_Number2Radio(radio, number, delay) return wait end +--- Compile a radio call when Marshal tells a flight the holding alitude. +-- @param #AIRBOSS self +-- @param #string modex Tail number. +-- @param #number case Recovery case. +-- @param #number brc Base recovery course. +-- @param #number altitude Holding alitude. +-- @param #string charlie Charlie Time estimate. +-- @param #string qfe Alitmeter inHg. +function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) + + -- Split strings etc. + local angels=self:_GetAngels(altitude) + local QFE=UTILS.Split(tostring(qfe), ".") + local CT=UTILS.Split(select(1, UTILS.Split(alitmeter, "+")), ":") + + -- Subtitle text. + local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s.\n", case, brc, angels, charlie) + text=text..string.format("Altimeter %.2f. Report see me.", qfe) + + -- Create new call to display complete subtitle. + local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) + + -- Case.. + self:RadioTransmission(self.MarshalRadio, casecall) + -- X.. + self:_Number2Radio(self.MarshalRadio, tostring(case)) + -- expected.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED) + -- BRC.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC) + -- XYZ.. + self:_Number2Radio(self.MarshalRadio, string.format("%d03", brc)) + -- hold at.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT) + -- angels.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ANGELS) + -- X.. + self:_Number2Radio(self.MarshalRadio, tostring(angels)) + -- Expected.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED) + -- Charlie time.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME) + -- XY.. (hours) + self:_Number2Radio(self.MarshalRadio, CT[1]) + -- XY.. (minutes) + self:_Number2Radio(self.MarshalRadio, CT[2]) + -- Altimeter.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER) + -- XY.. + self:_Number2Radio(self.MarshalRadio, QFE[1]) + -- Point.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT) + -- XY.. + self:_Number2Radio(self.MarshalRadio, QFE[2]) + -- Report see me. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME) + +end + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- RADIO MENU Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- From cff9557217e63aa5aa2115e43fc2c2da2698f404 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 14 Feb 2019 00:53:22 +0100 Subject: [PATCH 04/11] AB v0.9.8 wip --- Moose Development/Moose/Ops/Airboss.lua | 188 +++++++++++++++++++++--- 1 file changed, 164 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index fde40b486..20374dd40 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -203,6 +203,7 @@ -- @field #number dTbeacon Time interval to refresh the beacons. Default 5 minutes. -- @field #AIRBOSS.LSOCalls LSOCall Radio voice overs of the LSO. -- @field #AIRBOSS.MarshalCalls MarshalCall Radio voice over of the Marshal/Airboss. +-- @field #number lowfuelAI Low fuel threshold for AI groups in percent. -- @extends Core.Fsm#FSM --- Be the boss! @@ -1002,6 +1003,7 @@ AIRBOSS = { Tbeacon = nil, LSOCall = nil, MarshalCall = nil, + lowfuelAI = nil, } --- Aircraft types capable of landing on carrier (human+AI). @@ -2756,6 +2758,23 @@ end -- @param #AIRBOSS self function AIRBOSS:_CheckAIStatus() + -- Loop over all flights in Marshal stack. + for _,_flight in pairs(self.Qmarshal) do + local flight=_flight --#AIRBOSS.FlightGroup + + -- Only AI! + if flight.ai then + + -- TODO: Check that aircraft can be refueled. + local fuel=flight.group:GetFuelMin()*100 + + if self.lowfuelAI and fuelmath.abs(gd.LUE) then - self:T(self.lid..string.format("Got bigger Linue up error at %s: LUE %.3f>%.3f.", gs, lineupError, gd.LUE)) + self:T(self.lid..string.format("Got bigger Lineup error at %s: LUE %.3f>%.3f.", gs, lineupError, gd.LUE)) gd.LUE=lineupError end @@ -11508,6 +11607,36 @@ function AIRBOSS._ReachedHoldingZone(group, airboss, flight) end end +--- Function called when a group should be send to the Marshal stack. If stack is full, it is send to wait. +--@param Wrapper.Group#GROUP group Group that reached the holding zone. +--@param #AIRBOSS airboss Airboss object. +--@param #AIRBOSS.FlightGroup flight Flight group that has reached the holding zone. +function AIRBOSS._TaskFunctionMarshalAI(group, airboss, flight) + + -- Debug message. + local text=string.format("Flight %s is send to marshal.", group:GetName()) + MESSAGE:New(text,10):ToAllIf(airboss.Debug) + airboss:T(airboss.lid..text) + + -- Get the next free stack for current recovery case. + local stack=airboss:_GetFreeStack(flight.ai) + + if stack then + + -- Send AI to marshal stack. + airboss:_MarshalAI(flight, stack) + + else + + -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. + if not airboss:_InQueue(airboss.Qwaiting, flight.group) then + airboss:_WaitAI(flight) + end + + end + +end + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- MISC functions ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -11981,6 +12110,10 @@ end function AIRBOSS:RadioTransmission(radio, call, loud, delay) self:F2({radio=radio, call=call, loud=loud, delay=delay}) + if radio==nil or call==nil then + return + end + -- Create a new radio transmission item. local transmission={} --#AIRBOSS.Radioitem @@ -12577,18 +12710,24 @@ end -- @param #string charlie Charlie Time estimate. -- @param #string qfe Alitmeter inHg. function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) + self:E({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe}) -- Split strings etc. local angels=self:_GetAngels(altitude) - local QFE=UTILS.Split(tostring(qfe), ".") - local CT=UTILS.Split(select(1, UTILS.Split(alitmeter, "+")), ":") + local QFE=UTILS.Split(tostring(UTILS.Round(qfe,2)), ".") + local clock=UTILS.Split(charlie, "+") + self:E({clock=clock}) + local CT=UTILS.Split(clock[1], ":") -- Subtitle text. local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s.\n", case, brc, angels, charlie) text=text..string.format("Altimeter %.2f. Report see me.", qfe) -- Create new call to display complete subtitle. - local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) + --local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) + + -- Until we have a case sound we take the noise for testing + local casecall=self:_NewRadioCall(self.MarshalCall.NOISE, "MARSHAL", text, self.Tmessage, modex) -- Case.. self:RadioTransmission(self.MarshalRadio, casecall) @@ -12599,7 +12738,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) -- BRC.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC) -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%d03", brc)) + self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc)) -- hold at.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT) -- angels.. @@ -12624,7 +12763,8 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:_Number2Radio(self.MarshalRadio, QFE[2]) -- Report see me. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME) - + -- Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CLICK) end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 9df35840fe07b2b78bf3ada8fb9f5926d28d4910 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 14 Feb 2019 16:54:59 +0100 Subject: [PATCH 05/11] AB v0.9.8w --- Moose Development/Moose/Ops/Airboss.lua | 304 +++++++++++++++++++++--- 1 file changed, 265 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 20374dd40..b61af90df 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -189,6 +189,8 @@ -- @field #number windowcount Running number counting the recovery windows. -- @field #number LSOdT Time interval in seconds before the LSO will make its next call. -- @field #string senderac Name of the aircraft acting as sender for broadcasting radio messages from the carrier. DCS shortcoming workaround. +-- @field #string radiorelayLSO Name of the aircraft acting as sender for broadcasting LSO radio messages from the carrier. DCS shortcoming workaround. +-- @field #string radiorelayMSH Name of the aircraft acting as sender for broadcasting Marhsal radio messages from the carrier. DCS shortcoming workaround. -- @field #boolean turnintowind If true, carrier is currently turning into the wind. -- @field #boolean detour If true, carrier is currently making a detour from its path along the ME waypoints. -- @field Core.Point#COORDINATE Creturnto Position to return to after turn into the wind leg is over. @@ -204,6 +206,7 @@ -- @field #AIRBOSS.LSOCalls LSOCall Radio voice overs of the LSO. -- @field #AIRBOSS.MarshalCalls MarshalCall Radio voice over of the Marshal/Airboss. -- @field #number lowfuelAI Low fuel threshold for AI groups in percent. +-- @field #boolean emergency If true (default), allow emergency landings, i.e. bypass any pattern and go for final approach. -- @extends Core.Fsm#FSM --- Be the boss! @@ -299,13 +302,14 @@ -- -- The general structure -- --- * **F1 Help...**: Help submenu, see below. --- * **F2 Kneeboard...**: Kneeboard submenu, see below. Carrier information, weather report, player status. +-- * **F1 Help...** (Help submenu, see below.) +-- * **F2 Kneeboard...** (Kneeboard submenu, see below. Carrier information, weather report, player status.) -- * **F3 Request Marshal** -- * **F4 Request Commence** -- * **F5 Request Refueling** -- * **F6 Spinning** --- * **F7 [Reset My Status]** +-- * **F7 Emergency Landing** +-- * **F8 [Reset My Status]** -- -- ### Request Marshal -- @@ -347,6 +351,12 @@ -- -- If necessary, the player can call "Spinning" again when in the above mentioned steps. -- +-- ### Emergency Landing +-- +-- Request an emergency landing, i.e. bypass all pattern steps and go directly to the final approach. +-- +-- All section members are supposed to follow. Player (or section lead) is removed from all other queues and automatically added to the landing pattern queue. +-- -- ### [Reset My Status] -- -- This will reset the current player status. If player is currently in a marshal stack, he will be removed from the marshal queue and the stack above will collapse. @@ -785,7 +795,34 @@ -- -- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles/") -- --- ## Remarks +-- ## How To Use Your Own Voice Overs +-- +-- If you have a set of AIRBOSS sound files recorded or got it from elsewhere it is possible to use those instead of the default ones. +-- I recommend to use exactly the same file names as the original sound files have. +-- +-- However, the **timing is critical**! As sometimes sounds are played directly after one another, e.g. by saying the modex but also on other occations, the airboss +-- script has a radio queue implemented (actually two - one for the LSO and one for the Marshal/Airboss radio). +-- By this it is automatically taken care that played messages are not overlapping and played over each other. The disadvantage is, that the script needs to know +-- the exact duration of *each* voice over. For the default sounds this is hard coded in the source code. For your own files, you need to give that bit of information +-- to the script via the @{#AIRBOSS.SetVoiceOver}(**radiocall**, **duration**, **subtitle**, **subduration**, **filename**, **suffix**) function. Only the first two +-- parameters **radiocall** and **duration** are usually important to adjust here. +-- +-- For example, if you want to change the LSO "Call the Ball" and "Roger Ball" calls: +-- +-- airbossStennis:SetVoiceOver(airbossStennis.LSOCall.CALLTHEBALL, 0.6) +-- airbossStennis:SetVoiceOver(airbossStennis.LSOCall.ROGERBALL, 0.7) +-- +-- Again, changing the file name, subtitle, subtitle duration is not required if you name the file exactly like the original one, which is this case would be "LSO-RogerBall.ogg". +-- +-- ## Carrier Specific Voice Overs +-- +-- It is possible to use diffent sound files for different carriers. If you have set up two (or more) AIRBOSS objects at different carriers - say Stennis and Tarawa - each +-- carrier would use the files in the specified directory, e.g. +-- +-- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles Stenis/") +-- airbossTarawa:SetSoundfilesFolder("Airboss Soundfiles Tarawa/") +-- +-- ## The Radio Transmission Dilemma -- -- DCS offers two (actually three) ways to send radio messages. Each one has its advantages and disadvantages and it is important to understand the differences. -- @@ -794,8 +831,8 @@ -- *In principle*, the best way to transmit messages is via the [TransmitMessage](https://wiki.hoggitworld.com/view/DCS_command_transmitMessage) command. -- This method has the advantage that subtitles can be used and these subtitles are only displayed to the players who dialed in the same radio frequency as -- used for the transmission. --- However, this method unfortunately only works if the sending unit is an **aircraft**. There it is not usable by the AIRBOSS per se as the transmission comes from --- a naval unit (the carrier). +-- However, this method unfortunately only works if the sending unit is an **aircraft**. Therefore, it is not usable by the AIRBOSS per se as the transmission comes from +-- a naval unit (i.e. the carrier). -- -- As a workaround, you can put an aircraft, e.g. a Helicopter on the deck of the carrier or another ship of the strike group. The aircraft should be set to -- uncontrolled and maybe even to immortal. With the @{#AIRBOSS.SetRadioUnit}(*unitname*) function you can use this unit as "radio repeater". @@ -809,7 +846,7 @@ -- -- Another way to broadcast messages is via the [radio transmission trigger](https://wiki.hoggitworld.com/view/DCS_func_radioTransmission). This method can be used for all -- units (land, air, naval). However, messages cannot be subtitled. Therefore, subtitles are displayed to the players via normal textout messages. --- The disadvantage is that is is impossible to know which players have the right radio frequencies dialed in. There subtitles of the Marshal radio are displayed to all players +-- The disadvantage is that is is impossible to know which players have the right radio frequencies dialed in. Therefore, subtitles of the Marshal radio calls are displayed to all players -- inside the CCA. Subtitles on the LSO radio frequency are displayed to all players in the pattern. -- -- ### Sound to User @@ -990,6 +1027,8 @@ AIRBOSS = { windowcount = 0, LSOdT = nil, senderac = nil, + radiorelayLSO = nil, + radiorelayMSH = nil, turnintowind = nil, detour = nil, squadsetAI = nil, @@ -1004,6 +1043,7 @@ AIRBOSS = { LSOCall = nil, MarshalCall = nil, lowfuelAI = nil, + emergency = nil, } --- Aircraft types capable of landing on carrier (human+AI). @@ -1098,6 +1138,7 @@ AIRBOSS.CarrierType={ -- @field #string GROOVE_LC "Groove Level Cross". -- @field #string GROOVE_IW "Groove In the Wires". -- @field #string BOLTER "Bolter Pattern". +-- @field #string EMERGENCY "Emergency Landing". -- @field #string DEBRIEF "Debrief". AIRBOSS.PatternStep={ UNDEFINED="Undefined", @@ -1127,6 +1168,7 @@ AIRBOSS.PatternStep={ GROOVE_AL="Groove Abeam Landing Spot", GROOVE_LC="Groove Level Cross", BOLTER="Bolter Pattern", + EMERGENCY="Emergency Landing", DEBRIEF="Debrief", } @@ -1541,6 +1583,9 @@ function AIRBOSS:New(carriername, alias) -- Airboss is a nice guy. self:SetAirbossNiceGuy() + -- Allow emergency landings. + self:SetEmergencyLandings() + -- No despawn after engine shutdown by default. self:SetDespawnOnEngineShutdown(false) @@ -2158,6 +2203,20 @@ function AIRBOSS:SetAirbossNiceGuy(switch) return self end +--- Allow emergency landings, i.e. bypassing any pattern and go directly to final approach. +-- @param #AIRBOSS self +-- @param #boolean switch If true or nil, emergency landings are okay. +-- @return #AIRBOSS self +function AIRBOSS:SetEmergencyLandings(switch) + if switch==true or switch==nil then + self.emergency=true + else + self.emergency=false + end + return self +end + + --- Despawn AI groups after they they shut down their engines -- @param #AIRBOSS self -- @param #boolean switch If true or nil, AI groups are despawned. @@ -2393,6 +2452,25 @@ function AIRBOSS:SetRadioUnitName(unitname) return self end +--- Set unit acting as radio relay for the LSO radio. +-- @param #AIRBOSS self +-- @param #string unitname Name of the unit. +-- @return #AIRBOSS self +function AIRBOSS:SetRadioRelayLSO(unitname) + self.radiorelayLSO=unitname + return self +end + +--- Set unit acting as radio relay for the Marshal radio. +-- @param #AIRBOSS self +-- @param #string unitname Name of the unit. +-- @return #AIRBOSS self +function AIRBOSS:SetRadioRelayMarshal(unitname) + self.radiorelayMarshal=unitname + return self +end + + --- Use user sound output instead of radio transmission for messages. Might be handy if radio transmissions are broken. -- @param #AIRBOSS self -- @return #AIRBOSS self @@ -3071,7 +3149,7 @@ function AIRBOSS:_CheckRecoveryTimes() local v=UTILS.KnotsToMps(nextwindow.SPEED) -- Check that we do not go above max possible speed. - local vmax=self.carrier:GetSpeedMax() + local vmax=self.carrier:GetSpeedMax() v=math.min(v,vmax) -- Route carrier into the wind. Sets self.turnintowind=true @@ -3106,15 +3184,16 @@ end --@param #AIRBOSS self --@param #AIRBOSS.FlightGroup flight --@return #AIRBOSS.FlightGroup The leader of the section. Could be the flight itself. +--@return #boolean If true, flight is lead. function AIRBOSS:_GetFlightLead(flight) if flight.name~=flight.seclead then -- Section lead of flight. local lead=self.players[flight.seclead] - return lead + return lead,false else -- Flight without section or section lead. - return flight + return flight,true end end @@ -4967,7 +5046,18 @@ function AIRBOSS:_RefuelAI(flight) else + ------------------------------ + -- Guide AI to divert field -- + ------------------------------ + + -- Closest Airfield of the coaliton. local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME, self:GetCoalition()) + + -- Coordinate. + local divertcoord=divertfield:GetCoordinate() + + -- Landing waypoint. + wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(200), divertfield, {}, "Divert Field") end @@ -5385,7 +5475,6 @@ function AIRBOSS:_CollapseMarshalStack(flight, nopattern) self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.", flight.groupname, Tmarshal)) -- Add flight to pattern queue. - --table.insert(self.Qpattern, flight) self:_AddFlightToPatternQueue(flight) end @@ -6049,6 +6138,11 @@ function AIRBOSS:_AddFlightToPatternQueue(flight) -- Add flight to table. table.insert(self.Qpattern, flight) + -- Set flag to -1 (-1 is rather arbitrary but it should not be positive or -100 or -42). + flight.flag=-1 + -- New time stamp for time in pattern. + flight.time=timer.getAbsTime() + -- Init recovered switch. flight.recovered=false for _,elem in pairs(flight.elements) do @@ -6057,6 +6151,9 @@ function AIRBOSS:_AddFlightToPatternQueue(flight) -- Set recovered for all section members. for _,sec in pairs(flight.section) do + -- Set flag and timestamp for section members + sec.flag=-1 + sec.time=timer.getAbsTime() for _,elem in pairs(sec.elements) do elem.recoverd=false end @@ -6463,6 +6560,11 @@ function AIRBOSS:_CheckPlayerStatus() -- CASE I/II: In the wake. self:_Wake(playerData) + elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then + + -- Emergency landing. Player pos is not checked. + self:_Final(playerData, true) + elseif playerData.step==AIRBOSS.PatternStep.FINAL then -- CASE I/II: Turn to final and enter the groove. @@ -7950,15 +8052,17 @@ end --- Turn to final. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. -function AIRBOSS:_Final(playerData) - +-- @param #boolean nocheck If true, player is not checked to be in the right position. +function AIRBOSS:_Final(playerData, nocheck) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) local X, Z, rho, phi = self:_GetDistances(playerData.unit) - -- In front of carrier or more than 4 km behind carrier. - if self:_CheckAbort(X, Z, self.Final) then - self:_AbortPattern(playerData, X, Z, self.Final, true) - return + -- In front of carrier or more than 4 km behind carrier. + if not nocheck then + if self:_CheckAbort(X, Z, self.Final) then + self:_AbortPattern(playerData, X, Z, self.Final, true) + return + end end -- Relative heading 0=fly parallel +-90=fly perpendicular @@ -7970,8 +8074,15 @@ function AIRBOSS:_Final(playerData) -- Player's angle of bank. local roll=playerData.unit:GetRoll() + -- Get groove zone. + local zone=self:_GetZoneGroove() + + -- Check if player is in zone. + local inzone=playerData.unit:IsInZone(zone) + -- Check if player is in +-4 deg cone and flying towards the runway. - if math.abs(lineup)<=4 then + --if math.abs(lineup)<=4 then + if inzone then -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) @@ -7994,7 +8105,7 @@ function AIRBOSS:_Final(playerData) playerData.groove.X0=groovedata -- Next step: X start. - self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_XX) + self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_XX) end end @@ -8730,6 +8841,7 @@ end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ZONE functions ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Get Initial zone for Case I or II. -- @param #AIRBOSS self -- @param #number case Recovery Case. @@ -8742,8 +8854,7 @@ function AIRBOSS:_GetZoneInitial(case) -- Carrier coordinate. local cv=self:GetCoordinate() - -- Zone and vec2 array. - local zone + -- Vec2 array. local vec2 if case==1 then @@ -8775,11 +8886,43 @@ function AIRBOSS:_GetZoneInitial(case) end -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("CASE I/II initial.", vec2) + local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) return zone end +--- Get groove zone. +-- @param #AIRBOSS self +-- @param #number l Length of the groove in NM. Default 1.5 NM. +-- @param #number w Width of the groove in NM. Default +-- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. +function AIRBOSS:_GetZoneGroove(l, w) + + l=l or 1.5 + w=w or 0.5 + + -- Get radial, i.e. inverse of BRC. + local fbi=self:GetRadial(1, false, false) + + -- Stern coordinate. + local st=self:_GetSternCoord() + + -- Zone points. + local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90) + local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi) + local c3=st:Translate(UTILS.NMToMeters(w/2), fbi-90):Translate(UTILS.NMToMeters(l), fbi) + local c4=st:Translate(UTILS.NMToMeters(w/2), fbi+90):Translate(UTILS.NMToMeters(l), fbi) + local c5=st:Translate(UTILS.NMToMeters(0.10), fbi+90):Translate(UTILS.NMToMeters(0.3), fbi) + local c6=st:Translate(self.carrierparam.totwidthport, fbi+90) + + -- Vec2 array. + local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} + + -- Polygon zone. + local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + + return zone +end --- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case. -- @param #AIRBOSS self @@ -11195,13 +11338,18 @@ function AIRBOSS:CarrierDetour(coord, speed, uturn, uspeed) -- Speed in km/h. local speedkmh=UTILS.KnotsToKmph(speed) + local cspeedkmh=self.carrier:GetVelocityKMH() local uspeedkmh=UTILS.KnotsToKmph(uspeed) -- Waypoint table. local wp={} + -- Pos1 is a bit into. + local pos1=pos0:Translate(500, pos0:HeadingTo(coord)) + -- Create from/to waypoints. - table.insert(wp, pos0:WaypointGround(speedkmh)) + table.insert(wp, pos0:WaypointGround(cspeedkmh)) + table.insert(wp, pos1:WaypointGround(cspeedkmh)) table.insert(wp, coord:WaypointGround(speedkmh)) -- If enabled, go back to where you came from. @@ -11241,8 +11389,8 @@ function AIRBOSS:CarrierTurnIntoWind(time, vdeck, uturn) -- Wind speed. local _,vwind=self:GetWind() - -- Speed of carrier in m/s. - local vtot=vdeck-vwind + -- Speed of carrier in m/s but at least 2 knots. + local vtot=math.max(vdeck-vwind, UTILS.KnotsToMps(2)) -- Distance to travel local dist=vtot*time @@ -12197,7 +12345,7 @@ function AIRBOSS:Broadcast(radio, call, loud) ---------------------------- -- Get unit sending the transmission. - local sender=self:_GetRadioSender() + local sender=self:_GetRadioSender(radio) -- Construct file name and subtitle. local filename=self:_RadioFilename(call, loud) @@ -12535,14 +12683,31 @@ end --- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. -- @param #AIRBOSS self +-- @param #AIRBOSS.Radio radio Airboss radio data. -- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive. -function AIRBOSS:_GetRadioSender() +function AIRBOSS:_GetRadioSender(radio) -- Check if we have a sending aircraft. local sender=nil --Wrapper.Unit#UNIT + + -- Try the general default. if self.senderac then sender=UNIT:FindByName(self.senderac) end + + -- Try the specific marshal unit. + if radio.alias=="Marshal" then + if self.radiorelayMSH then + sender=UNIT:FindByName(self.radiorelayMSH) + end + end + + -- Try the specific LSO unit. + if radio.alias=="LSO" then + if self.radiorelayLSO then + sender=UNIT:FindByName(self.radiorelayLSO) + end + end -- Check that sender is alive and an aircraft. if sender and sender:IsAlive() and sender:IsAir() then @@ -12885,8 +13050,9 @@ function AIRBOSS:_AddF10Commands(_unitName) missionCommands.addCommandForGroup(gid, "Request Marshal", _rootPath, self._RequestMarshal, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "Request Commence", _rootPath, self._RequestCommence, self, _unitName) -- F4 missionCommands.addCommandForGroup(gid, "Request Refueling", _rootPath, self._RequestRefueling, self, _unitName) -- F5 - missionCommands.addCommandForGroup(gid, "Spinning", _rootPath, self._RequestSpinning, self, _unitName) -- F6 - missionCommands.addCommandForGroup(gid, "[Reset My Status]", _rootPath, self._ResetPlayerStatus, self, _unitName) -- F7 + missionCommands.addCommandForGroup(gid, "Spinning", _rootPath, self._RequestSpinning, self, _unitName) -- F6 + missionCommands.addCommandForGroup(gid, "Emergency Landing", _rootPath, self._RequestEmergency, self, _unitName) -- F7 + missionCommands.addCommandForGroup(gid, "[Reset My Status]", _rootPath, self._ResetPlayerStatus, self, _unitName) -- F8 end else self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName)) @@ -13012,6 +13178,71 @@ function AIRBOSS:_RequestMarshal(_unitName) end end +--- Request emergency landing. +-- @param #AIRBOSS self +-- @param #string _unitName Name fo the player unit. +function AIRBOSS:_RequestEmergency(_unitName) + self:F(_unitName) + + -- Get player unit and name. + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + + -- Check if we have a unit which is a player. + if _unit and _playername then + local playerData=self.players[_playername] --#AIRBOSS.PlayerData + + if playerData then + + local text="" + if not self.emergency then + + -- Mission designer did not allow emergency landing. + text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!" + + else + + -- Cleared. + text="roger, you can bypass the pattern and are cleared for final approach!" + + -- Now, if player is in the marshal or waiting queue he will be removed. But the new leader should stay in or not. + local lead=self:_GetFlightLead(playerData) + + -- Set set for lead. + self:_SetPlayerStep(lead, AIRBOSS.PatternStep.EMERGENCY) + + -- Also set emergency landing for all members. + for _,sec in pairs(lead.section) do + local sectionmember=sec --#AIRBOSS.PlayerData + self:_SetPlayerStep(sectionmember, AIRBOSS.PatternStep.EMERGENCY) + + -- Remove flight from spinning queue just in case (everone can spin on his own). + self:_RemoveFlightFromQueue(self.Qspinning, sectionmember) + end + + + -- Remove flight from waiting queue just in case. + self:_RemoveFlightFromQueue(self.Qwaiting, lead) + + if self:_InQueue(self.Qmarshal, lead.group) then + -- Remove flight from Marshal queue and add to pattern. + self:_RemoveFlightFromMarshalQueue(lead) + else + -- Add flight to pattern if he was not. + if not self:_InQueue(self.Qpattern, lead.group) then + self:_AddFlightToPatternQueue(lead) + end + end + + end + + -- Send message. + self:MessageToPlayer(playerData, text, "AIRBOSS") + + end + + end +end + --- Request spinning. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit. @@ -13030,25 +13261,20 @@ function AIRBOSS:_RequestSpinning(_unitName) local text="" if not self:_InQueue(self.Qpattern, playerData.group) then - -- Player not in pattern queue + -- Player not in pattern queue. text="negative, you have to be in the pattern to spin it!" - - --[[ - elseif playerData.seclead~=playerData.name then - - -- Player is not section lead - text="negative, your section lead has to call spinning." - ]] - + elseif playerData.step==AIRBOSS.PatternStep.SPINNING then + -- Player is already spinning. text="negative, you are already spinning." -- Check if player is in the right step. elseif not (playerData.step==AIRBOSS.PatternStep.BREAKENTRY or playerData.step==AIRBOSS.PatternStep.EARLYBREAK or playerData.step==AIRBOSS.PatternStep.LATEBREAK) then - + + -- Player is not in the right step. text="negative, you have to be in the right step to spin it!" else From 5ee0ae44cdf5ac0b5f04a024b01a1d650290d11c Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 Feb 2019 01:23:42 +0100 Subject: [PATCH 06/11] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 183 ++++++++++++++++++++---- 1 file changed, 152 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index b61af90df..70d22b19a 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -207,6 +207,8 @@ -- @field #AIRBOSS.MarshalCalls MarshalCall Radio voice over of the Marshal/Airboss. -- @field #number lowfuelAI Low fuel threshold for AI groups in percent. -- @field #boolean emergency If true (default), allow emergency landings, i.e. bypass any pattern and go for final approach. +-- @field #boolean respawnAI If true, respawn AI flights as they enter the CCA to detach and airfields from the mission plan. Default false. +-- @field #boolean turning If true, carrier is currently turning. -- @extends Core.Fsm#FSM --- Be the boss! @@ -1044,6 +1046,7 @@ AIRBOSS = { MarshalCall = nil, lowfuelAI = nil, emergency = nil, + respawnAI = nil, } --- Aircraft types capable of landing on carrier (human+AI). @@ -1589,6 +1592,9 @@ function AIRBOSS:New(carriername, alias) -- No despawn after engine shutdown by default. self:SetDespawnOnEngineShutdown(false) + -- No respawning of AI groups when entering the CCA. + self:SetRespawnAI(false) + -- Mission uses static weather by default. self:SetStaticWeather() @@ -1658,6 +1664,8 @@ function AIRBOSS:New(carriername, alias) BASE:TraceLevel(1) self.dTstatus=0.1 end + + --self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) -- Smoke zones. if self.Debug and false then @@ -2217,7 +2225,7 @@ function AIRBOSS:SetEmergencyLandings(switch) end ---- Despawn AI groups after they they shut down their engines +--- Despawn AI groups after they they shut down their engines. -- @param #AIRBOSS self -- @param #boolean switch If true or nil, AI groups are despawned. -- @return #AIRBOSS self @@ -2230,6 +2238,19 @@ function AIRBOSS:SetDespawnOnEngineShutdown(switch) return self end +--- Respawn AI groups once they reach the CCA. Clears any attached airbases and allows making them land on the carrier via script. +-- @param #AIRBOSS self +-- @param #boolean switch If true or nil, AI groups are respawned. +-- @return #AIRBOSS self +function AIRBOSS:SetRespawnAI(switch) + if switch==true or switch==nil then + self.respawnAI=true + else + self.respawnAI=false + end + return self +end + --- Set folder where the airboss sound files are located **within you mission (miz) file**. -- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission. @@ -3970,6 +3991,69 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=0.40, --0.38 too short }, + CASE={ + file="MARSHAL-Case", + suffix="ogg", + loud=false, + subtitle="", + duration=0.45, + }, + EXPECTED={ + file="MARSHAL-Expected", + suffix="ogg", + loud=false, + subtitle="", + duration=0.60, + }, + BRC={ + file="MARSHAL-BRC", + suffix="ogg", + loud=false, + subtitle="", + duration=0.68, + }, + HOLDAT={ + file="MARSHAL-HoldAt", + suffix="ogg", + loud=false, + subtitle="", + duration=0.43, + }, + ANGELS={ + file="MARSHAL-Angels", + suffix="ogg", + loud=false, + subtitle="", + duration=0.68, + }, + EXPECTED={ + file="MARSHAL-Expected", + suffix="ogg", + loud=false, + subtitle="", + duration=0.72, + }, + ALTIMETER={ + file="MARSHAL-Altimeter", + suffix="ogg", + loud=false, + subtitle="", + duration=0.73, + }, + POINT={ + file="MARSHAL-Point", + suffix="ogg", + loud=false, + subtitle="", + duration=0.42, + }, + REPORTSEEME={ + file="MARSHAL-ReportSeeMe", + suffix="ogg", + loud=false, + subtitle="", + duration=1.05, + }, CLICK={ file="AIRBOSS-RadioClick", suffix="ogg", @@ -4640,13 +4724,13 @@ function AIRBOSS:_ScanCarrierZone() if stack then -- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases. - self:_MarshalAI(knownflight, stack, true) + self:_MarshalAI(knownflight, stack, self.respawnAI) else -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. if not self:_InQueue(self.Qwaiting, knownflight.group) then - self:_WaitAI(knownflight, true) -- Group is respawned to clear any attached airfields. + self:_WaitAI(knownflight, self.respawnAI) -- Group is respawned to clear any attached airfields. end end @@ -8893,13 +8977,13 @@ end --- Get groove zone. -- @param #AIRBOSS self --- @param #number l Length of the groove in NM. Default 1.5 NM. --- @param #number w Width of the groove in NM. Default +-- @param #number l Length of the groove in NM. Default 2.0 NM. +-- @param #number w Width of the groove in NM. Default 0.3 NM. -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. function AIRBOSS:_GetZoneGroove(l, w) - l=l or 1.5 - w=w or 0.5 + l=l or 2.0 + w=w or 0.3 -- Get radial, i.e. inverse of BRC. local fbi=self:GetRadial(1, false, false) @@ -8907,6 +8991,8 @@ function AIRBOSS:_GetZoneGroove(l, w) -- Stern coordinate. local st=self:_GetSternCoord() + -- TODO: optimize for Tarawa. shift port. + -- Zone points. local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90) local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi) @@ -11579,7 +11665,25 @@ function AIRBOSS:_CheckPatternUpdate() self.Corientlast=vNew -- Carrier is turning when its heading changed by at least one degree since last check. - local turning=deltaLast>=1 + local turning=math.abs(deltaLast)>=1 + + -- Starting to turn. + if turning and not self.turning then + -- Turning! + self.turning=true + + -- Get heading. + local hdg + if self.turnintowind then + hdg=select(1,self:GetWind()) + else + hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) + end + + -- Inform everyone. + local text=string.format("staring turn to heading %03d°.", hdg) + self:MessageToMarshal(text, "AIRBOSS", "99") + end -- No update if carrier is turning! if turning then @@ -11624,12 +11728,13 @@ function AIRBOSS:_CheckPatternUpdate() local FB=self:GetFinalBearing(true) local text=string.format("new final bearing %03d°.", FB) self:MessageToMarshal(text, "AIRBOSS", "99") + self.turning=false end -- Reset parameters for next update check. self.Corientation=vNew self.Cposition=pos - self.Tpupdate=timer.getTime() + self.Tpupdate=timer.getTime() end end @@ -12310,7 +12415,16 @@ function AIRBOSS:RadioTransmission(radio, call, loud, delay) call~=self[caller].N6 and call~=self[caller].N7 and call~=self[caller].N8 and - call~=self[caller].N9 then + call~=self[caller].N9 and + call~=self[caller].EXPECTED and + call~=self[caller].BRC and + call~=self[caller].ALTIMETER and + call~=self[caller].POINT and + call~=self[caller].HOLDAT and + call~=self[caller].CASE and + call~=self[caller].POINT and + call~=self[caller].ANGELS and + call~=self[caller].CHARLIETIME then self:RadioTransmission(radio, self[caller].CLICK, false, delay) end end @@ -12889,47 +13003,54 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) text=text..string.format("Altimeter %.2f. Report see me.", qfe) -- Create new call to display complete subtitle. - --local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) + local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) - -- Until we have a case sound we take the noise for testing - local casecall=self:_NewRadioCall(self.MarshalCall.NOISE, "MARSHAL", text, self.Tmessage, modex) + + local delay=0 -- Case.. - self:RadioTransmission(self.MarshalRadio, casecall) + self:RadioTransmission(self.MarshalRadio, casecall, nil, delay) -- X.. - self:_Number2Radio(self.MarshalRadio, tostring(case)) + self:_Number2Radio(self.MarshalRadio, tostring(case), nil, delay) + delay=delay+0.5 -- expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) -- BRC.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC, nil, delay) -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc)) + self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc), nil, delay) + delay=delay+0.5 -- hold at.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT, nil, delay) + delay=delay+0.1 -- angels.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ANGELS) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ANGELS, nil, delay) -- X.. - self:_Number2Radio(self.MarshalRadio, tostring(angels)) + self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, delay) + delay=delay+0.5 -- Expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) -- Charlie time.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME, nil, delay) -- XY.. (hours) - self:_Number2Radio(self.MarshalRadio, CT[1]) + self:_Number2Radio(self.MarshalRadio, CT[1], nil, delay) -- XY.. (minutes) - self:_Number2Radio(self.MarshalRadio, CT[2]) + self:_Number2Radio(self.MarshalRadio, CT[2], nil, delay) + delay=delay+0.5 -- Altimeter.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, delay) -- XY.. - self:_Number2Radio(self.MarshalRadio, QFE[1]) + self:_Number2Radio(self.MarshalRadio, QFE[1], nil, delay) -- Point.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT, nil, delay) -- XY.. - self:_Number2Radio(self.MarshalRadio, QFE[2]) + self:_Number2Radio(self.MarshalRadio, QFE[2], nil, delay) + delay=delay+0.5 -- Report see me. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, delay) + delay=delay+0.2 -- Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CLICK) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CLICK, nil, delay) end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- From df43ff08415d9881e3b62807feb26a85335469f1 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 15 Feb 2019 17:00:22 +0100 Subject: [PATCH 07/11] AB v0.9.8w --- Moose Development/Moose/Ops/Airboss.lua | 880 +++++++++++++----------- 1 file changed, 492 insertions(+), 388 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 70d22b19a..8951c67f6 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -209,6 +209,8 @@ -- @field #boolean emergency If true (default), allow emergency landings, i.e. bypass any pattern and go for final approach. -- @field #boolean respawnAI If true, respawn AI flights as they enter the CCA to detach and airfields from the mission plan. Default false. -- @field #boolean turning If true, carrier is currently turning. +-- @field #AIRBOSS.GLE gle Glidesope error thresholds. +-- @field #AIRBOSS.LUE lue Lineup error thresholds. -- @extends Core.Fsm#FSM --- Be the boss! @@ -1047,6 +1049,8 @@ AIRBOSS = { lowfuelAI = nil, emergency = nil, respawnAI = nil, + gle = {}, + lue = {}, } --- Aircraft types capable of landing on carrier (human+AI). @@ -1112,6 +1116,25 @@ AIRBOSS.CarrierType={ -- @field #number FAST Really fast AoA threshold. -- @field #number SLOW Really slow AoA threshold. +--- Glideslope error thresholds in degrees. +-- @type AIRBOSS.GLE +-- @field #number _max Max _OK_ value. Default 0.4 deg. +-- @field #number _min Min _OK_ value. Default -0.3 deg. +-- @field #number High (H) threshold. Default 0.8 deg. +-- @field #number Low (L) threshold. Default -0.6 deg. +-- @field #number HIGH H threshold. Default 1.5 deg. +-- @field #number LOW L threshold. Default -0.9 deg. + +--- Lineup error thresholds in degrees. +-- @type AIRBOSS.LUE +-- @field #number _max Max _OK_ value. Default 0.5 deg. +-- @field #number _min Min _OK_ value. Default -0.5 deg. +-- @field #number Left (LUR) threshold. Default -1.0 deg. +-- @field #number Right (LUL) threshold. Default 1.0 deg. +-- @field #number LEFT LUR threshold. Default -3.0 deg. +-- @field #number RIGHT LUL threshold. Default 3.0 deg. + + --- Pattern steps. -- @type AIRBOSS.PatternStep -- @field #string UNDEFINED "Undefined". @@ -1279,13 +1302,6 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall ALTIMETER "Altimeter" call. -- @field #AIRBOSS.RadioCall POINT "Point" call. ---- Message for player. --- @type AIRBOSS.PlayerMessage --- @field string message Message text. --- @field #string sender Sender of the message. --- @field #string receiver Receiver of the message. --- @field #number duration --- @field #boolean clear --- Difficulty level. -- @type AIRBOSS.Difficulty @@ -1409,7 +1425,7 @@ AIRBOSS.Difficulty={ -- @field #number finalscore Final score if points are averaged over multiple passes. -- @field #boolean valid If true, player made a valid approach. Is set true on start of Groove X. -- @field #boolean subtitles If true, display subtitles of radio messages. --- @field #table messages Table of messages. +-- @field #boolean showhints If true, show step hints. -- @extends #AIRBOSS.FlightGroup --- Main group level radio menu: F10 Other/Airboss. @@ -1609,6 +1625,12 @@ function AIRBOSS:New(carriername, alias) -- Default player skill EASY. self:SetDefaultPlayerSkill(AIRBOSS.Difficulty.EASY) + + -- Default glideslope error thresholds. + self:SetGlideslopeErrorThresholds() + + -- Default lineup error thresholds. + --self:SetLine -- CCA 50 NM radius zone around the carrier. self:SetCarrierControlledArea() @@ -2295,6 +2317,45 @@ function AIRBOSS:SetDefaultMessageDuration(duration) return self end + +--- Set glideslope error thresholds. +-- @param #AIRBOSS self +-- @param #number _max +-- @param #number _min +-- @param #number High +-- @param #number HIGH +-- @param #number Low +-- @param #number LOW +-- @return #AIRBOSS self +function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) + self.gle._max=_max or 0.4 + self.gle.High=High or 0.8 + self.gle.HIGH=HIGH or 1.5 + self.gle._min=_min or -0.3 + self.gle.Low=Low or -0.6 + self.gle.LOW=LOW or -0.9 + return self +end + +--- Set lineup error thresholds. +-- @param #AIRBOSS self +-- @param #number _max +-- @param #number _min +-- @param #number Left +-- @param #number LEFT +-- @param #number Right +-- @param #number RIGHT +-- @return #AIRBOSS self +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) + self.lue._max=_max or 0.5 + self.lue._min=_min or -0.5 + self.lue.Left=Left or -1.0 + self.lue.LEFT=LEFT or -3.0 + self.lue.Right=Right or 1.0 + self.lue.RIGHT=RIGHT or 3.0 + return self +end + --- Set Case I Marshal radius. This is the radius of the valid zone around "the post" aircraft are supposed to be holding in the Case I Marshal stack. -- The post is 2.5 NM port of the carrier. -- @param #AIRBOSS self @@ -4842,7 +4903,7 @@ function AIRBOSS:_MarshalPlayer(playerData, stack) for _,_flight in pairs(playerData.section) do local flight=_flight --#AIRBOSS.PlayerData - -- TODO: Inform player? Should be done by lead via radio? + -- XXX: Inform player? Should be done by lead via radio? -- Set step. self:_SetPlayerStep(flight, AIRBOSS.PatternStep.HOLDING) @@ -4928,7 +4989,7 @@ function AIRBOSS:_WaitAI(flight, respawn) if respawn then -- This should clear the landing waypoints. - -- TODO: This resets the weapons and the fuel state. But not the units fortunately. + -- Note: This resets the weapons and the fuel state. But not the units fortunately. -- Get group template. local Template=group:GetTemplate() @@ -5078,7 +5139,7 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) if respawn then -- This should clear the landing waypoints. - -- TODO: This resets the weapons and the fuel state. But not the units fortunately. + -- Note: This resets the weapons and the fuel state. But not the units fortunately. -- Get group template. local Template=group:GetTemplate() @@ -5943,11 +6004,20 @@ function AIRBOSS:_NewPlayer(unitname) -- Set difficulty level. playerData.difficulty=playerData.difficulty or self.defaultskill - -- Subtitles of player + -- Subtitles of player. if playerData.subtitles==nil then playerData.subtitles=true end + -- Show step hints. + if playerData.showhints==nil then + if playerData.difficulty~=AIRBOSS.Difficulty.HARD then + playerData.showhints=false + else + playerData.showhints=true + end + end + -- Points rewarded. playerData.points={} @@ -6190,7 +6260,7 @@ function AIRBOSS:_CheckSectionRecovered(flight) -- Check all elements of the secmember flight group. for _,_element in pairs(sectionmember.elements) do - local element=_element --#AIROBSS.FlightElement + local element=_element --#AIRBOSS.FlightElement if not element.recovered then return false end @@ -7389,7 +7459,6 @@ function AIRBOSS:_Holding(playerData) self:T3("Player is still in the holding zone. Good job.") else -- Player left the holding zone. - self:T("Player just left the holding zone. Come back!") text=text..string.format("You just left the holding zone. Watch your numbers!") playerData.holding=false end @@ -7428,12 +7497,11 @@ function AIRBOSS:_Holding(playerData) -- Player left holding zone if inholdingzone then -- Player is back in the holding zone. - self:T("Player is back in the holding zone after leaving it.") text=text..string.format("You are back in the holding zone. Now stay there!") playerData.holding=true else -- Player is still outside the holding zone. - self:T2("Player still outside the holding zone. What are you doing man?!") + self:T3("Player still outside the holding zone. What are you doing man?!") end elseif playerData.holding==nil then @@ -7444,9 +7512,6 @@ function AIRBOSS:_Holding(playerData) -- Player arrived in holding zone. playerData.holding=true - -- Debug output. - self:T("Player entered the holding zone for the first time.") - -- Inform player. text=text..string.format("You arrived at the holding zone.") @@ -7463,20 +7528,18 @@ function AIRBOSS:_Holding(playerData) playerData.warning=true end - -- No info for the pros. - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - text="" - end - else -- Player did not yet arrive in holding zone. - self:T2("Waiting for player to arrive in the holding zone.") + self:T3("Waiting for player to arrive in the holding zone.") end end - -- Send message. - self:MessageToPlayer(playerData, text, "AIRBOSS", nil, 5) + -- Send message. + if playerData.showhints then + self:MessageToPlayer(playerData, text, "MARSHAL") + end + end @@ -7523,13 +7586,12 @@ function AIRBOSS:_Commencing(playerData, zonecheck) text=text.."Proceed to initial." else text=text.."Descent to platform." - if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then text=text.." VSI 4000 ft/min until you reach 5000 ft." end end -- Message to player. - --self:MessageToPlayer(playerData, text, "MARSHAL", nil, 3) self:MessageToPlayer(playerData, text, "MARSHAL") end @@ -7557,7 +7619,7 @@ end --- Start pattern when player enters the initial zone in case I/II recoveries. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. --- @return #boolean True if player is in the inital zone. +-- @return #boolean True if player is in the initial zone. function AIRBOSS:_Initial(playerData) -- Check if player is in initial zone and entering the CASE I pattern. @@ -7570,7 +7632,7 @@ function AIRBOSS:_Initial(playerData) if inzone and math.abs(relheading)<60 then -- Send message for normal and easy difficulty. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then + if playerData.showhints then -- Inform player. local hint=string.format("Initial") @@ -7805,128 +7867,7 @@ function AIRBOSS:_BolterPattern(playerData) end end ---- Display hint to player. --- @param #AIRBOSS self --- @param #AIRBOSS.PlayerData playerData Player data table. --- @param #number delay Delay before playing sound messages. Default 0 sec. -function AIRBOSS:_PlayerHint(playerData, delay) - -- No hint for the pros. - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - return - end - - -- Get optimal altitude, distance and speed. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData) - - -- Get altitude hint. - local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData, alt) - - -- Get speed hint. - local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData, speed) - - -- Get AoA hint. - local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData, aoa) - - -- Get distance to the boat hint. - local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData, dist) - - -- Message to player. - local hint="" - if hintAlt then - hint=hint.."\n"..hintAlt - end - if hintSpeed then - hint=hint.."\n"..hintSpeed - end - if hintAoA then - hint=hint.."\n"..hintAoA - end - if hintDist then - hint=hint.."\n"..hintDist - end - - -- Debriefing text. - local debrief="" - if debriefAlt then - debrief=debrief.."\n-"..debriefAlt - end - if debriefSpeed then - debrief=debrief.."\n-"..debriefSpeed - end - if debriefAoA then - debrief=debrief.."\n-"..debriefAoA - end - if debriefDist then - debrief=debrief.."\n-"..debriefDist - end - - -- Add step to debriefing. - if debrief~="" then - self:_AddToDebrief(playerData, debrief) - end - - delay=delay or 0 - if callAlt then - self:Sound2Player(playerData, self.LSORadio, callAlt, false, delay) - delay=delay+callAlt.duration+0.5 - end - if callSpeed then - self:Sound2Player(playerData, self.LSORadio, callSpeed, false, delay) - delay=delay+callSpeed.duration+0.5 - end - if callAoA then - self:Sound2Player(playerData, self.LSORadio, callAoA, false, delay) - delay=delay+callAoA.duration+0.5 - end - if callDist then - self:Sound2Player(playerData, self.LSORadio, callDist, false, delay) - delay=delay+callDist.duration+0.5 - end - - -- ARC IN info. - if playerData.step==AIRBOSS.PatternStep.ARCIN then - - -- Hint turn and set TACAN. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - -- Get inverse magnetic radial without offset ==> FB for Case II or BRC for Case III. - local radial=self:GetRadial(playerData.case, true, false, true) - local turn="right" - if self.holdingoffset<0 then - turn="left" - end - hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) - end - - end - - -- DIRTUP additonal info. - if playerData.step==AIRBOSS.PatternStep.DIRTYUP then - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nFAF! Checks completed. Nozzles 50°." - else - hint=hint.."\nDirty up! Hook, gear and flaps down." - end - end - end - - -- BULLSEYE additonal info. - if playerData.step==AIRBOSS.PatternStep.BULLSEYE then - -- Hint follow the needles. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then - hint=hint..string.format("\nIntercept glideslope and follow the needles.") - end - end - end - - -- Message to player. - if hint~="" then - local text=string.format("%s%s", playerData.step, hint) - self:MessageToPlayer(playerData, hint, "AIRBOSS", "") - end -end --- Break entry for case I/II recoveries. -- @param #AIRBOSS self @@ -8683,7 +8624,7 @@ function AIRBOSS:_CheckFoulDeck(playerData) self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2) -- Player hint for flight students. - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then + if playerData.showhints then local text=string.format("overfly landing area and enter bolter pattern.") self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3) end @@ -10091,19 +10032,20 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) -- Glideslope high/low calls. --TODO: introduce GSE enumerator values. - if glideslopeError>1.5 then + if glideslopeError>self.gle.HIGH then --1.5 then -- "You're high!" self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true) advice=advice+self.LSOCall.HIGH.duration - elseif glideslopeError>0.8 then + elseif glideslopeError>self.gle.High then --0.8 then -- "You're high." self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, false) advice=advice+self.LSOCall.HIGH.duration - elseif glideslopeError<-0.9 then + elseif glideslopeError3 then + elseif lineupError>self.lue.RIGHT then --3 then -- "Right for lineup!" self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true) advice=advice+self.LSOCall.RIGHTFORLINEUP.duration - elseif lineupError>1 then + elseif lineupError>self.lue.Right then -- 1 then -- "Right for lineup." self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false) advice=advice+self.LSOCall.RIGHTFORLINEUP.duration @@ -10369,7 +10311,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) elseif AOA FB for Case II or BRC for Case III. + local radial=self:GetRadial(playerData.case, true, false, true) + local turn="right" + if self.holdingoffset<0 then + turn="left" + end + hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) + end + + end + + -- DIRTUP additonal info. + if playerData.step==AIRBOSS.PatternStep.DIRTYUP then + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + hint=hint.."\nFAF! Checks completed. Nozzles 50°." + else + hint=hint.."\nDirty up! Hook, gear and flaps down." + end + end + end + + -- BULLSEYE additonal info. + if playerData.step==AIRBOSS.PatternStep.BULLSEYE then + -- Hint follow the needles. + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then + hint=hint..string.format("\nIntercept glideslope and follow the needles.") + end + end + end + + -- Message to player. + if hint~="" then + local text=string.format("%s%s", playerData.step, hint) + self:MessageToPlayer(playerData, hint, "AIRBOSS", "") + end +end + + +--- Display hint for flight students about the (next) step. Message is displayed after one second. +-- @param #AIRBOSS self +-- @param #AIRBOSS.PlayerData playerData Player data. +-- @param #string step Step for which hint is given. +function AIRBOSS:_StepHint(playerData, step) + + -- Set step. + step=step or playerData.step + + -- Message is only for "Flight Students". + if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then + + -- Get optimal parameters at step. + local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData, step) + + -- Hint: + local hint="" + + -- Altitude. + if alt then + hint=hint..string.format("\nAltitude %d ft", UTILS.MetersToFeet(alt)) + end + + -- AoA. + if aoa then + hint=hint..string.format("\nAoA %.1f", aoa) + end + + -- Speed. + if speed then + hint=hint..string.format("\nSpeed %d knots", UTILS.MpsToKnots(speed)) + end + + -- Distance to the boat. + if dist then + hint=hint..string.format("\nDistance to the boat %.1f NM", UTILS.MetersToNM(dist)) + end + + if step==AIRBOSS.PatternStep.ABEAM then + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." + else + hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down." + end + end + + -- Check if there was actually anything to tell. + if hint~="" then + + -- Compile text if any. + local text=string.format("Optimal setup at next step %s:%s", step, hint) + + -- Send hint to player. + self:MessageToPlayer(playerData, text, "AIRBOSS", "", nil, false, 1) + + end + + end end @@ -10701,23 +10799,20 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt) local radiocall=nil --#AIRBOSS.RadioCall local hint="" - local loud=false if _error>badscore then --hint=string.format("You're high.") - radiocall=self:_NewRadioCall(self.LSOCall.HIGH, nil, nil, 5) - loud=true + radiocall=self:_NewRadioCall(self.LSOCall.HIGH, "Paddles", "") elseif _error>lowscore then --hint= string.format("You're slightly high.") - radiocall=self:_NewRadioCall(self.LSOCall.HIGH, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.HIGH, "Paddles", "") elseif _error<-badscore then --hint=string.format("You're low. ") - radiocall=self:_NewRadioCall(self.LSOCall.LOW, nil, nil, 5) - loud=true + radiocall=self:_NewRadioCall(self.LSOCall.LOW, "Paddles", "") elseif _error<-lowscore then --hint=string.format("You're slightly low.") - radiocall=self:_NewRadioCall(self.LSOCall.LOW, nil, nil, 5) + radiocall=self:_NewRadioCall(self.LSOCall.LOW, "Paddles", "") else - hint=string.format("Good altitude.") + hint=string.format("Good altitude. ") end -- Extend or decrease depending on skill. @@ -10738,6 +10833,133 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt) return hint, debrief,radiocall end +--- Score for correct AoA. +-- @param #AIRBOSS self +-- @param #AIRBOSS.PlayerData playerData Player data. +-- @param #number optaoa Optimal AoA. +-- @return #string Feedback message text or easy and normal difficulty level or nil for hard. +-- @return #string Debriefing text. +-- @return #AIRBOSS.RadioCall Radio call. +function AIRBOSS:_AoACheck(playerData, optaoa) + + if optaoa==nil then + return nil, nil + end + + -- Get relative score. + local lowscore, badscore = self:_GetGoodBadScore(playerData) + + -- Player AoA + local aoa=playerData.unit:GetAoA() + + -- Altitude error +-X% + local _error=(aoa-optaoa)/optaoa*100 + + -- Get aircraft AoA parameters. + local aircraftaoa=self:_GetAircraftAoA(playerData) + + -- Radio call for flight students. + local radiocall=nil --#AIRBOSS.RadioCall + + -- Rate aoa. + local hint="" + if aoa>=aircraftaoa.SLOW then + --hint="Your're slow!" + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "Paddles", "") + elseif aoa>=aircraftaoa.Slow then + --hint="Your're slow." + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "Paddles", "") + elseif aoa>=aircraftaoa.OnSpeedMax then + hint="Your're a little slow. " + elseif aoa>=aircraftaoa.OnSpeedMin then + hint="You're on speed. " + elseif aoa>=aircraftaoa.Fast then + hint="You're a little fast. " + elseif aoa>=aircraftaoa.FAST then + --hint="Your're fast." + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "Paddles", "") + else + --hint="You're fast!" + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "Paddles", "") + end + + -- Extend or decrease depending on skill. + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + -- Also inform students about optimal value. + hint=hint..string.format("Optimal AoA is %.1f.", self:_AoADeg2Units(playerData, optaoa)) + elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + -- We keep is short normally. + hint="" + elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + -- No hint at all for the pros. + hint="" + end + + -- Debriefing text. + local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.", self:_AoADeg2Units(playerData, aoa), _error, self:_AoADeg2Units(playerData, optaoa)) + + return hint, debrief,radiocall +end + +--- Evaluate player's speed. +-- @param #AIRBOSS self +-- @param #AIRBOSS.PlayerData playerData Player data table. +-- @param #number speedopt Optimal speed in m/s. +-- @return #string Feedback text. +-- @return #string Debriefing text. +-- @return #AIRBOSS.RadioCall Radio call. +function AIRBOSS:_SpeedCheck(playerData, speedopt) + + if speedopt==nil then + return nil, nil + end + + -- Player altitude. + local speed=playerData.unit:GetVelocityMPS() + + -- Get relative score. + local lowscore, badscore=self:_GetGoodBadScore(playerData) + + -- Altitude error +-X% + local _error=(speed-speedopt)/speedopt*100 + + -- Radio call for flight students. + local radiocall=nil --#AIRBOSS.RadioCall + + local hint="" + if _error>badscore then + --hint=string.format("You're fast.") + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", "") + elseif _error>lowscore then + --hint= string.format("You're slightly fast.") + radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", "") + elseif _error<-badscore then + --hint=string.format("You're slow.") + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", "") + elseif _error<-lowscore then + --hint=string.format("You're slightly slow.") + radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", "") + else + hint=string.format("Good speed. ") + end + + -- Extend or decrease depending on skill. + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + hint=hint..string.format("Optimal speed is %d knots.", UTILS.MpsToKnots(speedopt)) + elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + -- We keep is short normally. + hint="" + elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + -- No hint at all for pros. + hint="" + end + + -- Debrief text. + local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.", UTILS.MpsToKnots(speed), _error, UTILS.MpsToKnots(speedopt)) + + return hint, debrief, radiocall +end + --- Evaluate player's distance to the boat at checkpoint. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -10781,7 +11003,6 @@ function AIRBOSS:_DistanceCheck(playerData, optdist) -- We keep it short normally. elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. - hint="" end -- Debriefing text. @@ -10790,140 +11011,9 @@ function AIRBOSS:_DistanceCheck(playerData, optdist) return hint, debrief, nil end ---- Score for correct AoA. --- @param #AIRBOSS self --- @param #AIRBOSS.PlayerData playerData Player data. --- @param #number optaoa Optimal AoA. --- @return #string Feedback message text or easy and normal difficulty level or nil for hard. --- @return #string Debriefing text. --- @return #AIRBOSS.RadioCall Radio call. -function AIRBOSS:_AoACheck(playerData, optaoa) - - if optaoa==nil then - return nil, nil - end - - -- Get relative score. - local lowscore, badscore = self:_GetGoodBadScore(playerData) - - -- Player AoA - local aoa=playerData.unit:GetAoA() - - -- Altitude error +-X% - local _error=(aoa-optaoa)/optaoa*100 - - -- Get aircraft AoA parameters. - local aircraftaoa=self:_GetAircraftAoA(playerData) - - -- Radio call for flight students. - local radiocall=nil --#AIRBOSS.RadioCall - - -- Rate aoa. - local hint="" - local loud=false - if aoa>=aircraftaoa.SLOW then - --hint="Your're slow!" - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, nil, nil, 5) - loud=true - elseif aoa>=aircraftaoa.Slow then - --hint="Your're slow." - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, nil, nil, 5) - elseif aoa>=aircraftaoa.OnSpeedMax then - hint="Your're a little slow." - elseif aoa>=aircraftaoa.OnSpeedMin then - hint="You're on speed." - elseif aoa>=aircraftaoa.Fast then - hint="You're a little fast." - elseif aoa>=aircraftaoa.FAST then - --hint="Your're fast." - radiocall=self:_NewRadioCall(self.LSOCall.FAST, nil, nil, 5) - else - --hint="You're fast!" - radiocall=self:_NewRadioCall(self.LSOCall.FAST, nil, nil, 5) - loud=true - end - - -- Extend or decrease depending on skill. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - -- Also inform students about optimal value. - hint=hint..string.format(" Optimal AoA is %.1f.", self:_AoADeg2Units(playerData, optaoa)) - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then - -- We keep is short normally. - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then - -- No hint at all for the pros. - hint="" - end - - -- Debriefing text. - local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.", self:_AoADeg2Units(playerData, aoa), _error, self:_AoADeg2Units(playerData, optaoa)) - - return hint, debrief,radiocall -end - ---- Evaluate player's speed. --- @param #AIRBOSS self --- @param #AIRBOSS.PlayerData playerData Player data table. --- @param #number speedopt Optimal speed in m/s. --- @return #string Feedback text. --- @return #string Debriefing text. --- @return #AIRBOSS.RadioCall Radio call. -function AIRBOSS:_SpeedCheck(playerData, speedopt) - - if speedopt==nil then - return nil, nil - end - - -- Player altitude. - local speed=playerData.unit:GetVelocityMPS() - - -- Get relative score. - local lowscore, badscore=self:_GetGoodBadScore(playerData) - - -- Altitude error +-X% - local _error=(speed-speedopt)/speedopt*100 - - -- Radio call for flight students. - local radiocall=nil --#AIRBOSS.RadioCall - - local hint="" - local loud=false - if _error>badscore then - --hint=string.format("You're fast.") - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", nil, 5) - loud=true - elseif _error>lowscore then - --hint= string.format("You're slightly fast.") - radiocall=self:_NewRadioCall(self.LSOCall.FAST, "AIRBOSS", nil, 5) - elseif _error<-badscore then - --hint=string.format("You're slow.") - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", nil, 5) - elseif _error<-lowscore then - --hint=string.format("You're slightly slow.") - radiocall=self:_NewRadioCall(self.LSOCall.SLOW, "AIRBOSS", nil, 5) - loud=true - else - hint=string.format("Good speed.") - end - - -- Extend or decrease depending on skill. - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - hint=hint..string.format(" Optimal speed is %d knots.", UTILS.MpsToKnots(speedopt)) - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) - elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then - -- We keep is short normally. - self:Sound2Player(playerData, self.LSORadio, radiocall, loud) - elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then - -- No hint at all for pros. - hint="" - end - - -- Debrief text. - local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.", UTILS.MpsToKnots(speed), _error, UTILS.MpsToKnots(speedopt)) - - return hint, debrief, radiocall -end +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DEBRIEFING +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Append text to debriefing. -- @param #AIRBOSS self @@ -11201,66 +11291,6 @@ function AIRBOSS:_Debrief(playerData) end end ---- Display hint for flight students about the (next) step. Message is displayed after one second. --- @param #AIRBOSS self --- @param #AIRBOSS.PlayerData playerData Player data. --- @param #string step Step for which hint is given. -function AIRBOSS:_StepHint(playerData, step) - - -- Set step. - step=step or playerData.step - - -- Message is only for "Flight Students". - if playerData.difficulty==AIRBOSS.Difficulty.EASY then - - -- Get optimal parameters at step. - local alt, aoa, dist, speed=self:_GetAircraftParameters(playerData, step) - - -- Hint: - local hint="" - - -- Altitude. - if alt then - hint=hint..string.format("\nAltitude %d ft", UTILS.MetersToFeet(alt)) - end - - -- AoA. - if aoa then - hint=hint..string.format("\nAoA %.1f", aoa) - end - - -- Speed. - if speed then - hint=hint..string.format("\nSpeed %d knots", UTILS.MpsToKnots(speed)) - end - - -- Distance to the boat. - if dist then - hint=hint..string.format("\nDistance to the boat %.1f NM", UTILS.MetersToNM(dist)) - end - - if step==AIRBOSS.PatternStep.ABEAM then - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." - else - hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down." - end - end - - -- Check if there was actually anything to tell. - if hint~="" then - - -- Compile text if any. - local text=string.format("Optimal setup at next step %s:%s", step, hint) - - -- Send hint to player. - self:MessageToPlayer(playerData, text, "AIRBOSS", "", nil, false, 1) - - end - - end -end - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- CARRIER ROUTING Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -11989,6 +12019,34 @@ function AIRBOSS:_GetTowerFrequency() end end +--- Get error margin depending on player skill. +-- +-- * Flight students: 10% and 20% +-- * Naval Aviators: 5% and 10% +-- * TOPGUN Graduates: 2.5% and 5% +-- +-- @param #AIRBOSS self +-- @param #AIRBOSS.PlayerData playerData Player data table. +-- @return #number Error margin for still being okay. +-- @return #number Error margin for really sucking. +function AIRBOSS:_GetGoodBadScore(playerData) + + local lowscore + local badscore + if playerData.difficulty==AIRBOSS.Difficulty.EASY then + lowscore=10 + badscore=20 + elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then + lowscore=5 + badscore=10 + elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then + lowscore=2.5 + badscore=5 + end + + return lowscore, badscore +end + --- Check if aircraft is capable of landing on this aircraft carrier. -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. (Will also work with groups as given parameter.) @@ -12363,6 +12421,7 @@ end function AIRBOSS:RadioTransmission(radio, call, loud, delay) self:F2({radio=radio, call=call, loud=loud, delay=delay}) + -- Nil check. if radio==nil or call==nil then return end @@ -13012,6 +13071,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:RadioTransmission(self.MarshalRadio, casecall, nil, delay) -- X.. self:_Number2Radio(self.MarshalRadio, tostring(case), nil, delay) + delay=delay+0.5 -- expected.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) @@ -13019,6 +13079,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC, nil, delay) -- XYZ.. self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc), nil, delay) + delay=delay+0.5 -- hold at.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT, nil, delay) @@ -13027,6 +13088,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ANGELS, nil, delay) -- X.. self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, delay) + delay=delay+0.5 -- Expected.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) @@ -13036,6 +13098,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:_Number2Radio(self.MarshalRadio, CT[1], nil, delay) -- XY.. (minutes) self:_Number2Radio(self.MarshalRadio, CT[2], nil, delay) + delay=delay+0.5 -- Altimeter.. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, delay) @@ -13045,6 +13108,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT, nil, delay) -- XY.. self:_Number2Radio(self.MarshalRadio, QFE[2], nil, delay) + delay=delay+0.5 -- Report see me. self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, delay) @@ -13140,6 +13204,7 @@ function AIRBOSS:_AddF10Commands(_unitName) missionCommands.addCommandForGroup(gid, "Flight Student", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.EASY) -- F1 missionCommands.addCommandForGroup(gid, "Naval Aviator", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.NORMAL) -- F2 missionCommands.addCommandForGroup(gid, "TOPGUN Graduate", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.HARD) -- F3 + missionCommands.addCommandForGroup(gid, "Hints On/Off", _skillPath, self._SetHintsOnOff, self, playername) -- F4 -- F10/Airboss//F1 Help/ missionCommands.addCommandForGroup(gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "Attitude Monitor", _helpPath, self._DisplayAttitude, self, _unitName) -- F4 @@ -13985,7 +14050,7 @@ function AIRBOSS:_DisplayDebriefing(_unitName) for _,_data in pairs(playerData.lastdebrief) do local step=_data.step local comment=_data.hint - text=text..string.format("* %s:\n",step) + text=text..string.format("* %s:",step) text=text..string.format("%s\n", comment) end else @@ -14286,6 +14351,45 @@ function AIRBOSS:_SetDifficulty(playername, difficulty) else self:E(self.lid..string.format("ERROR: Could not get player data for player %s.", playername)) end + + -- Set hints as well. + if playerData.difficulty==AIRBOSS.Difficulty.HARD then + playerData.showhints=false + else + playerData.showhints=true + end + +end + +--- Turn player's aircraft attitude display on or off. +-- @param #AIRBOSS self +-- @param #string _unitname Name of the player unit. +function AIRBOSS:_SetHintsOnOff(_unitname) + self:F2(_unitname) + + -- Get player unit and player name. + local unit, playername = self:_GetPlayerUnitAndName(_unitname) + + -- Check if we have a player. + if unit and playername then + + -- Player data. + local playerData=self.players[playername] --#AIRBOSS.PlayerData + + if playerData then + playerData.showhints=not playerData.showhints + + -- Inform player. + local text="" + if playerData.showhints==true then + text=string.format("hints are now ON.") + elseif playerData.showhints==false then + text=string.format("hints are now OFF.") + end + self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + + end + end end --- Turn player's aircraft attitude display on or off. From 60b6d2ade03a3fdc0ab761a08442db6e50886515 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 17 Feb 2019 23:16:07 +0100 Subject: [PATCH 08/11] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 975 ++++++++++++++++-------- 1 file changed, 644 insertions(+), 331 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 8951c67f6..b79a9c42a 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -157,7 +157,9 @@ -- @field #table Qwaiting Queue of aircraft groups waiting outside 10 NM zone for the next free Marshal stack. -- @field #table Qspinning Queue of aircraft currently spinning. -- @field #table RQMarshal Radio queue of marshal. +-- @field #number TQMarshal Abs mission time, the last transmission ended. -- @field #table RQLSO Radio queue of LSO. +-- @field #number TQLSO Abs mission time, the last transmission ended. -- @field #number Nmaxpattern Max number of aircraft in landing pattern. -- @field #number Nmaxmarshal Number of max Case I Marshal stacks available. Default 3, i.e. angels 2, 3 and 4. -- @field #number NmaxSection Number of max section members (excluding the lead itself), i.e. NmaxSection=1 is a section of two. @@ -580,7 +582,7 @@ -- -- * 2.5 Points **B**: "Bolder", when the player landed but did not catch a wire. -- * 1.0 Points **WO**: "Wave-Off": Player got waved off in the final parts of the groove. --- * 1.0 Points **PWO**: "Pattern Wave-Off", when pilot was far away from where he should be in the pattern. For example, being long in the groove gives a "LIG PWO". +-- * 2.0 Points **PWO**: "Pattern Wave-Off", when pilot was far away from where he should be in the pattern. For example, being long in the groove gives a "LIG PWO". -- * 0.0 Points **CUT**: "Cut pass", when player was waved off but landed anyway. -- -- ## Foul Deck Waveoff @@ -1000,6 +1002,8 @@ AIRBOSS = { Qspinning = {}, RQMarshal = {}, RQLSO = {}, + TQMarshal = 0, + TQLSO = 0, Nmaxpattern = nil, Nmaxmarshal = nil, NmaxSection = nil, @@ -1277,9 +1281,17 @@ AIRBOSS.GroovePos={ --- Marshal radio calls. -- @type AIRBOSS.MarshalCalls --- @field #AIRBOSS.RadioCall RADIOCHECK "Radio check" call. --- @field #AIRBOSS.RadioCall SAYNEEDLES "Say needles" call. +-- @field #AIRBOSS.RadioCall ALTIMETER "Altimeter" call. +-- @field #AIRBOSS.RadioCall BRC "BRC" call. +-- @field #AIRBOSS.RadioCall CARRIERTURNTOHEADING "Turn to heading" call. +-- @field #AIRBOSS.RadioCall CASE "Case" call. +-- @field #AIRBOSS.RadioCall CHARLIETIME "Charlie Time" call. +-- @field #AIRBOSS.RadioCall CLEAREDFORCASE "You're cleared for case" call. +-- @field #AIRBOSS.RadioCall DEGREES "Degrees" call. +-- @field #AIRBOSS.RadioCall EXPECTED "Expected" call. -- @field #AIRBOSS.RadioCall FLYNEEDLES "Fly your needles" call. +-- @field #AIRBOSS.RadioCall HOLDATANGELS "Hold at angels" call. +-- @field #AIRBOSS.RadioCall MARSHALRADIAL "Marshal radial" call. -- @field #AIRBOSS.RadioCall N0 "Zero" call. -- @field #AIRBOSS.RadioCall N1 "One" call. -- @field #AIRBOSS.RadioCall N2 "Two" call. @@ -1290,17 +1302,21 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N7 "Seven" call. -- @field #AIRBOSS.RadioCall N8 "Eight" call. -- @field #AIRBOSS.RadioCall N9 "Nine" call. +-- @field #AIRBOSS.RadioCall NEWFB "New final bearing" call. +-- @field #AIRBOSS.RadioCall OBS "Obs" call. +-- @field #AIRBOSS.RadioCall POINT "Point" call. +-- @field #AIRBOSS.RadioCall RADIOCHECK "Radio check" call. +-- @field #AIRBOSS.RadioCall RECOVERY "Recovery" call. +-- @field #AIRBOSS.RadioCall RECOVERYPAUSEDNOTICE "Recovery paused until further notice" call. +-- @field #AIRBOSS.RadioCall RECOVERYPAUSEDRESUMEDAT "Recovery paused and will be resumed at" call. +-- @field #AIRBOSS.RadioCall RESUMERECOVERY "Recovery paused until further notice" call. +-- @field #AIRBOSS.RadioCall REPORTSEEME "Report see me" call. +-- @field #AIRBOSS.RadioCall SAYNEEDLES "Say needles" call. +-- @field #AIRBOSS.RadioCall STACKFULL "Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions" call. +-- @field #AIRBOSS.RadioCall STARTINGRECOVERY "Starting aircraft recovery" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. --- @field #AIRBOSS.RadioCall CASE "Case" call. --- @field #AIRBOSS.RadioCall EXPECTED "Expected" call. --- @field #AIRBOSS.RadioCall BRC "BRC" call. --- @field #AIRBOSS.RadioCall CHARLIETIME "Charlie Time" call. --- @field #AIRBOSS.RadioCall REPORTSEEME "Report see me" call. --- @field #AIRBOSS.RadioCall HOLDAT "Hold at" call. --- @field #AIRBOSS.RadioCall ANGELS "Angels" call. --- @field #AIRBOSS.RadioCall ALTIMETER "Altimeter" call. --- @field #AIRBOSS.RadioCall POINT "Point" call. + --- Difficulty level. @@ -1630,7 +1646,7 @@ function AIRBOSS:New(carriername, alias) self:SetGlideslopeErrorThresholds() -- Default lineup error thresholds. - --self:SetLine + self:SetLineupErrorThresholds() -- CCA 50 NM radius zone around the carrier. self:SetCarrierControlledArea() @@ -1679,8 +1695,8 @@ function AIRBOSS:New(carriername, alias) ------------------- -- Debug trace. - if false then - self.Debug=true + if true then + --self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) @@ -1783,28 +1799,7 @@ function AIRBOSS:New(carriername, alias) -- Flare points every 3 seconds for 3 minutes. SCHEDULER:New(nil, flareme, {}, 1, 3, nil, 180) end - - -- Debug: - if false then - local text="Playing default sound files:" - for _name,_call in pairs(self.LSOCall) do - local call=_call --#AIRBOSS.RadioCall - - -- Debug text. - text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".", call.file, call.suffix, call.duration, tostring(call.loud), call.subtitle) - - -- Radio transmission to queue. - self:RadioTransmission(self.LSORadio, call, false, 10) - - -- Also play the loud version. - if call.loud then - self:RadioTransmission(self.LSORadio, call, true, 10) - end - end - self:I(self.lid..text) - end - - + ----------------------- --- FSM Transitions --- ----------------------- @@ -2561,6 +2556,76 @@ function AIRBOSS:SetUserSoundRadio() return self end +--- Test LSO radio sounds. +-- @param #AIRBOSS self +-- @param #number delay Delay in seconds be sound check starts. +-- @return #AIRBOSS self +function AIRBOSS:SoundCheckLSO(delay) + + if delay and delay>0 then + -- Delayed call. + SCHEDULER:New(nil, AIRBOSS.SoundCheckLSO, {self}, delay) + else + + + local text="Playing LSO sound files:" + + for _name,_call in pairs(self.LSOCall) do + local call=_call --#AIRBOSS.RadioCall + + -- Debug text. + text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".", call.file, call.suffix, call.duration, tostring(call.loud), call.subtitle) + + -- Radio transmission to queue. + self:RadioTransmission(self.LSORadio, call, false) + + -- Also play the loud version. + if call.loud then + self:RadioTransmission(self.LSORadio, call, true) + end + end + + -- Debug message. + self:I(self.lid..text) + + end +end + +--- Test Marshal radio sounds. +-- @param #AIRBOSS self +-- @param #number delay Delay in seconds be sound check starts. +-- @return #AIRBOSS self +function AIRBOSS:SoundCheckMarshal(delay) + + if delay and delay>0 then + -- Delayed call. + SCHEDULER:New(nil, AIRBOSS.SoundCheckMarshal, {self}, delay) + else + + + local text="Playing Marshal sound files:" + + for _name,_call in pairs(self.MarshalCall) do + local call=_call --#AIRBOSS.RadioCall + + -- Debug text. + text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".", call.file, call.suffix, call.duration, tostring(call.loud), call.subtitle) + + -- Radio transmission to queue. + self:RadioTransmission(self.MarshalRadio, call, false) + + -- Also play the loud version. + if call.loud then + self:RadioTransmission(self.MarshalRadio, call, true) + end + end + + -- Debug message. + self:I(self.lid..text) + + end +end + --- Set number of aircraft units, which can be in the landing pattern before the pattern is full. -- @param #AIRBOSS self -- @param #number nmax Max number. Default 4. Minimum is 1, maximum is 6. @@ -2962,7 +3027,7 @@ function AIRBOSS:_CheckAIStatus() if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then -- Paddles: Call the ball! - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, false, 0) + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, false, 0, nil, true) -- Pilot: "405, Hornet Ball, 3.2" -- TODO: Voice over. @@ -2972,8 +3037,8 @@ function AIRBOSS:_CheckAIStatus() -- Debug message. MESSAGE:New(string.format("%s, %s", element.onboard, text), 15, "DEBUG"):ToAllIf(self.Debug) - -- Paddles: Roger ball after 3 seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 6) + -- Paddles: Roger ball after 6 seconds. + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 6, nil, true) -- Flight element called the ball. element.ballcall=true @@ -3370,19 +3435,10 @@ function AIRBOSS:onafterRecoveryStart(From, Event, To, Case, Offset) -- Input or default value. Offset=Offset or self.defaultoffset - - -- Debug output. - local text=string.format("Starting aircraft recovery Case %d ops.", Case) - if Case>1 then - local radial=self:GetRadial(Case, true, true, true) - text=text..string.format(" Marshal radial %03d°.", radial) - end - MESSAGE:New(text, 20, self.alias):ToAllIf(self.Debug) - self:T(self.lid..text) - - -- Message to all players inside CCA. - self:MessageToMarshal(text, "AIRBOSS", "99") + -- Radio message: "99, starting aircraft recovery case X ops. (Marshal radial XYZ degrees)" + self:_MarshalCallRecoveryStart(Case) + -- Switch to case. self:RecoveryCase(Case, Offset) end @@ -3430,19 +3486,26 @@ function AIRBOSS:onafterRecoveryPause(From, Event, To, duration) self:T(self.lid..string.format("Pausing aircraft recovery.")) -- Message text - local text=string.format("aircraft recovery is paused until further notice.") + if duration then -- Auto resume. self:__RecoveryUnpause(duration) - -- Message text. + -- Time to resume. local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration) - text=string.format("aircraft recovery is paused and will be resumed at %s.", clock) + + -- Marshal call: "99, aircraft recovery paused and will be resume at XX:YY." + self:_MarshalCallRecoveryPausedResumedAt(clock) + else + + local text=string.format("aircraft recovery is paused until further notice.") + + -- Marshal call: "99, aircraft recovery paused until further notice." + self:_MarshalCallRecoveryPausedNotice() + end - -- Message to Marshal. - self:MessageToMarshal(text, "AIRBOSS", "99") end --- On after "RecoveryUnpause" event. Recovery of aircraft is resumed. @@ -3453,12 +3516,10 @@ end function AIRBOSS:onafterRecoveryUnpause(From, Event, To) -- Debug output. self:T(self.lid..string.format("Unpausing aircraft recovery.")) + + -- Resume recovery. + self:_MarshalCallRecoveryResume() - -- Message text. - local text=string.format("resuming aircraft recovery.") - - -- Message to Marshal. - self:MessageToMarshal(text, "AIRBOSS", "99") end @@ -3686,7 +3747,52 @@ end -- @param #AIRBOSS self function AIRBOSS:_InitVoiceOvers() + --------------- + -- LSO Radio -- + --------------- + + -- LSO Radio Calls. self.LSOCall={ + BOLTER={ + file="LSO-BolterBolter", + suffix="ogg", + loud=false, + subtitle="Bolter, Bolter", + duration=0.75, + subduration=5, + }, + CALLTHEBALL={ + file="LSO-CallTheBall", + suffix="ogg", + loud=false, + subtitle="Call the ball", + duration=0.6, + subduration=2, + }, + CHECK={ + file="LSO-Check", + suffix="ogg", + loud=false, + subtitle="Check", + duration=0.45, + subduration=2.5, + }, + CLEAREDTOLAND={ + file="LSO-ClearedToLand", + suffix="ogg", + loud=false, + subtitle="Cleared to land", + duration=1.0, + subduration=5, + }, + COMELEFT={ + file="LSO-ComeLeft", + suffix="ogg", + loud=true, + subtitle="Come left", + duration=0.60, + subduration=1, + }, RADIOCHECK={ file="LSO-RadioCheck", suffix="ogg", @@ -3703,14 +3809,6 @@ function AIRBOSS:_InitVoiceOvers() duration=0.80, subduration=1, }, - COMELEFT={ - file="LSO-ComeLeft", - suffix="ogg", - loud=true, - subtitle="Come left", - duration=0.60, - subduration=1, - }, HIGH={ file="LSO-High", suffix="ogg", @@ -3751,14 +3849,6 @@ function AIRBOSS:_InitVoiceOvers() duration=0.7, subduration=1, }, - CALLTHEBALL={ - file="LSO-CallTheBall", - suffix="ogg", - loud=false, - subtitle="Call the ball", - duration=0.6, - subduration=2, - }, ROGERBALL={ file="LSO-RogerBall", suffix="ogg", @@ -3774,14 +3864,6 @@ function AIRBOSS:_InitVoiceOvers() subtitle="Wave off", duration=0.6, subduration=5, - }, - BOLTER={ - file="LSO-BolterBolter", - suffix="ogg", - loud=false, - subtitle="Bolter, Bolter", - duration=0.75, - subduration=5, }, LONGINGROOVE={ file="LSO-LongInTheGroove", @@ -3839,22 +3921,6 @@ function AIRBOSS:_InitVoiceOvers() duration=2.0, subduration=5, }, - CLEAREDTOLAND={ - file="LSO-ClearedToLand", - suffix="ogg", - loud=false, - subtitle="Cleared to land", - duration=1.0, - subduration=5, - }, - CHECK={ - file="LSO-Check", - suffix="ogg", - loud=false, - subtitle="Check", - duration=0.45, - subduration=2.5, - }, STABILIZED={ file="LSO-Stabilized", suffix="ogg", @@ -3957,22 +4023,68 @@ function AIRBOSS:_InitVoiceOvers() }, } + ------------------- + -- MARSHAL Radio -- + ------------------- + + -- MARSHAL Radio Calls. self.MarshalCall={ - RADIOCHECK={ - file="MARSHAL-RadioCheck", + ALTIMETER={ + file="MARSHAL-Altimeter", suffix="ogg", loud=false, - subtitle="Radio check", - duration=1.1, + subtitle="", + duration=0.85, + }, + BRC={ + file="MARSHAL-BRC", + suffix="ogg", + loud=false, + subtitle="", + duration=0.80, + }, + CARRIERTURNTOHEADING={ + file="MARSHAL-CarrierTurnToHeading", + suffix="ogg", + loud=false, + subtitle="", + duration=1.75, subduration=5, }, - SAYNEEDLES={ - file="MARSHAL-SayNeedles", + CASE={ + file="MARSHAL-Case", suffix="ogg", loud=false, - subtitle="Say needles", - duration=0.9, - subduration=5, + subtitle="", + duration=0.40, + }, + CHARLIETIME={ + file="MARSHAL-CharlieTime", + suffix="ogg", + loud=false, + subtitle="", + duration=0.90, + }, + CLEAREDFORCASE={ + file="MARSHAL-ClearedForCase", + suffix="ogg", + loud=false, + subtitle="", + duration=1.25, + }, + DEGREES={ + file="MARSHAL-Degrees", + suffix="ogg", + loud=false, + subtitle="", + duration=0.60, + }, + EXPECTED={ + file="MARSHAL-Expected", + suffix="ogg", + loud=false, + subtitle="", + duration=0.55, }, FLYNEEDLES={ file="MARSHAL-FlyYourNeedles", @@ -3982,6 +4094,27 @@ function AIRBOSS:_InitVoiceOvers() duration=0.9, subduration=5, }, + HOLDATANGELS={ + file="MARSHAL-HoldAtAngels", + suffix="ogg", + loud=false, + subtitle="", + duration=1.10, + }, + MARSHALRADIAL={ + file="MARSHAL-MarshalRadial", + suffix="ogg", + loud=false, + subtitle="", + duration=1.10, + }, + NEWFB={ + file="MARSHAL-NewFB", + suffix="ogg", + loud=false, + subtitle="", + duration=1.35, + }, N0={ file="MARSHAL-N0", suffix="ogg", @@ -4052,69 +4185,91 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=0.40, --0.38 too short }, - CASE={ - file="MARSHAL-Case", + OPS={ + file="MARSHAL-Ops", suffix="ogg", loud=false, subtitle="", - duration=0.45, + duration=0.48, }, - EXPECTED={ - file="MARSHAL-Expected", - suffix="ogg", - loud=false, - subtitle="", - duration=0.60, - }, - BRC={ - file="MARSHAL-BRC", - suffix="ogg", - loud=false, - subtitle="", - duration=0.68, - }, - HOLDAT={ - file="MARSHAL-HoldAt", - suffix="ogg", - loud=false, - subtitle="", - duration=0.43, - }, - ANGELS={ - file="MARSHAL-Angels", - suffix="ogg", - loud=false, - subtitle="", - duration=0.68, - }, - EXPECTED={ - file="MARSHAL-Expected", - suffix="ogg", - loud=false, - subtitle="", - duration=0.72, - }, - ALTIMETER={ - file="MARSHAL-Altimeter", - suffix="ogg", - loud=false, - subtitle="", - duration=0.73, - }, POINT={ file="MARSHAL-Point", suffix="ogg", loud=false, subtitle="", - duration=0.42, + duration=0.33, }, + RADIOCHECK={ + file="MARSHAL-RadioCheck", + suffix="ogg", + loud=false, + subtitle="Radio check", + duration=1.20, + subduration=5, + }, + RECOVERY={ + file="MARSHAL-Recovery", + suffix="ogg", + loud=false, + subtitle="", + duration=0.70, + subduration=5, + }, + RECOVERYPAUSEDNOTICE={ + file="MARSHAL-RecoveryPausedNotice", + suffix="ogg", + loud=false, + subtitle="aircraft recovery paused until further notice", + duration=2.90, + subduration=5, + }, + RECOVERYPAUSEDRESUMEDAT={ + file="MARSHAL-RecoveryPausedResumed", + suffix="ogg", + loud=false, + subtitle="", + duration=3.40, + subduration=5, + }, REPORTSEEME={ file="MARSHAL-ReportSeeMe", suffix="ogg", loud=false, subtitle="", - duration=1.05, - }, + duration=0.95, + }, + RESUMERECOVERY={ + file="MARSHAL-ResumeRecovery", + suffix="ogg", + loud=false, + subtitle="resuming aircraft recovery", + duration=1.75, + subduraction=5, + }, + SAYNEEDLES={ + file="MARSHAL-SayNeedles", + suffix="ogg", + loud=false, + subtitle="Say needles", + duration=0.90, + subduration=5, + }, + STACKFULL={ + file="MARSHAL-StackFull", + suffix="ogg", + loud=false, + subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instuctions", + duration=6.35, + subduration=10, + }, + STARTINGRECOVERY={ + file="MARSHAL-StartingRecovery", + suffix="ogg", + loud=false, + subtitle="", + duration=2.65, + subduration=5, + }, CLICK={ file="AIRBOSS-RadioClick", suffix="ogg", @@ -4640,12 +4795,9 @@ function AIRBOSS:_ClearForLanding(flight) end - -- Inform all Marshal flights. - local text=string.format("you are cleared for Case %d recovery.", flight.case) - - -- Add a little delay because message that recovery window opened could come just before. - self:MessageToMarshal(text, "MARSHAL", flight.onboard, nil, false, 2) - + -- Cleared for Case X recovery. + self:_MarshalCallClearedForRecovery(flight.onboard, flight.case) + end --- Set player step. Any warning is erased and next step hint shown. @@ -4844,19 +4996,9 @@ function AIRBOSS:_WaitPlayer(playerData) -- Number of waiting flights local nwaiting=#self.Qwaiting - - -- Message text. - local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ") - if nwaiting==1 then - text=text..string.format("There is one flight ahead of you.") - elseif nwaiting>1 then - text=text..string.format("There are %d flights ahead of you.", nwaiting) - else - text=text..string.format("You are next in line.") - end - -- Send message. - self:MessageToMarshal(text, "AIRBOSS", playerData.onboard) + -- Radio message: Stack is full. + self:_MarshalCallStackFull(playerData.onboard, nwaiting) -- Add player flight to waiting queue. table.insert(self.Qwaiting, playerData) @@ -5471,7 +5613,8 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure()) -- Stack altitude. - local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack, flight.case)) + --local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack, flight.case)) + local alt=self:_GetMarshalAltitude(stack, flight.case) -- Current BRC. local brc=self:GetBRC() @@ -5487,13 +5630,6 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) -- Convert to clock string. local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) - -- Marshal message. - --local text=string.format("Case %d, expected BRC %03d°, hold at %d. Expected Charlie Time %s.\n", flight.case, brc, alt, tostring(Ccharlie)) - --text=text..string.format("Altimeter %.2f. Report see me.", P) - - -- Message to all players. - --self:MessageToMarshal(text, "MARSHAL", flight.onboard) - -- Combined marshal call. self:_MarshalCallArrived(flight.onboard, flight.case, brc, alt, Ccharlie, P) @@ -5846,23 +5982,7 @@ function AIRBOSS:_PrintQueue(queue, name) -- Airborne units. local _, nunits, nsec=self:_GetFlightUnits(flight, false) - -- XXX: Include player data. - --[[ - if not flight.ai then - local playerData=_flight --#AIRBOSS.PlayerData - e=playerData.name - c=playerData.difficulty - f=playerData.passes - g=playerData.step - j=playerData.warning - a=playerData.holding - b=playerData.landed - d=playerData.boltered - h=playerData.lig - i=playerData.patternwo - k=playerData.waveoff - end - ]] + -- Text. text=text..string.format("\n[%d] %s*%d (%s): lead=%s (%d/%d), onboard=%s, flag=%d, case=%d, time=%s, fuel=%d, ai=%s, holding=%s", i, flight.groupname, nunits, actype, lead, nsec, Nsec, onboard, stack, case, clock, fuel, ai, holding) if stack>0 then @@ -6011,7 +6131,7 @@ function AIRBOSS:_NewPlayer(unitname) -- Show step hints. if playerData.showhints==nil then - if playerData.difficulty~=AIRBOSS.Difficulty.HARD then + if playerData.difficulty==AIRBOSS.Difficulty.HARD then playerData.showhints=false else playerData.showhints=true @@ -7037,7 +7157,7 @@ function AIRBOSS:OnEventLand(EventData) if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Power "Idle". - self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1) + self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true) -- Next step debrief. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.DEBRIEF) @@ -7389,6 +7509,12 @@ function AIRBOSS:_Holding(playerData) -- Current stack. local stack=playerData.flag + -- Check for reported error. + if stack<=0 then + local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)", playerData.name, playerData.step, tostring(stack)) + self:E(self.lid..text) + end + --------------------------- -- Holding Pattern Check -- --------------------------- @@ -7401,6 +7527,13 @@ function AIRBOSS:_Holding(playerData) -- Get holding zone of player. local zoneHolding=self:_GetZoneHolding(playerData.case, stack) + + -- Nil check. + if zoneHolding==nil then + self:E(self.lid.."ERROR: zoneHolding is nil!") + self:E({playerData=playerData}) + return + end -- Check if player is in holding zone. local inholdingzone=unit:IsInZone(zoneHolding) @@ -7791,8 +7924,8 @@ function AIRBOSS:_DirtyUp(playerData) if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard) local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard) - self:RadioTransmission(self.MarshalRadio, callsay, false, 40) - self:RadioTransmission(self.MarshalRadio, callfly, false, 45) + self:RadioTransmission(self.MarshalRadio, callsay, false, 40, nil, true) + self:RadioTransmission(self.MarshalRadio, callfly, false, 45, nil, true) end -- TODO: Make Fly Bullseye call if no automatic ICLS is active. @@ -7826,7 +7959,7 @@ function AIRBOSS:_Bullseye(playerData) -- LSO expect spot 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75) + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true) end -- Next step: Groove Call the ball. @@ -7956,7 +8089,7 @@ function AIRBOSS:_CheckForLongDownwind(playerData) -- Sound output. self:RadioTransmission(self.LSORadio, self.LSOCall.LONGINGROOVE) - self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER) + self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER, nil, nil, nil, true) -- Debrief. self:_AddToDebrief(playerData, "Long in the groove - Pattern Waveoff!") @@ -7990,11 +8123,11 @@ function AIRBOSS:_Abeam(playerData) if self:_CheckLimits(X, Z, self.Abeam) then -- Paddles contact. - self:RadioTransmission(self.LSORadio, self.LSOCall.PADDLESCONTACT) + self:RadioTransmission(self.LSORadio, self.LSOCall.PADDLESCONTACT, nil, nil, nil, true) -- LSO expect spot 7.5 call if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, false, 5) + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, false, 5, nil, true) end -- Hint for player about altitude, AoA etc. @@ -8040,7 +8173,7 @@ function AIRBOSS:_Ninety(playerData) elseif relheading>90 and self:_CheckLimits(X, Z, self.Wake) then -- Message to player. self:MessageToPlayer(playerData, "You are already at the wake and have not passed the 90. Turn faster next time!", "LSO") - self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER) + self:RadioTransmission(self.LSORadio, self.LSOCall.DEPARTANDREENTER, nil, nil, nil, true) playerData.patternwo=true -- Debrief. self:_AddToDebrief(playerData, "Overshoot at wake - Pattern Waveoff!") @@ -8198,13 +8331,13 @@ function AIRBOSS:_Groove(playerData) if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX then -- LSO "Call the ball" call. - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL) + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) playerData.Tlso=timer.getTime() -- Pilot "405, Hornet Ball, 3.2". Output should come from pilot. -- LSO "Roger ball" call in three seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 3) + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 3, nil, true) -- Store data. playerData.groove.XX=groovedata @@ -8282,7 +8415,7 @@ function AIRBOSS:_Groove(playerData) if playerData.unit:IsInZone(ZoneALS) and stable then -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. - self:RadioTransmission(self.LSORadio, self.LSOCall.CLEAREDTOLAND) + self:RadioTransmission(self.LSORadio, self.LSOCall.CLEAREDTOLAND, nil, nil, nil, true) -- Next step: Level cross. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_LC) @@ -8310,7 +8443,7 @@ function AIRBOSS:_Groove(playerData) -- Radio Transmission "Cleared to land" once the aircraft is inside the zone. if playerData.unit:IsInZone(ZoneLS) and stable and playerData.warning==false then - self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED) + self:RadioTransmission(self.LSORadio, self.LSOCall.STABILIZED, nil, nil, nil, true) playerData.warning=true end @@ -8335,7 +8468,7 @@ function AIRBOSS:_Groove(playerData) self:T3(self.lid..string.format("Waveoff distance rho=%.1f m", rho)) -- LSO Wave off! - self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF) + self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true) playerData.Tlso=timer.getTime() -- Player was waved off! @@ -8621,7 +8754,7 @@ function AIRBOSS:_CheckFoulDeck(playerData) -- Foul deck + wave off radio message. self:RadioTransmission(self.LSORadio, self.LSOCall.FOULDECK, false, 1) - self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2) + self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, false, 1.2, nil, true) -- Player hint for flight students. if playerData.showhints then @@ -8804,7 +8937,7 @@ function AIRBOSS:_Trapped(playerData) -- Check if we passed all wires. if wire>4 and v>10 and not playerData.warning then -- Looks like we missed the wires ==> Bolter! - self:RadioTransmission(self.LSORadio, self.LSOCall.BOLTER) + self:RadioTransmission(self.LSORadio, self.LSOCall.BOLTER, nil, nil, nil, true) playerData.warning=true end @@ -9041,6 +9174,7 @@ function AIRBOSS:_GetZoneArcIn(case) local alpha=math.rad(self.holdingoffset) -- 14+x NM from carrier + -- TODO local x=14/math.cos(alpha) -- Distance = 14 NM @@ -9327,6 +9461,8 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Stack is <= 0 ==> no marshal zone. if stack<=0 then + self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!") + self:E({case=case, stack=stack}) return nil end @@ -10034,20 +10170,20 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) --TODO: introduce GSE enumerator values. if glideslopeError>self.gle.HIGH then --1.5 then -- "You're high!" - self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true) + self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true, nil, nil, true) advice=advice+self.LSOCall.HIGH.duration elseif glideslopeError>self.gle.High then --0.8 then -- "You're high." - self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, false) + self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, false, nil, nil, true) advice=advice+self.LSOCall.HIGH.duration elseif glideslopeErrorself.lue.RIGHT then --3 then -- "Right for lineup!" - self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true) + self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, true, nil, nil, true) advice=advice+self.LSOCall.RIGHTFORLINEUP.duration elseif lineupError>self.lue.Right then -- 1 then -- "Right for lineup." - self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false) + self:RadioTransmission(self.LSORadio, self.LSOCall.RIGHTFORLINEUP, false, nil, nil, true) advice=advice+self.LSOCall.RIGHTFORLINEUP.duration else -- "Good lineup." @@ -10086,12 +10222,12 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then if AOA>acaoa.SLOW then -- "Your're slow!" - self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, true) + self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, true, nil, nil, true) advice=advice+self.LSOCall.SLOW.duration --S=underline("SLO") elseif AOA>acaoa.Slow then -- "Your're slow." - self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, false) + self:RadioTransmission(self.LSORadio, self.LSOCall.SLOW, false, nil, nil, true) advice=advice+self.LSOCall.SLOW.duration --S="SLO" elseif AOA>acaoa.OnSpeedMax then @@ -10099,12 +10235,12 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) --S=little("SLO") elseif AOAtransmission.Tplay then + if time>=transmission.Tplay then -- Check if transmission is currently playing. if transmission.isplaying then @@ -12371,7 +12503,12 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) -- Transmission over. transmission.isplaying=false remove=i - --table.insert(remove, i) + + if transmission.radio.alias=="LSO" then + self.TQLSO=time + elseif transmission.radio.alias=="MARSHAL" then + self.TQMarshal=time + end else -- still playing @@ -12382,9 +12519,34 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) else -- not playing yet - -- Not playing ==> this will be next. - if next==nil then - next=transmission + local Tlast=nil + if transmission.interval then + if transmission.radio.alias=="LSO" then + Tlast=self.TQLSO + elseif transmission.radio.alias=="MARSHAL" then + Tlast=self.TQMarshal + end + end + + if transmission.interval==nil then + + -- Not playing ==> this will be next. + if next==nil then + next=transmission + end + + else + + if time-Tlast>=transmission.interval then + next=transmission + else + + end + end + + -- We got a transmission or one with an interval that is not due yet. No need for anything else. + if next or Tlast then + break end end @@ -12404,11 +12566,9 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) end -- Remove completed calls from queue. - --for _,idx in pairs(remove) do if remove then table.remove(radioqueue, remove) end - --end end @@ -12418,8 +12578,10 @@ end -- @param #AIRBOSS.RadioCall call Radio sound files and subtitles. -- @param #boolean loud If true, play loud sound file version. -- @param #number delay Delay in seconds, before the message is broadcasted. -function AIRBOSS:RadioTransmission(radio, call, loud, delay) - self:F2({radio=radio, call=call, loud=loud, delay=delay}) +-- @param #number interval Interval in seconds after the last sound has been played. +-- @param #boolean click If true, play radio click at the end. +function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click) + self:F2({radio=radio, call=call, loud=loud, delay=delay, interval=interval, click=click}) -- Nil check. if radio==nil or call==nil then @@ -12432,19 +12594,19 @@ function AIRBOSS:RadioTransmission(radio, call, loud, delay) transmission.radio=radio transmission.call=call transmission.Tplay=timer.getAbsTime()+(delay or 0) - transmission.prio=50 + 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) + self:_Number2Radio(radio, call.modexsender, delay, 0.3) end -- Play onboard number if receiver has one. if self:_IsOnboard(call.modexreceiver) then - self:_Number2Radio(radio, call.modexreceiver, delay) + self:_Number2Radio(radio, call.modexreceiver, delay, 0.3) end -- Add transmission to the right queue. @@ -12464,26 +12626,7 @@ function AIRBOSS:RadioTransmission(radio, call, loud, delay) end -- Append radio click sound at the end of the transmission. - if call~=self[caller].CLICK and - call~=self[caller].N0 and - call~=self[caller].N1 and - call~=self[caller].N2 and - call~=self[caller].N3 and - call~=self[caller].N4 and - call~=self[caller].N5 and - call~=self[caller].N6 and - call~=self[caller].N7 and - call~=self[caller].N8 and - call~=self[caller].N9 and - call~=self[caller].EXPECTED and - call~=self[caller].BRC and - call~=self[caller].ALTIMETER and - call~=self[caller].POINT and - call~=self[caller].HOLDAT and - call~=self[caller].CASE and - call~=self[caller].POINT and - call~=self[caller].ANGELS and - call~=self[caller].CHARLIETIME then + if click then self:RadioTransmission(radio, self[caller].CLICK, false, delay) end end @@ -12796,7 +12939,7 @@ function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, de local call=self:_NewRadioCall(self.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender) -- Dummy radio transmission to display subtitle only to those who tuned in. - self:RadioTransmission(self.LSORadio, call, false, delay) + self:RadioTransmission(self.LSORadio, call, false, delay, nil, true) end @@ -12815,7 +12958,7 @@ function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, de local call=self:_NewRadioCall(self.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender) -- Dummy radio transmission to display subtitle only to those who tuned in. - self:RadioTransmission(self.MarshalRadio, call, false, delay) + self:RadioTransmission(self.MarshalRadio, call, false, delay, nil, true) end @@ -12990,8 +13133,9 @@ end -- @param #AIRBOSS.Radio radio Radio used for transmission. -- @param #string number Number string, e.g. "032" or "183". -- @param #number delay Delay before transmission in seconds. +-- @param #number interval Interval between the next call. -- @return #number Duration of the call in seconds. -function AIRBOSS:_Number2Radio(radio, number, delay) +function AIRBOSS:_Number2Radio(radio, number, delay, interval) --- Split string into characters. local function _split(str) @@ -13028,17 +13172,197 @@ function AIRBOSS:_Number2Radio(radio, number, delay) -- Radio call. local call=self[Sender][N] --#AIRBOSS.RadioCall - -- Transmit. - self:RadioTransmission(radio, call, false, delay) + if interval and i==1 then + -- Transmit. + self:RadioTransmission(radio, call, false, delay, interval) + else + self:RadioTransmission(radio, call, false, delay) + end -- Add up duration of the number. - wait=wait+call.duration + wait=wait+call.duration end -- Return the total duration of the call. return wait end + +--- Inform everyone that recovery is paused and will resume at a certain time. +-- @param #AIRBOSS self +function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice() + + -- Create new call. Subtitle already set. + local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE, "AIRBOSS", nil, self.Tmessage, "99") + + -- 99, aircraft recovery is paused until further notice. + self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) + +end + +--- Inform everyone that recovery is paused and will resume at a certain time. +-- @param #AIRBOSS self +-- @param #string clock Time. +function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock) + + -- Get relevant part of clock. + local _clock=UTILS.Split(clock, "+") + local CT=UTILS.Split(_clock[1], ":") + + -- Subtitle. + local text=string.format("aircraft recovery is paused and will be resumed at %s.", clock) + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMEDAT, "AIRBOSS", text, self.Tmessage, "99") + + -- 99, aircraft recovery is paused and will resume at... + self:RadioTransmission(self.MarshalRadio, call) + + -- XY.. (hours) + self:_Number2Radio(self.MarshalRadio, CT[1]) + -- XY (minutes). + self:_Number2Radio(self.MarshalRadio, CT[2]) + + -- Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CLICK) +end + + +--- Inform flight that he is cleared for recovery. +-- @param #AIRBOSS self +-- @param #string modex Tail number. +-- @param #number case Recovery case. +function AIRBOSS:_MarshalCallClearedForRecovery(modex, case) + + -- Subtitle. + local text=string.format("you're cleared for Case %d recovery", case) + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORCASE, "MARSHAL", text, self.Tmessage, modex) + + -- Two second delay. + local delay=2 + + -- XYZ, you're cleared for case.. + self:RadioTransmission(self.MarshalRadio, call, nil, delay) + -- X.. + self:_Number2Radio(self.MarshalRadio, tostring(case), delay) + -- recovery. Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RECOVERY, nil, delay, nil, true) + +end + +--- Inform everyone that recovery is resumed after pause. +-- @param #AIRBOSS self +function AIRBOSS:_MarshalCallResumeRecovery() + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY, "AIRBOSS", nil, self.Tmessage, "99") + + -- 99, aircraft recovery resumed. Click! + self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) + +end + +--- Inform everyone about new final bearing. +-- @param #AIRBOSS self +-- @param #number FB Final Bearing in degrees. +function AIRBOSS:_MarshalCallNewFinalBearing(FB) + + -- Subtitle. + local text=string.format("new final bearing %03d°", FB) + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.NEWFB, "AIRBOSS", text, self.Tmessage, "99") + + -- 99, new final bearing.. + self:RadioTransmission(self.MarshalRadio, call) + -- XYZ.. + self:_Number2Radio(self.MarshalRadio, string.format("%03d", FB), nil, 0.2) + -- Degrees. Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + +end + +--- Compile a radio call when Marshal tells a flight the holding alitude. +-- @param #AIRBOSS self +-- @param #number hdg Heading in degrees. +function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) + + -- Subtitle. + local text=string.format("starting turn to heading %03d°", hdg) + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING, "AIRBOSS", text, self.Tmessage, "99") + + -- 99, turning to heading... + self:RadioTransmission(self.MarshalRadio, call) + -- XYZ.. + self:_Number2Radio(self.MarshalRadio, string.format("%03d", hdg), nil, 0.2) + -- Degrees. Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + +end + +--- Compile a radio call when Marshal tells a flight the holding alitude. +-- @param #AIRBOSS self +-- @param #string modex Tail number. +-- @param #number nwaiting Number of flights already waiting. +function AIRBOSS:_MarshalCallStackFull(modex, nwaiting) + + -- Subtitle. + local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ") + if nwaiting==1 then + text=text..string.format("There is one flight ahead of you.") + elseif nwaiting>1 then + text=text..string.format("There are %d flights ahead of you.", nwaiting) + else + text=text..string.format("You are next in line.") + end + + -- Create new call with full subtitle. + local call=self:_NewRadioCall(self.MarshalCall.STACKFULL, "AIRBOSS", text, self.Tmessage, modex) + + -- XYZ, Marshal stack is currently full. + self:RadioTransmission(self.MarshalRadio, call, nil, nil, nil, true) +end + +--- Compile a radio call when Marshal tells a flight the holding alitude. +-- @param #AIRBOSS self +function AIRBOSS:_MarshalCallRecoveryStart(case) + + -- Marshal radial. + local radial=self:GetRadial(case, true, true, true) + + -- Debug output. + local text=string.format("Starting aircraft recovery Case %d ops.", case) + if case>1 then + text=text..string.format(" Marshal radial %03d°.", radial) + end + self:T(self.lid..text) + + -- New call including the subtitle. + local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY, "AIRBOSS", text, self.Tmessage, "99") + + -- 99, Starting aircraft recovery case.. + self:RadioTransmission(self.MarshalRadio, call) + -- X.. + self:_Number2Radio(self.MarshalRadio,tostring(case), nil, 0.2) + -- ops. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.OPS) + + --Marshal Radial + if case>1 then + -- Marshal radial.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.MARSHALRADIAL) + -- XYZ.. + self:_Number2Radio(self.MarshalRadio, string.format("%03d", radial), nil, 0.2) + -- Degrees. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES, nil, nil, nil, true) + end + +end + --- Compile a radio call when Marshal tells a flight the holding alitude. -- @param #AIRBOSS self -- @param #string modex Tail number. @@ -13054,7 +13378,6 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) local angels=self:_GetAngels(altitude) local QFE=UTILS.Split(tostring(UTILS.Round(qfe,2)), ".") local clock=UTILS.Split(charlie, "+") - self:E({clock=clock}) local CT=UTILS.Split(clock[1], ":") -- Subtitle text. @@ -13063,58 +13386,48 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) -- Create new call to display complete subtitle. local casecall=self:_NewRadioCall(self.MarshalCall.CASE, "MARSHAL", text, self.Tmessage, modex) - - - local delay=0 - + -- Case.. - self:RadioTransmission(self.MarshalRadio, casecall, nil, delay) - -- X.. - self:_Number2Radio(self.MarshalRadio, tostring(case), nil, delay) + self:RadioTransmission(self.MarshalRadio, casecall) + -- X. + self:_Number2Radio(self.MarshalRadio, tostring(case)) - delay=delay+0.5 - -- expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) - -- BRC.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC, nil, delay) - -- XYZ.. - self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc), nil, delay) - - delay=delay+0.5 - -- hold at.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDAT, nil, delay) - delay=delay+0.1 - -- angels.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ANGELS, nil, delay) - -- X.. - self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, delay) - - delay=delay+0.5 -- Expected.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, delay) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5) + -- BRC.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.BRC) + -- XYZ... + self:_Number2Radio(self.MarshalRadio, string.format("%03d", brc)) + -- Degrees. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.DEGREES) + + + -- Hold at.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.HOLDATANGELS, nil, nil, 0.5) + -- X. + self:_Number2Radio(self.MarshalRadio, tostring(angels)) + + -- Expected.. + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.EXPECTED, nil, nil, 0.5) -- Charlie time.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME, nil, delay) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CHARLIETIME) -- XY.. (hours) - self:_Number2Radio(self.MarshalRadio, CT[1], nil, delay) - -- XY.. (minutes) - self:_Number2Radio(self.MarshalRadio, CT[2], nil, delay) + self:_Number2Radio(self.MarshalRadio, CT[1]) + -- XY (minutes). + self:_Number2Radio(self.MarshalRadio, CT[2]) - delay=delay+0.5 -- Altimeter.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, delay) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.ALTIMETER, nil, nil, 0.5) -- XY.. - self:_Number2Radio(self.MarshalRadio, QFE[1], nil, delay) + self:_Number2Radio(self.MarshalRadio, QFE[1]) -- Point.. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT, nil, delay) - -- XY.. - self:_Number2Radio(self.MarshalRadio, QFE[2], nil, delay) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.POINT) + -- XY. + self:_Number2Radio(self.MarshalRadio, QFE[2]) + + -- Report see me. Click! + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, nil, 0.5, true) - delay=delay+0.5 - -- Report see me. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.REPORTSEEME, nil, delay) - delay=delay+0.2 - -- Click! - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.CLICK, nil, delay) end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -14766,7 +15079,7 @@ function AIRBOSS:_LSORadioCheck(_unitName) local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then -- Broadcase LSO radio check message on LSO radio. - self:RadioTransmission(self.LSORadio, self.LSOCall.RADIOCHECK) + self:RadioTransmission(self.LSORadio, self.LSOCall.RADIOCHECK, nil, nil, nil, true) end end end @@ -14785,7 +15098,7 @@ function AIRBOSS:_MarshalRadioCheck(_unitName) local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then -- Broadcase Marshal radio check message on Marshal radio. - self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RADIOCHECK) + self:RadioTransmission(self.MarshalRadio, self.MarshalCall.RADIOCHECK, nil, nil, nil, true) end end end From 48c7254c8463d52cf8fe11f2eb75a5a15194039b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 Feb 2019 10:41:58 +0100 Subject: [PATCH 09/11] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 365 ++++++++++++++++++------ 1 file changed, 281 insertions(+), 84 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index b79a9c42a..eb14efcc6 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -399,6 +399,7 @@ -- * **Flight Student**: The player receives tips at certain stages of the pattern, e.g. if he is at the right altitude, speed, etc. -- * **Naval Aviator**: Less tips are show. Player should be familiar with the procedures and its aircraft parameters. -- * **TOPGUN Graduate**: Only very few information is provided to the player. This is for the pros. +-- * **Hints On/Off**: Toggle displaying hints. -- -- ### My Status -- @@ -822,13 +823,13 @@ -- -- ## Carrier Specific Voice Overs -- --- It is possible to use diffent sound files for different carriers. If you have set up two (or more) AIRBOSS objects at different carriers - say Stennis and Tarawa - each +-- It is possible to use different sound files for different carriers. If you have set up two (or more) AIRBOSS objects at different carriers - say Stennis and Tarawa - each -- carrier would use the files in the specified directory, e.g. -- --- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles Stenis/") +-- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles Stennis/") -- airbossTarawa:SetSoundfilesFolder("Airboss Soundfiles Tarawa/") -- --- ## The Radio Transmission Dilemma +-- ## The Radio Dilemma -- -- DCS offers two (actually three) ways to send radio messages. Each one has its advantages and disadvantages and it is important to understand the differences. -- @@ -1278,19 +1279,28 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N9 "Nine" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. +-- @field #AIRBOSS.RadioCall SKYHAWK "Skyhawk" sound. +-- @field #AIRBOSS.RadioCall HARRIER "Harrier" sound. +-- @field #AIRBOSS.RadioCall HAWKEYE "Hawkeye" sound. +-- @field #AIRBOSS.RadioCall TOMCAT "Tomcat" sound. +-- @field #AIRBOSS.RadioCall HORNET "Hornet" sound. +-- @field #AIRBOSS.RadioCall BALL "Ball" sound. --- Marshal radio calls. -- @type AIRBOSS.MarshalCalls +-- @field #AIRBOSS.RadioCall AFFIRMATIVE "Affirmative" call. -- @field #AIRBOSS.RadioCall ALTIMETER "Altimeter" call. -- @field #AIRBOSS.RadioCall BRC "BRC" call. -- @field #AIRBOSS.RadioCall CARRIERTURNTOHEADING "Turn to heading" call. -- @field #AIRBOSS.RadioCall CASE "Case" call. -- @field #AIRBOSS.RadioCall CHARLIETIME "Charlie Time" call. --- @field #AIRBOSS.RadioCall CLEAREDFORCASE "You're cleared for case" call. +-- @field #AIRBOSS.RadioCall CLEAREDFORRECOVERY "You're cleared for case" call. +-- @field #AIRBOSS.RadioCall DECKCLOSED "Deck closed" sound. -- @field #AIRBOSS.RadioCall DEGREES "Degrees" call. -- @field #AIRBOSS.RadioCall EXPECTED "Expected" call. -- @field #AIRBOSS.RadioCall FLYNEEDLES "Fly your needles" call. -- @field #AIRBOSS.RadioCall HOLDATANGELS "Hold at angels" call. +-- @field #AIRBOSS.RadioCall HOURS "Hours" sound. -- @field #AIRBOSS.RadioCall MARSHALRADIAL "Marshal radial" call. -- @field #AIRBOSS.RadioCall N0 "Zero" call. -- @field #AIRBOSS.RadioCall N1 "One" call. @@ -1302,14 +1312,16 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N7 "Seven" call. -- @field #AIRBOSS.RadioCall N8 "Eight" call. -- @field #AIRBOSS.RadioCall N9 "Nine" call. +-- @field #AIRBOSS.RadioCall NEGATIVE "Negative" sound. -- @field #AIRBOSS.RadioCall NEWFB "New final bearing" call. -- @field #AIRBOSS.RadioCall OBS "Obs" call. -- @field #AIRBOSS.RadioCall POINT "Point" call. -- @field #AIRBOSS.RadioCall RADIOCHECK "Radio check" call. -- @field #AIRBOSS.RadioCall RECOVERY "Recovery" call. +-- @field #AIRBOSS.RadioCall RECOVERYOPSSTOPPED "Recovery ops stopped" sound. -- @field #AIRBOSS.RadioCall RECOVERYPAUSEDNOTICE "Recovery paused until further notice" call. --- @field #AIRBOSS.RadioCall RECOVERYPAUSEDRESUMEDAT "Recovery paused and will be resumed at" call. --- @field #AIRBOSS.RadioCall RESUMERECOVERY "Recovery paused until further notice" call. +-- @field #AIRBOSS.RadioCall RECOVERYPAUSEDRESUMED "Recovery paused and will be resumed at" call. +-- @field #AIRBOSS.RadioCall RESUMERECOVERY "Resuming aircraft recovery" call. -- @field #AIRBOSS.RadioCall REPORTSEEME "Report see me" call. -- @field #AIRBOSS.RadioCall SAYNEEDLES "Say needles" call. -- @field #AIRBOSS.RadioCall STACKFULL "Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions" call. @@ -1318,7 +1330,6 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall NOISE Static noise sound. - --- Difficulty level. -- @type AIRBOSS.Difficulty -- @field #string EASY Flight Student. Shows tips and hints in important phases of the approach. @@ -1435,6 +1446,7 @@ AIRBOSS.Difficulty={ -- @field #boolean fouldeckwo If true, player was waved off because of a foul deck. -- @field #number Tlso Last time the LSO gave an advice. -- @field #number Tgroove Time in the groove in seconds. +-- @field #number TIG0 Time in groove start timer.getTime(). -- @field #number wire Wire caught by player when trapped. -- @field #AIRBOSS.GroovePos groove Data table at each position in the groove. Elements are of type @{#AIRBOSS.GrooveData}. -- @field #table points Points of passes until finally landed. @@ -2543,7 +2555,7 @@ end -- @param #string unitname Name of the unit. -- @return #AIRBOSS self function AIRBOSS:SetRadioRelayMarshal(unitname) - self.radiorelayMarshal=unitname + self.radiorelayMSH=unitname return self end @@ -3027,18 +3039,13 @@ function AIRBOSS:_CheckAIStatus() if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then -- Paddles: Call the ball! - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, false, 0, nil, true) + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) -- Pilot: "405, Hornet Ball, 3.2" - -- TODO: Voice over. - local text=string.format("%s Ball, %.1f.", self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) - self:MessageToPattern(text, element.onboard, "", 5) + self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) - -- Debug message. - MESSAGE:New(string.format("%s, %s", element.onboard, text), 15, "DEBUG"):ToAllIf(self.Debug) - - -- Paddles: Roger ball after 6 seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, false, 6, nil, true) + -- Paddles: Roger ball after 0.5 seconds. + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true) -- Flight element called the ball. element.ballcall=true @@ -3459,11 +3466,8 @@ function AIRBOSS:onafterRecoveryStop(From, Event, To) self:DeleteRecoveryWindow(self.recoverywindow) end - -- Message text. - local text=string.format("Case %d recovery ops are stopped.", self.case) - - -- Message to Marshal. - self:MessageToMarshal(text, "AIRBOSS", "99") + -- Recovery ops stopped message. + self:_MarshalCallRecoveryStopped(self.case) -- If carrier is currently heading into the wind, we resume the original route. if self.turnintowind then @@ -3846,7 +3850,7 @@ function AIRBOSS:_InitVoiceOvers() suffix="ogg", loud=true, subtitle="You're fast", - duration=0.7, + duration=0.70, subduration=1, }, ROGERBALL={ @@ -3854,7 +3858,7 @@ function AIRBOSS:_InitVoiceOvers() suffix="ogg", loud=false, subtitle="Roger ball", - duration=0.7, + duration=1.00, subduration=2, }, WAVEOFF={ @@ -4021,6 +4025,62 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=3.6, }, + SKYHAWK={ + file="AIRBOSS-Skyhawk", + suffix="ogg", + loud=false, + subtitle="", + duration=0.95, + subduration=5, + }, + HARRIER={ + file="AIRBOSS-Harrier", + suffix="ogg", + loud=false, + subtitle="", + duration=0.60, + subduration=5, + }, + HAWKEYE={ + file="AIRBOSS-Hawkeye", + suffix="ogg", + loud=false, + subtitle="", + duration=0.65, + subduration=5, + }, + TOMCAT={ + file="AIRBOSS-Tomcat", + suffix="ogg", + loud=false, + subtitle="", + duration=0.70, + subduration=5, + }, + HORNET={ + file="AIRBOSS-Hornet", + suffix="ogg", + loud=false, + subtitle="", + duration=0.60, + subduration=5, + }, + VIKING={ + file="AIRBOSS-Viking", + suffix="ogg", + loud=false, + subtitle="", + duration=0.65, + subduration=5, + }, + BALL={ + file="AIRBOSS-Ball", + suffix="ogg", + loud=false, + subtitle="", + duration=0.50, + subduration=5, + }, } ------------------- @@ -4029,6 +4089,13 @@ function AIRBOSS:_InitVoiceOvers() -- MARSHAL Radio Calls. self.MarshalCall={ + AFFIRMATIVE={ + file="MARSHAL-Affirmative", + suffix="ogg", + loud=false, + subtitle="", + duration=0.90, + }, ALTIMETER={ file="MARSHAL-Altimeter", suffix="ogg", @@ -4065,13 +4132,21 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=0.90, }, - CLEAREDFORCASE={ - file="MARSHAL-ClearedForCase", + CLEAREDFORRECOVERY={ + file="MARSHAL-ClearedForRecovery", suffix="ogg", loud=false, subtitle="", duration=1.25, }, + DECKCLOSED={ + file="MARSHAL-DeckClosed", + suffix="ogg", + loud=false, + subtitle="", + duration=1.10, + subduration=5, + }, DEGREES={ file="MARSHAL-Degrees", suffix="ogg", @@ -4101,6 +4176,14 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=1.10, }, + HOURS={ + file="MARSHAL-Hours", + suffix="ogg", + loud=false, + subtitle="", + duration=0.60, + subduration=5, + }, MARSHALRADIAL={ file="MARSHAL-MarshalRadial", suffix="ogg", @@ -4108,13 +4191,6 @@ function AIRBOSS:_InitVoiceOvers() subtitle="", duration=1.10, }, - NEWFB={ - file="MARSHAL-NewFB", - suffix="ogg", - loud=false, - subtitle="", - duration=1.35, - }, N0={ file="MARSHAL-N0", suffix="ogg", @@ -4183,7 +4259,22 @@ function AIRBOSS:_InitVoiceOvers() suffix="ogg", loud=false, subtitle="", - duration=0.40, --0.38 too short + duration=0.40, + }, + NEGATIVE={ + file="MARSHAL-Negative", + suffix="ogg", + loud=false, + subtitle="", + duration=0.80, + subduration=5, + }, + NEWFB={ + file="MARSHAL-NewFB", + suffix="ogg", + loud=false, + subtitle="", + duration=1.35, }, OPS={ file="MARSHAL-Ops", @@ -4215,6 +4306,14 @@ function AIRBOSS:_InitVoiceOvers() duration=0.70, subduration=5, }, + RECOVERYOPSSTOPPED={ + file="MARSHAL-RecoveryOpsStopped", + suffix="ogg", + loud=false, + subtitle="", + duration=1.65, + subduration=5, + }, RECOVERYPAUSEDNOTICE={ file="MARSHAL-RecoveryPausedNotice", suffix="ogg", @@ -4223,7 +4322,7 @@ function AIRBOSS:_InitVoiceOvers() duration=2.90, subduration=5, }, - RECOVERYPAUSEDRESUMEDAT={ + RECOVERYPAUSEDRESUMED={ file="MARSHAL-RecoveryPausedResumed", suffix="ogg", loud=false, @@ -4258,7 +4357,7 @@ function AIRBOSS:_InitVoiceOvers() file="MARSHAL-StackFull", suffix="ogg", loud=false, - subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instuctions", + subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions", duration=6.35, subduration=10, }, @@ -4392,7 +4491,7 @@ function AIRBOSS:_AoAUnit2Deg(playerData, aoaunits) -- A-4E -- ---------- - -- A-4E-C source code suggests a imple factor of 1/2 for conversion. + -- A-4E-C source code suggests a simple factor of 1/2 for conversion. degrees=0.5*aoaunits end @@ -5167,6 +5266,10 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Add group to marshal stack queue. self:_AddMarshalGroup(flight, nstack) end + + -- Explot unit. + local u1=flight.group:GetUnit(1) --Wrapper.Unit#UNIT + u1:Explode(500, 10) -- Recovery case. local case=flight.case @@ -6184,6 +6287,7 @@ function AIRBOSS:_InitPlayer(playerData, step) playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil + playerData.TIG0=nil playerData.wire=nil playerData.flag=-100 @@ -6952,6 +7056,8 @@ end -- @param #AIRBOSS.PlayerData playerData Player data. function AIRBOSS:_SetTimeInGroove(playerData) + --[[ + -- Get time in the groove. local gdataX0=playerData.groove.X0 --#AIRBOSS.GrooveData if gdataX0 then @@ -6959,6 +7065,14 @@ function AIRBOSS:_SetTimeInGroove(playerData) else playerData.Tgroove=9999 end + + ]] + + if playerData.TIG0 then + playerData.Tgroove=timer.getTime()-playerData.TIG0 + else + playerData.Tgroove=999 + end end @@ -6970,11 +7084,19 @@ function AIRBOSS:_GetTimeInGroove(playerData) local Tgroove=999 + --[[ + -- Get time in the groove. local gdataX0=playerData.groove.X0 --#AIRBOSS.GrooveData if gdataX0 then Tgroove=timer.getTime()-gdataX0.TGroove end + + ]] + + if playerData.TIG0 then + Tgroove=timer.getTime()-playerData.TIG0 + end return Tgroove end @@ -7289,7 +7411,7 @@ function AIRBOSS:OnEventTakeoff(EventData) end -- Check right airbase. - if airbasename==self.carrier:GetName() then + if airbasename==self.airbase:GetName() then if _unit and _playername then @@ -8239,7 +8361,6 @@ function AIRBOSS:_Final(playerData, nocheck) local inzone=playerData.unit:IsInZone(zone) -- Check if player is in +-4 deg cone and flying towards the runway. - --if math.abs(lineup)<=4 then if inzone then -- Hint for player about altitude, AoA etc. @@ -8262,6 +8383,9 @@ function AIRBOSS:_Final(playerData, nocheck) -- Groove data. playerData.groove.X0=groovedata + -- Set time stamp. Next call in 4 seconds. + playerData.Tlso=timer.getTime() + -- Next step: X start. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.GROOVE_XX) end @@ -8303,6 +8427,9 @@ function AIRBOSS:_Groove(playerData) -- Get AoA. local AoA=playerData.unit:GetAoA() + -- Get Angle of Bank. + local roll=playerData.unit:GetRoll() + -- Aircraft is behind the carrier. local astern=X=RAR and rho Date: Wed, 20 Feb 2019 09:41:16 +0100 Subject: [PATCH 10/11] AIRBOSS v0.9.8 --- Moose Development/Moose/Ops/Airboss.lua | 277 +++++++++++++++++------- 1 file changed, 194 insertions(+), 83 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index eb14efcc6..7de0646ec 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -842,7 +842,10 @@ -- a naval unit (i.e. the carrier). -- -- As a workaround, you can put an aircraft, e.g. a Helicopter on the deck of the carrier or another ship of the strike group. The aircraft should be set to --- uncontrolled and maybe even to immortal. With the @{#AIRBOSS.SetRadioUnit}(*unitname*) function you can use this unit as "radio repeater". +-- uncontrolled and maybe even to immortal. With the @{#AIRBOSS.SetRadioUnitName}(*unitname*) function you can use this unit as "radio repeater" for both Marshal and LSO +-- radio channels. However, this might lead to interruptions in the transmission if both channels transmit simultaniously. Therefore, it is better to assign a unit for +-- each radio via the @{#AIRBOSS.SetRadioRelayLSO}(unitname) and @{#AIRBOSS.SetRadioRelayMarshal}(unitname) functions. +-- -- Of course you can also use any other aircraft in the vicinity of the carrier, e.g. a rescue helo or a recovery tanker. It is just important that this -- unit is and stays close the the boat as the distance from the sender to the receiver is modeled in DCS. So messages from too far away might not reach the players. -- @@ -880,6 +883,27 @@ -- -- If no AI handling is desired, this can be turned off via the @{#AIRBOSS.SetHandleAIOFF} function. -- +-- In case only specifc AI groups shall be excluded, it can be done by adding the groups to a set, e.g. +-- +-- -- AI groups explicitly excluded from handling by the Airboss +-- local CarrierExcludeSet=SET_GROUP:New():FilterPrefixes("E-2D Wizard Group"):FilterStart() +-- AirbossStennis:SetExcludeAI(CarrierExcludeSet) +-- +-- Similarly, to the @{#AIRBOSS.SetExcludeAI} function, AI groups can be explicitly *included* via the @{#AIRBOSS.SetSquadronAI} function. If this is used, only the *included* groups are handled +-- by the AIRBOSS. +-- +-- ## Refueling +-- +-- AI groups in the marshal pattern can be send to refuel at the recovery tanker or if none is defined to the nearest divert airfield. This can be enabled by the @{AIRBOSS.SetRefuelAI}(*lowfuelthreshold*). +-- The parameter *lowfuelthreshold* is the threshold of fuel in percent. If the fuel drops below this value, the group will go for refueling. If refueling is performed at the recovery tanker, +-- the group will return to the marshal stack when done. The aircraft will not return from the divert airfield however. +-- +-- ## Respawning - DCS Landing Bug +-- +-- AI groups that enter the CCA are usually guided to Marshal stack. However, due to DCS limitations they might not obey the landing task if they have another airfield as departure and/or destination in +-- their mission task. Therefore, AI groups can be respawned when detected in the CCA. This should clear all other airfields and allow the aircraft to land on the carrier. +-- This is achieved by the @{AIRBOSS.SetRespawnAI}() function. +-- -- ## Known Issues -- -- Dealing with the DCS AI is a big challenge and there is only so much one can do. Please bear this in mind! @@ -1362,7 +1386,6 @@ AIRBOSS.Difficulty={ -- @field #number LUE Lineup error in degrees. -- @field #number Roll Roll angle. -- @field #number Rhdg Relative heading player to carrier. 0=parallel, +-90=perpendicular. --- @field #number TGroove Time stamp when pilot entered the groove. -- @field #string FlyThrough Fly through up "/" or fly through down "\\". --- LSO grade data. @@ -1416,6 +1439,7 @@ AIRBOSS.Difficulty={ -- @field #table elements Flight group elements. -- @field #number Tcharlie Charlie (abs) time in seconds. -- @field #string name Player name or name of first AI unit. +-- @field #boolean refueling Flight is refueling. --- Parameters of an element in a flight group. -- @type AIRBOSS.FlightElement @@ -1707,25 +1731,28 @@ function AIRBOSS:New(carriername, alias) ------------------- -- Debug trace. - if true then + if false then --self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) self.dTstatus=0.1 end - - --self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) -- Smoke zones. if self.Debug and false then local case=2 + self.holdingoffset=30 + self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red, 45) self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green, 45) + self:_GetZoneHolding(case, 1):SmokeZone(SMOKECOLOR.White, 45) + self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange, 45) + self:_GetZoneCommence(1):SmokeZone(SMOKECOLOR.Red, 45) end -- Carrier parameter debug tests. @@ -2280,6 +2307,15 @@ function AIRBOSS:SetRespawnAI(switch) return self end +--- Give AI aircraft the refueling task if a recovery tanker is present or send them to the nearest divert airfield. +-- @param #AIRBOSS self +-- @param #number lowfuelthreshold Low fuel threshold in percent. AI will go refueling if their fuel level drops below this value. Default 10 %. +-- @return #AIRBOSS self +function AIRBOSS:SetRefuelAI(lowfuelthreshold) + self.lowfuelAI=lowfuelthreshold or 10 + return self +end + --- Set folder where the airboss sound files are located **within you mission (miz) file**. -- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission. @@ -3002,11 +3038,16 @@ function AIRBOSS:_CheckAIStatus() -- Only AI! if flight.ai then - -- TODO: Check that aircraft can be refueled. + -- Get fuel amount in %. local fuel=flight.group:GetFuelMin()*100 - if self.lowfuelAI and fuel5. This would mean the player has not turned in correctly! @@ -9177,8 +9234,6 @@ function AIRBOSS:_GetZoneGroove(l, w) -- Stern coordinate. local st=self:_GetSternCoord() - -- TODO: optimize for Tarawa. shift port. - -- Zone points. local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90) local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi) @@ -9252,7 +9307,7 @@ end -- @return Core.Zone#ZONE_RADIUS Arc in zone. function AIRBOSS:_GetZoneArcOut(case) - -- Radius = 1 NM. + -- Radius = 1.25 NM. local radius=UTILS.NMToMeters(1.25) -- Distance = 12 NM @@ -9276,7 +9331,7 @@ end -- @return Core.Zone#ZONE_RADIUS Arc in zone. function AIRBOSS:_GetZoneArcIn(case) - -- Radius = 1 NM. + -- Radius = 1.25 NM. local radius=UTILS.NMToMeters(1.25) -- Zone depends on Case recovery. @@ -9286,8 +9341,7 @@ function AIRBOSS:_GetZoneArcIn(case) local alpha=math.rad(self.holdingoffset) -- 14+x NM from carrier - -- TODO - local x=14/math.cos(alpha) + local x=14 --/math.cos(alpha) -- Distance = 14 NM local distance=UTILS.NMToMeters(x) @@ -9342,8 +9396,8 @@ function AIRBOSS:_GetZoneCorridor(case) -- Angle between radial and offset in rad. local alpha=math.rad(self.holdingoffset) - -- Distance shift ahead of carrier to allow for some space to bolter - local dx=2 + -- Distance shift ahead of carrier to allow for some space to bolter. + local dx=5 -- Width of the box in NM. local w=2 @@ -9382,9 +9436,12 @@ function AIRBOSS:_GetZoneCorridor(case) self:T3(string.format("FF Q = %.1f NM", Q)) local c={} - c[1]=self:GetCoordinate():Translate(-UTILS.NMToMeters(dx), radial) --Carrier coordinate translated 2 NM in direction of travel to allow for bolter space. + local cv=self:GetCoordinate() + c[1]=cv:Translate(-UTILS.NMToMeters(dx), radial) --Carrier coordinate translated 2 NM in direction of travel to allow for bolter space. - if math.abs(self.holdingoffset)>1 then + if math.abs(self.holdingoffset)>=5 then + + --[[ -- Complicated case with an angle. c[2]=c[1]:Translate( UTILS.NMToMeters(w2), radial-90) -- 1 Right of carrier. c[3]=c[2]:Translate( UTILS.NMToMeters(d+dx+w2), radial) -- 13 "south" @ 1 right @@ -9394,10 +9451,22 @@ function AIRBOSS:_GetZoneCorridor(case) c[9]=c[1]:Translate( UTILS.NMToMeters(w2), radial+90) -- 1 left of carrier. c[8]=c[9]:Translate( UTILS.NMToMeters(d+dx-w2), radial) -- 1 left and 11 behind of carrier. c[7]=c[8]:Translate( UTILS.NMToMeters(P), radial+90) + ]] + + c[2]=c[1]:Translate( UTILS.NMToMeters(w2), radial-90) -- 1 Right of carrier, dx ahead. + c[3]=c[2]:Translate( UTILS.NMToMeters(d+dx+w2), radial) -- 13 "south" @ 1 right + + c[4]=cv:Translate(UTILS.NMToMeters(15), offset):Translate(UTILS.NMToMeters(1), offset-90) + c[5]=cv:Translate(UTILS.NMToMeters(31), offset):Translate(UTILS.NMToMeters(1), offset-90) + c[6]=cv:Translate(UTILS.NMToMeters(31), offset):Translate(UTILS.NMToMeters(1), offset+90) + c[7]=cv:Translate(UTILS.NMToMeters(13), offset):Translate(UTILS.NMToMeters(1), offset+90) + c[8]=cv:Translate(UTILS.NMToMeters(11), radial):Translate(UTILS.NMToMeters(1), radial+90) + c[9]=c[1]:Translate(UTILS.NMToMeters(w2), radial+90) + -- Translate these points a bit for a smoother turn. - c[4]=c[4]:Translate(UTILS.NMToMeters(2), offset) - c[7]=c[7]:Translate(UTILS.NMToMeters(2), offset) + --c[4]=c[4]:Translate(UTILS.NMToMeters(2), offset) + --c[7]=c[7]:Translate(UTILS.NMToMeters(2), offset) else -- Easy case of a long box. c[2]=c[1]:Translate( UTILS.NMToMeters(w2), radial-90) @@ -9958,15 +10027,25 @@ end --- Get wind direction and speed at carrier position. -- @param #AIRBOSS self -- @param #number alt Altitude ASL in meters. Default 50 m. +-- @param #boolean magnetic Direction including magnetic declination. -- @return #number Direction the wind is blowing **from** in degrees. -- @return #number Wind speed in m/s. -function AIRBOSS:GetWind(alt) +function AIRBOSS:GetWind(alt, magnetic) -- Current position of the carrier local cv=self:GetCoordinate() -- Wind direction and speed. By default at 50 meters ASL. local Wdir, Wspeed=cv:GetWind(alt or 50) + + -- Include magnetic declination. + if magnetic then + Wdir=Wdir-self.magvar + -- Adjust negative values. + if Wdir<0 then + Wdir=Wdir+360 + end + end return Wdir, Wspeed end @@ -10279,7 +10358,6 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) local advice=0 -- Glideslope high/low calls. - --TODO: introduce GSE enumerator values. if glideslopeError>self.gle.HIGH then --1.5 then -- "You're high!" self:RadioTransmission(self.LSORadio, self.LSOCall.HIGH, true, nil, nil, true) @@ -10293,7 +10371,6 @@ function AIRBOSS:_LSOadvice(playerData, glideslopeError, lineupError) self:RadioTransmission(self.LSORadio, self.LSOCall.POWER, true, nil, nil, true) advice=advice+self.LSOCall.POWER.duration elseif glideslopeError/F1 Help/F2 Skill Level local _skillPath=missionCommands.addSubMenuForGroup(gid, "Skill Level", _helpPath) -- F10/Airboss//F1 Help/F2 Skill Level/ - missionCommands.addCommandForGroup(gid, "Flight Student", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.EASY) -- F1 - missionCommands.addCommandForGroup(gid, "Naval Aviator", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.NORMAL) -- F2 - missionCommands.addCommandForGroup(gid, "TOPGUN Graduate", _skillPath, self._SetDifficulty, self, playername, AIRBOSS.Difficulty.HARD) -- F3 - missionCommands.addCommandForGroup(gid, "Hints On/Off", _skillPath, self._SetHintsOnOff, self, playername) -- F4 + missionCommands.addCommandForGroup(gid, "Flight Student", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.EASY) -- F1 + missionCommands.addCommandForGroup(gid, "Naval Aviator", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.NORMAL) -- F2 + missionCommands.addCommandForGroup(gid, "TOPGUN Graduate", _skillPath, self._SetDifficulty, self, _unitName, AIRBOSS.Difficulty.HARD) -- F3 + missionCommands.addCommandForGroup(gid, "Hints On/Off", _skillPath, self._SetHintsOnOff, self, _unitName) -- F4 -- F10/Airboss//F1 Help/ missionCommands.addCommandForGroup(gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "Attitude Monitor", _helpPath, self._DisplayAttitude, self, _unitName) -- F4 @@ -14187,7 +14288,7 @@ function AIRBOSS:_RequestRefueling(_unitName) elseif self.tanker:IsReturning() then -- Tanker is RTB. - text="Tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." + text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." end else @@ -14847,28 +14948,36 @@ end --- Set difficulty level. -- @param #AIRBOSS self --- @param #string playername Player name. +-- @param #string _unitname Name of the player unit. -- @param #AIRBOSS.Difficulty difficulty Difficulty level. -function AIRBOSS:_SetDifficulty(playername, difficulty) - self:T2({difficulty=difficulty, playername=playername}) +function AIRBOSS:_SetDifficulty(_unitname, difficulty) + self:T2({difficulty=difficulty, unitname=_unitname}) - local playerData=self.players[playername] --#AIRBOSS.PlayerData + -- Get player unit and player name. + local unit, playername = self:_GetPlayerUnitAndName(_unitname) - if playerData then - playerData.difficulty=difficulty - local text=string.format("your skill level is now: %s.", difficulty) - self:MessageToPlayer(playerData, text, nil, playerData.name, 5) - else - self:E(self.lid..string.format("ERROR: Could not get player data for player %s.", playername)) - end - - -- Set hints as well. - if playerData.difficulty==AIRBOSS.Difficulty.HARD then - playerData.showhints=false - else - playerData.showhints=true - end + -- Check if we have a player. + if unit and playername then + -- Player data. + local playerData=self.players[playername] --#AIRBOSS.PlayerData + + if playerData then + playerData.difficulty=difficulty + local text=string.format("your skill level is now: %s.", difficulty) + self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + else + self:E(self.lid..string.format("ERROR: Could not get player data for player %s.", playername)) + end + + -- Set hints as well. + if playerData.difficulty==AIRBOSS.Difficulty.HARD then + playerData.showhints=false + else + playerData.showhints=true + end + + end end --- Turn player's aircraft attitude display on or off. @@ -14887,13 +14996,15 @@ function AIRBOSS:_SetHintsOnOff(_unitname) local playerData=self.players[playername] --#AIRBOSS.PlayerData if playerData then + + -- Invert hints. playerData.showhints=not playerData.showhints -- Inform player. local text="" if playerData.showhints==true then text=string.format("hints are now ON.") - elseif playerData.showhints==false then + else text=string.format("hints are now OFF.") end self:MessageToPlayer(playerData, text, nil, playerData.name, 5) From 71001adc5e256fe49b90203819bffcd5aae88308 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 21 Feb 2019 09:55:57 +0100 Subject: [PATCH 11/11] AIRBOSS v0.9.8 --- Moose Development/Moose/Core/Point.lua | 24 +- Moose Development/Moose/Ops/Airboss.lua | 403 ++++++++++++++++-------- 2 files changed, 300 insertions(+), 127 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 2a5c8d0ee..a0cebabb4 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -210,6 +210,7 @@ do -- COORDINATE FromParkingAreaHot = "From Parking Area Hot", FromRunway = "From Runway", Landing = "Landing", + LandingReFuAr = "LandingReFuAr", } --- @field COORDINATE.WaypointType @@ -219,6 +220,7 @@ do -- COORDINATE TakeOff = "TakeOffParkingHot", TurningPoint = "Turning Point", Land = "Land", + LandingReFuAr = "LandingReFuAr", } @@ -1027,8 +1029,9 @@ do -- COORDINATE -- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points. -- @param #table DCSTasks A table of @{DCS#Task} items which are executed at the waypoint. -- @param #string description A text description of the waypoint, which will be shown on the F10 map. + -- @param #number timeReFuAr Time in minutes the aircraft stays at the airport for ReFueling and ReArming. -- @return #table The route point. - function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description ) + function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description, timeReFuAr ) self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) -- Set alttype or "RADIO" which is AGL. @@ -1055,7 +1058,7 @@ do -- COORDINATE -- Waypoint type. RoutePoint.type = Type or nil - RoutePoint.action = Action or nil + RoutePoint.action = Action or nil -- Speed. RoutePoint.speed = Speed/3.6 @@ -1084,6 +1087,11 @@ do -- COORDINATE --self:MarkToAll(string.format("Landing waypoint at airbase %s, ID=%d, Category=%d", airbase:GetName(), AirbaseID, AirbaseCategory )) end + -- Time in minutes to stay at the airbase before resuming route. + if Type==COORDINATE.WaypointType.LandingReFuAr then + RoutePoint.timeReFuAr=timeReFuAr or 10 + end + -- Waypoint tasks. RoutePoint.task = {} RoutePoint.task.id = "ComboTask" @@ -1173,7 +1181,17 @@ do -- COORDINATE return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, false, airbase, DCSTasks, description) end - + --- Build a Waypoint Air "LandingReFuAr". Mimics the aircraft ReFueling and ReArming. + -- @param #COORDINATE self + -- @param DCS#Speed Speed Airspeed in km/h. + -- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points. + -- @param #number timeReFuAr Time in minutes, the aircraft stays at the airbase. Default 10 min. + -- @param #table DCSTasks A table of @{DCS#Task} items which are executed at the waypoint. + -- @param #string description A text description of the waypoint, which will be shown on the F10 map. + -- @return #table The route point. + function COORDINATE:WaypointAirLandingReFu( Speed, airbase, timeReFuAr, DCSTasks, description ) + return self:WaypointAir(nil, COORDINATE.WaypointType.LandingReFuAr, COORDINATE.WaypointAction.LandingReFuAr, Speed, false, airbase, DCSTasks, description, timeReFuAr or 10) + end --- Build an ground type route point. diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7de0646ec..d706f01f0 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -82,6 +82,12 @@ -- * [DCS World - F/A-18 - Case I Carrier Recovery Tutorial](https://www.youtube.com/watch?v=lm-M3VUy-_I) -- * [DCS World - Case I Recovery Tutorial - Followup](https://www.youtube.com/watch?v=cW5R32Q6xC8) -- * [DCS World - CASE III Recovery Tutorial](https://www.youtube.com/watch?v=Lnfug5CVAvo) +-- +-- Wags DCS Hornet Videos: +-- +-- * [DCS: F/A-18C Hornet - Episode 9: CASE I Carrier Landing](https://www.youtube.com/watch?v=TuigBLhtAH8) +-- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) +-- * [DCS: F/A-18C Hornet Case I Carrier Landing Training Lesson Recording](https://www.youtube.com/watch?v=D33uM9q4xgA) -- -- ### Open Questions? -- @@ -363,6 +369,8 @@ -- -- All section members are supposed to follow. Player (or section lead) is removed from all other queues and automatically added to the landing pattern queue. -- +-- The mission designer can forbid this option my setting @{#AIRBOSS.SetEmergencyLandings}(false) in the script. +-- -- ### [Reset My Status] -- -- This will reset the current player status. If player is currently in a marshal stack, he will be removed from the marshal queue and the stack above will collapse. @@ -426,7 +434,7 @@ -- -- ### Subtitles On/Off -- --- This command toggles the display of radio message subtitles. By default subtitles are on. +-- This command toggles the display of radio message subtitles if no radio relay unit is used. By default subtitles are on. -- Note that subtitles for radio messages which do not have a complete voice over are always displayed. -- -- ## Kneeboard Menu @@ -892,12 +900,18 @@ -- Similarly, to the @{#AIRBOSS.SetExcludeAI} function, AI groups can be explicitly *included* via the @{#AIRBOSS.SetSquadronAI} function. If this is used, only the *included* groups are handled -- by the AIRBOSS. -- +-- ## Keep the Deck Clean +-- +-- Once the AI groups have landed on the carrier, they can be despawned automatically after they shut down their engines. This is achieved by the @{#AIRBOSS.SetDespawnOnEngineShutdown}() function. +-- -- ## Refueling -- -- AI groups in the marshal pattern can be send to refuel at the recovery tanker or if none is defined to the nearest divert airfield. This can be enabled by the @{AIRBOSS.SetRefuelAI}(*lowfuelthreshold*). -- The parameter *lowfuelthreshold* is the threshold of fuel in percent. If the fuel drops below this value, the group will go for refueling. If refueling is performed at the recovery tanker, -- the group will return to the marshal stack when done. The aircraft will not return from the divert airfield however. -- +-- Note that this feature is not enabled by default as there might be bugs in DCS that prevent a smooth refueling of the AI. Enable at your own risk. +-- -- ## Respawning - DCS Landing Bug -- -- AI groups that enter the CCA are usually guided to Marshal stack. However, due to DCS limitations they might not obey the landing task if they have another airfield as departure and/or destination in @@ -1268,29 +1282,20 @@ AIRBOSS.GroovePos={ --- LSO radio calls. -- @type AIRBOSS.LSOCalls --- @field #AIRBOSS.RadioCall RADIOCHECK "Paddles, radio check" call. --- @field #AIRBOSS.RadioCall RIGHTFORLINEUP "Right for line up" call. +-- @field #AIRBOSS.RadioCall BOLTER "Bolter, Bolter" call. +-- @field #AIRBOSS.RadioCall CALLTHEBALL "Call the Ball" call. +-- @field #AIRBOSS.RadioCall CHECK "CHECK" call. +-- @field #AIRBOSS.RadioCall CLEAREDTOLAND "Cleared to land" call. -- @field #AIRBOSS.RadioCall COMELEFT "Come left" call. --- @field #AIRBOSS.RadioCall HIGH "You're high" call. --- @field #AIRBOSS.RadioCall LOW "You're low" call. --- @field #AIRBOSS.RadioCall POWER "Power" call. --- @field #AIRBOSS.RadioCall FAST "You're fast" call. --- @field #AIRBOSS.RadioCall SLOW "You're slow" call. --- @field #AIRBOSS.RadioCall PADDLESCONTACT "Paddles, contact" call. --- @field #AIRBOSS.RadioCall CALLTHEBALL "Call the Ball" --- @field #AIRBOSS.RadioCall ROGERBALL "Roger ball" call. --- @field #AIRBOSS.RadioCall WAVEOFF "Wave off" call. --- @field #AIRBOSS.RadioCall BOLTER "Bolter, Bolter" call --- @field #AIRBOSS.RadioCall LONGINGROOVE "You're long in the groove" call. --- @field #AIRBOSS.RadioCall FOULDECK "Foul Deck" call. -- @field #AIRBOSS.RadioCall DEPARTANDREENTER "Depart and re-enter" call. --- @field #AIRBOSS.RadioCall WELCOMEABOARD "Welcome aboard" call. -- @field #AIRBOSS.RadioCall EXPECTHEAVYWAVEOFF "Expect heavy wavoff" call. -- @field #AIRBOSS.RadioCall EXPECTSPOT75 "Expect spot 7.5" call. --- @field #AIRBOSS.RadioCall CLEAREDTOLAND "Cleared to land" call. --- @field #AIRBOSS.RadioCall CHECK "CHECK" call. --- @field #AIRBOSS.RadioCall STABILIZED "Stabilized" call. +-- @field #AIRBOSS.RadioCall FAST "You're fast" call. +-- @field #AIRBOSS.RadioCall FOULDECK "Foul Deck" call. +-- @field #AIRBOSS.RadioCall HIGH "You're high" call. -- @field #AIRBOSS.RadioCall IDLE "Idle" call. +-- @field #AIRBOSS.RadioCall LONGINGROOVE "You're long in the groove" call. +-- @field #AIRBOSS.RadioCall LOW "You're low" call. -- @field #AIRBOSS.RadioCall N0 "Zero" call. -- @field #AIRBOSS.RadioCall N1 "One" call. -- @field #AIRBOSS.RadioCall N2 "Two" call. @@ -1301,14 +1306,25 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall N7 "Seven" call. -- @field #AIRBOSS.RadioCall N8 "Eight" call. -- @field #AIRBOSS.RadioCall N9 "Nine" call. +-- @field #AIRBOSS.RadioCall PADDLESCONTACT "Paddles, contact" call. +-- @field #AIRBOSS.RadioCall POWER "Power" call. +-- @field #AIRBOSS.RadioCall RADIOCHECK "Paddles, radio check" call. +-- @field #AIRBOSS.RadioCall RIGHTFORLINEUP "Right for line up" call. +-- @field #AIRBOSS.RadioCall ROGERBALL "Roger ball" call. +-- @field #AIRBOSS.RadioCall SLOW "You're slow" call. +-- @field #AIRBOSS.RadioCall STABILIZED "Stabilized" call. +-- @field #AIRBOSS.RadioCall WAVEOFF "Wave off" call. +-- @field #AIRBOSS.RadioCall WELCOMEABOARD "Welcome aboard" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. --- @field #AIRBOSS.RadioCall SKYHAWK "Skyhawk" sound. --- @field #AIRBOSS.RadioCall HARRIER "Harrier" sound. --- @field #AIRBOSS.RadioCall HAWKEYE "Hawkeye" sound. --- @field #AIRBOSS.RadioCall TOMCAT "Tomcat" sound. --- @field #AIRBOSS.RadioCall HORNET "Hornet" sound. --- @field #AIRBOSS.RadioCall BALL "Ball" sound. +-- @field #AIRBOSS.RadioCall BALL "Ball" call. +-- @field #AIRBOSS.RadioCall HARRIER "Harrier" call. +-- @field #AIRBOSS.RadioCall HAWKEYE "Hawkeye" call. +-- @field #AIRBOSS.RadioCall HORNET "Hornet" call. +-- @field #AIRBOSS.RadioCall SKYHAWK "Skyhawk" call. +-- @field #AIRBOSS.RadioCall TOMCAT "Tomcat" call. +-- @field #AIRBOSS.RadioCall VIKING "Viking" call. +-- @field #AIRBOSS.RadioCall SPINIT "Spin it" call. --- Marshal radio calls. -- @type AIRBOSS.MarshalCalls @@ -1347,12 +1363,15 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall RECOVERYPAUSEDRESUMED "Recovery paused and will be resumed at" call. -- @field #AIRBOSS.RadioCall RESUMERECOVERY "Resuming aircraft recovery" call. -- @field #AIRBOSS.RadioCall REPORTSEEME "Report see me" call. +-- @field #AIRBOSS.RadioCall ROGER "Roger" call. -- @field #AIRBOSS.RadioCall SAYNEEDLES "Say needles" call. -- @field #AIRBOSS.RadioCall STACKFULL "Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions" call. -- @field #AIRBOSS.RadioCall STARTINGRECOVERY "Starting aircraft recovery" call. -- @field #AIRBOSS.RadioCall CLICK Radio end transmission click sound. -- @field #AIRBOSS.RadioCall NOISE Static noise sound. - +-- @field #AIRBOSS.RadioCall BINGOFUEL "Bingo Fuel" call. +-- @field #AIRBOSS.RadioCall GASATDIVERT "Going for gas at the divert field" call. +-- @field #AIRBOSS.RadioCall GASATTANKER "Going for gas at the recovery tanker" call. --- Difficulty level. -- @type AIRBOSS.Difficulty @@ -2063,7 +2082,7 @@ function AIRBOSS:SetRecoveryCase(case) end --- Set holding pattern offset from final bearing for Case II/III recoveries. --- Usually, this is +-15 or +-30 degrees. You should not use and offet angle >= 90 degrees, because this will cause a devision by zero in some of the equations used to calculate the approach corridor. +-- Usually, this is +-15 or +-30 degrees. You should not use and offset angle >= 90 degrees, because this will cause a devision by zero in some of the equations used to calculate the approach corridor. -- So best stick to the defaults up to 30 degrees. -- @param #AIRBOSS self -- @param #number offset Offset angle in degrees. Default 0. @@ -2390,12 +2409,12 @@ end -- @param #number RIGHT -- @return #AIRBOSS self function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) - self.lue._max=_max or 0.5 - self.lue._min=_min or -0.5 - self.lue.Left=Left or -1.0 - self.lue.LEFT=LEFT or -3.0 - self.lue.Right=Right or 1.0 - self.lue.RIGHT=RIGHT or 3.0 + self.lue._max=_max or 0.5 + self.lue._min=_min or -0.5 + self.lue.Left=Left or -1.0 + self.lue.LEFT=LEFT or -3.0 + self.lue.Right=Right or 1.0 + self.lue.RIGHT=RIGHT or 3.0 return self end @@ -2512,10 +2531,10 @@ end --- Set beacon (TACAN/ICLS) time refresh interfal in case the beacons die. -- @param #AIRBOSS self --- @param #number interval Time interval in seconds. Default 300 sec = 5 min. +-- @param #number interval Time interval in seconds. Default 1200 sec = 20 min. -- @return #AIRBOSS self function AIRBOSS:SetBeaconRefresh(interval) - self.dTbeacon=interval or 300 + self.dTbeacon=interval or 20*60 return self end @@ -2865,11 +2884,13 @@ function AIRBOSS:_ActivateBeacons() -- Activate TACAN. if self.TACANon then + self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)", self.TACANchannel, self.TACANmode, self.TACANmorse)) self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, self.TACANmorse, true) end -- Activate ICLS. if self.ICLSon then + self:I(self.lid..string.format("Activating ICLS Channel %d (%s)", self.ICLSchannel, self.ICLSmorse)) self.beacon:ActivateICLS(self.ICLSchannel, self.ICLSmorse) end @@ -3045,9 +3066,15 @@ function AIRBOSS:_CheckAIStatus() local text=string.format("Group %s fuel=%.1f %%", flight.groupname, fuel) self:T3(self.lid..text) - -- Send AI for refueling at tanker or divert field. + -- Check if flight is low on fuel and not yet refueling. if self.lowfuelAI and fuelglMax then - local text=string.format("Wave off due to glideslope error %.2f > %.1f degrees!", glideslopeError, glMax) + local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!", glideslopeError, glMax) self:T(self.lid..string.format("%s: %s", playerData.name, text)) self:_AddToDebrief(playerData, text) waveoff=true elseif glideslopeErrorluAbs then - local text=string.format("Wave off due to line up error |%.1f| > %.1f degrees!", lineupError, luAbs) + local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!", lineupError, luAbs) self:T(self.lid..string.format("%s: %s", playerData.name, text)) self:_AddToDebrief(playerData, text) waveoff=true @@ -8820,12 +8911,12 @@ function AIRBOSS:_CheckWaveOff(glideslopeError, lineupError, AoA, playerData) local aoaac=self:_GetAircraftAoA(playerData) -- Check too slow or too fast. if AoAaoaac.SLOW then - local text=string.format("Wave off due to AoA %.1f > %.1f!", AoA, aoaac.SLOW) + local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!", AoA, aoaac.SLOW) self:T(self.lid..string.format("%s: %s", playerData.name, text)) self:_AddToDebrief(playerData, text) waveoff=true @@ -10638,36 +10729,34 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) end -- Glideslope/altitude. Good [-0.3, 0.4] asymmetric! - -- TODO: introduce enumerator with GSE values. local A=nil - if GSE>1.5 then + if GSE>self.gle.HIGH then A=underline("H") - elseif GSE>0.8 then + elseif GSE>self.gle.High then A="H" - elseif GSE>0.4 then + elseif GSE>self.gle._max then A=little("H") - elseif GSE<-0.9 then + elseif GSE3 then + if LUE>self.lue.RIGHT then D=underline("LUL") - elseif LUE>1 then + elseif LUE>self.lue.Right then D="LUL" - elseif LUE>0.5 then + elseif LUE>self.lue._max then D=little("LUL") - elseif LUE<-3 then + elseif LUE