AIRBOSS v0.4.3w

This commit is contained in:
funkyfranky 2018-12-04 11:37:07 +01:00
parent 74d97cc220
commit d66028a1b9
2 changed files with 355 additions and 72 deletions

View File

@ -10,8 +10,8 @@
-- * Different skill levels from on-the-fly tips for flight students to ziplip for pros.
-- * Define recovery time windows with individual recovery cases.
-- * Automatic TACAN and ICLS channel setting of carrier.
-- * Separate radio channels for LSO and Marshal/Airboss transmissions.
-- * Voice over support for LSO, Marshal and Airboss radio transmissions.
-- * Separate radio channels for LSO and Marshal transmissions.
-- * Voice over support for LSO and Marshal and Airboss radio transmissions.
-- * F10 radio menu including carrier info (weather, radio frequencies, TACAN/ICLS channels), player LSO grades, help function (player aircraft attitude, marking of pattern zones etc).
-- * Recovery tanker and refueling option via integration of @{#Ops.RecoveryTanker} class.
-- * Rescue helo option via @{#Ops.RescueHelo} class.
@ -20,7 +20,7 @@
-- **PLEASE NOTE** that his class is work in progress and in an **alpha** stage and very much work in progress.
--
-- At the moment, parameters are optimized for F/A-18C Hornet as aircraft and USS John C. Stennis as carrier.
-- The community A-4E-C mod is also supported in priciple but needs further tweaking of parameters suche as on speed AoA values.
-- The community A-4E mod is also supported in priciple but maybe needs further tweaking of parameters such as on speed AoA values.
--
-- Other aircraft and carriers **might** be possible in future but would need a different set of optimized parameters.
--
@ -43,11 +43,19 @@
-- @field #string alias Alias of the carrier.
-- @field Wrapper.Airbase#AIRBASE airbase Carrier airbase object.
-- @field Core.Radio#BEACON beacon Carrier beacon for TACAN and ICLS.
-- @field #boolean TACANon Automatic TACAN is activated.
-- @field #number TACANchannel TACAN channel.
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y".
-- @field #string TACANmorse TACAN morse code, e.g. "STN".
-- @field #boolean ICLSon Automatic ICLS is activated.
-- @field #number ICLSchannel ICLS channel.
-- @field #string ICLSmorse ICLS morse code, e.g. "STN".
-- @field Core.Radio#RADIO LSOradio Radio for LSO calls.
-- @field #number LSOfreq LSO radio frequency in MHz.
-- @field #string LSOmodulation LSO radio modulation "AM" or "FM".
-- @field Core.Radio#RADIO Carrierradio Radio for carrier calls.
-- @field #number Carrierfreq Marshal radio frequency in MHz.
-- @field #string Carriermodulation Marshal radio modulation "AM" or "FM".
-- @field Core.Scheduler#SCHEDULER radiotimer Radio queue scheduler.
-- @field Core.Zone#ZONE_UNIT zoneCCA Carrier controlled area (CCA), i.e. a zone of 50 NM radius around the carrier.
-- @field Core.Zone#ZONE_UNIT zoneCCZ Carrier controlled zone (CCZ), i.e. a zone of 5 NM radius around the carrier.
@ -125,13 +133,19 @@ AIRBOSS = {
alias = nil,
airbase = nil,
beacon = nil,
TACANon = nil,
TACANchannel = nil,
TACANmode = nil,
TACANmorse = nil,
ICLSon = nil,
ICLSchannel = nil,
ICLSmorse = nil,
LSOradio = nil,
LSOfreq = nil,
LSOmodulation = nil,
Carrierradio = nil,
Carrierfreq = nil,
Carriermodulation = nil,
radiotimer = nil,
zoneCCA = nil,
zoneCCZ = nil,
@ -265,18 +279,30 @@ AIRBOSS.PatternStep={
--- LSO radio calls.
-- @type AIRBOSS.LSOCall
-- @field #AIRBOSS.RadioSound RIGHTFORLINEUP "Right for line up!" call.
-- @field #AIRBOSS.RadioSound COMELEFT "Come left!" call.
-- @field #AIRBOSS.RadioSound HIGH "You're high!" call.
-- @field #AIRBOSS.RadioSound POWER "Power!" call.
-- @field #AIRBOSS.RadioSound RIGHTFORLINEUP "Right for line up" call.
-- @field #AIRBOSS.RadioSound COMELEFT "Come left" call.
-- @field #AIRBOSS.RadioSound HIGH "You're high" call.
-- @field #AIRBOSS.RadioSound LOW "You're low" call.
-- @field #AIRBOSS.RadioSound POWER "Power" call.
-- @field #AIRBOSS.RadioSound FAST "You're fast" call.
-- @field #AIRBOSS.RadioSound SLOW "You're slow" call.
-- @field #AIRBOSS.RadioSound PADDLESCONTACT "Paddles, contact" call.
-- @field #AIRBOSS.RadioSound CALLTHEBALL "Call the Ball"
-- @field #AIRBOSS.RadioSound ROGERBALL "Roger ball" call (actually from pilot).
-- @field #AIRBOSS.RadioSound WAVEOFF "Wafe off" call
-- @field #AIRBOSS.RadioSound BOLTER "Bolter, Bolter" call
-- @field #AIRBOSS.RadioSound LONGINGROOVE "You're long in the groove. Depart and re-enter." call.
-- @field #AIRBOSS.RadioSound DEPARTANDREENTER "Depart and re-enter" call.
-- @field #AIRBOSS.RadioSound N1 "One" call.
-- @field #AIRBOSS.RadioSound N2 "Two" call.
-- @field #AIRBOSS.RadioSound N3 "Three" call.
-- @field #AIRBOSS.RadioSound N4 "Four" call.
-- @field #AIRBOSS.RadioSound N5 "Five" call.
-- @field #AIRBOSS.RadioSound N6 "Six" call.
-- @field #AIRBOSS.RadioSound N7 "Seven" call.
-- @field #AIRBOSS.RadioSound N8 "Eight" call.
-- @field #AIRBOSS.RadioSound N9 "Nine" call.
-- @field #AIRBOSS.RadioSound N0 "Zero" call.
AIRBOSS.LSOCall={
RIGHTFORLINEUP={
file="LSO-RightForLineup",
@ -444,7 +470,91 @@ AIRBOSS.LSOCall={
subtitle="",
duration=0.4,
},
}
--- Marshal radio calls.
-- @type AIRBOSS.MarshalCall
-- @field #AIRBOSS.RadioSound N1 "One" call.
-- @field #AIRBOSS.RadioSound N2 "Two" call.
-- @field #AIRBOSS.RadioSound N3 "Three" call.
-- @field #AIRBOSS.RadioSound N4 "Four" call.
-- @field #AIRBOSS.RadioSound N5 "Five" call.
-- @field #AIRBOSS.RadioSound N6 "Six" call.
-- @field #AIRBOSS.RadioSound N7 "Seven" call.
-- @field #AIRBOSS.RadioSound N8 "Eight" call.
-- @field #AIRBOSS.RadioSound N9 "Nine" call.
-- @field #AIRBOSS.RadioSound N0 "Zero" call.
AIRBOSS.MarshalCall={
N1={
file="LSO-N1",
suffix="ogg",
louder=false,
subtitle="",
duration=0.3,
},
N2={
file="LSO-N2",
suffix="ogg",
louder=false,
subtitle="",
duration=0.3,
},
N3={
file="LSO-N3",
suffix="ogg",
louder=false,
subtitle="",
duration=0.4,
},
N4={
file="LSO-N4",
suffix="ogg",
louder=false,
subtitle="",
duration=0.4,
},
N5={
file="LSO-N5",
suffix="ogg",
louder=false,
subtitle="",
duration=0.4,
},
N6={
file="LSO-N6",
suffix="ogg",
louder=false,
subtitle="",
duration=0.6,
},
N7={
file="LSO-N7",
suffix="ogg",
louder=false,
subtitle="",
duration=0.6,
},
N8={
file="LSO-N8",
suffix="ogg",
louder=false,
subtitle="",
duration=0.4,
},
N9={
file="LSO-N9",
suffix="ogg",
louder=false,
subtitle="",
duration=0.5,
},
N0={
file="LSO-N0",
suffix="ogg",
louder=false,
subtitle="",
duration=0.4,
},
}
--- Difficulty level.
@ -567,7 +677,7 @@ AIRBOSS.MenuF10={}
--- Airboss class version.
-- @field #string version
AIRBOSS.version="0.4.3"
AIRBOSS.version="0.4.3w"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -905,6 +1015,12 @@ function AIRBOSS:AddRecoveryTime(starttime, stoptime, case)
return self
end
--- Disable automatic TACAN activation
-- @param #AIRBOSS self
-- @return #AIRBOSS self
function AIRBOSS:SetTACANoff()
self.TACANon=false
end
--- Set TACAN channel of carrier.
-- @param #AIRBOSS self
@ -912,21 +1028,33 @@ end
-- @param #string mode TACAN mode, i.e. "X" or "Y". Default "X".
-- @param #string morsecode Morse code identifier. Three letters, e.g. "STN".
-- @return #AIRBOSS self
function AIRBOSS:SetTACAN(channel, mode, moresecode)
function AIRBOSS:SetTACAN(channel, mode, morsecode)
self.TACANchannel=channel or 74
self.TACANmode=mode or "X"
self.TACANmorse=morsecode or "STN"
self.TACANon=true
return self
end
--- Disable automatic ICLS activation.
-- @param #AIRBOSS self
-- @return #AIRBOSS self
function AIRBOSS:SetICLSoff()
self.ICLSon=false
end
--- Set ICLS channel of carrier.
-- @param #AIRBOSS self
-- @param #number channel ICLS channel. Default 1.
-- @param #string morsecode Morse code identifier. Three letters, e.g. "STN".
-- @return #AIRBOSS self
function AIRBOSS:SetICLS(channel)
function AIRBOSS:SetICLS(channel, morsecode)
self.ICLSchannel=channel or 1
self.ICLSmorse=morsecode or "STN"
self.ICLSon=true
return self
end
@ -1036,13 +1164,13 @@ function AIRBOSS:onafterStart(From, Event, To)
self:I(self.lid..string.format("Theatre = %s", tostring(theatre)))
-- Activate TACAN.
if self.TACANchannel~=nil and self.TACANmode~=nil then
self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, "STN", true)
if self.TACANon then
self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, self.TACANmorse, true)
end
-- Activate ICLS.
if self.ICLSchannel then
self.beacon:ActivateICLS(self.ICLSchannel, "STN")
if self.ICLSon then
self.beacon:ActivateICLS(self.ICLSchannel, self.ICLSmorse)
end
-- Handle events.
@ -1399,6 +1527,7 @@ function AIRBOSS:_GetAircraftAoA(playerData)
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
-- Table with AoA values.
local aoa={} -- #AIRBOSS.AircraftAoA
if hornet then
@ -1761,11 +1890,16 @@ function AIRBOSS:_MarshalPlayer(playerData)
-- Set step to holding.
playerData.step=AIRBOSS.PatternStep.HOLDING
playerData.warning=nil
-- Holding switch to nil until player arrives in the holding zone.
playerData.holding=nil
-- Set same stack for all flights in section.
for _,_flight in pairs(playerData.section) do
local flight=_flight --#AIRBOSS.PlayerData
flight.step=AIRBOSS.PatternStep.HOLDING
flight.holding=nil
flight.flag:Set(mystack)
end
@ -1963,7 +2097,7 @@ function AIRBOSS:_CheckCollapseMarshalStack(flight)
-- TODO: Message to all players!
MESSAGE:New(text, 15, "MARSHAL"):ToAll()
--self:MessageToAll(text, "MARSHAL", flight)
self:MessageToAll(text, "MARSHAL", flight)
-- Hint for human players.
if not flight.ai then
@ -2827,7 +2961,7 @@ function AIRBOSS:_Holding(playerData)
local stack=playerData.flag:Get()
-- Pattern alitude.
local patternalt, c1, c2=self:_GetMarshalAltitude(stack)
local patternalt, c1, c2=self:_GetMarshalAltitude(stack, playerData.case)
-- Player altitude.
local playeralt=unit:GetAltitude()
@ -2912,13 +3046,13 @@ function AIRBOSS:_Commencing(playerData)
self:_InitPlayer(playerData)
-- Commence
local text=string.format("Commencing. (Case %d)", self.case)
local text=string.format("Commencing. (Case %d)", playerData.case)
-- Message to all players.
self:MessageToAll(text, playerData.onboard, "", 5)
-- Next step: depends on case recovery.
if self.case==1 then
if playerData.case==1 then
-- CASE I: Player has to fly to the initial which is 3 NM DME astern of the boat.
playerData.step=AIRBOSS.PatternStep.INITIAL
else
@ -2936,8 +3070,8 @@ function AIRBOSS:_Initial(playerData)
if playerData.unit:IsInZone(self.zoneInitial) then
-- Inform player.
local hint=string.format("Entering the pattern.")
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
local hint=string.format("Initial")
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint.."\nAim for 800 feet and 350 kts at the break entry."
end
@ -2966,6 +3100,12 @@ function AIRBOSS:_Platform(playerData)
self:MessageToPlayer(playerData, "You left the valid approach corridor!", "AIRBOSS")
playerData.warning=true
end
-- Back in zone.
if not invalid and playerData.warning then
self:MessageToPlayer(playerData, "You're back in the approach corridor. Now stay there!", "AIRBOSS")
playerData.warning=false
end
-- Check if we are inside the moving zone.
local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case))
@ -3027,9 +3167,8 @@ function AIRBOSS:_ArcInTurn(playerData)
end
-- Back in zone.
-- TODO: add this to the other checkpoints!
if not invalid and playerData.warning then
self:MessageToPlayer(playerData, "You are back in the approach corridor. Now stay there!", "AIRBOSS")
self:MessageToPlayer(playerData, "You're back in the approach corridor. Now stay there!", "AIRBOSS")
playerData.warning=false
end
@ -3054,8 +3193,7 @@ function AIRBOSS:_ArcInTurn(playerData)
end
-- Next step: Arc Out Turn.
playerData.step=AIRBOSS.PatternStep.ARCOUT
playerData.step=AIRBOSS.PatternStep.ARCOUT
playerData.warning=nil
end
end
@ -3076,6 +3214,13 @@ function AIRBOSS:_ArcOutTurn(playerData)
self:MessageToPlayer(playerData, "You left the valid approach corridor!", "AIRBOSS")
playerData.warning=true
end
-- Back in zone.
if not invalid and playerData.warning then
self:MessageToPlayer(playerData, "You're back in the approach corridor. Now stay there!", "AIRBOSS")
playerData.warning=false
end
-- Check if we are inside the moving zone.
local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case))
@ -3129,6 +3274,13 @@ function AIRBOSS:_DirtyUp(playerData)
self:MessageToPlayer(playerData, "You left the valid approach corridor!", "AIRBOSS")
playerData.warning=true
end
-- Back in zone.
if not invalid and playerData.warning then
self:MessageToPlayer(playerData, "You're back in the approach corridor. Now stay there!", "AIRBOSS")
playerData.warning=false
end
-- Check if we are inside the moving zone.
local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case))
@ -3156,8 +3308,7 @@ function AIRBOSS:_DirtyUp(playerData)
end
-- Next step: CASE III: Intercept glide slope and follow bullseye (ICLS).
playerData.step=AIRBOSS.PatternStep.BULLSEYE
playerData.step=AIRBOSS.PatternStep.BULLSEYE
playerData.warning=nil
end
end
@ -3178,6 +3329,13 @@ function AIRBOSS:_Bullseye(playerData)
self:MessageToPlayer(playerData, "You left the valid approach corridor!", "AIRBOSS")
playerData.warning=true
end
-- Back in zone.
if not invalid and playerData.warning then
self:MessageToPlayer(playerData, "You're back in the approach corridor. Now stay there!", "AIRBOSS")
playerData.warning=false
end
-- Check if we are inside the moving zone.
local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case))
@ -3205,8 +3363,7 @@ function AIRBOSS:_Bullseye(playerData)
end
-- Next step: Groove Call the ball.
playerData.step=AIRBOSS.PatternStep.GROOVE_XX
playerData.step=AIRBOSS.PatternStep.GROOVE_XX
playerData.warning=nil
end
end
@ -3249,6 +3406,7 @@ function AIRBOSS:_Upwind(playerData)
-- Next step: Early Break.
playerData.step=AIRBOSS.PatternStep.EARLYBREAK
playerData.warning=nil
end
end
@ -3298,6 +3456,7 @@ function AIRBOSS:_Break(playerData, part)
else
playerData.step=AIRBOSS.PatternStep.ABEAM
end
playerData.warning=nil
end
end
@ -3327,6 +3486,7 @@ function AIRBOSS:_CheckForLongDownwind(playerData)
-- Next step: Debriefing.
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
end
@ -3377,6 +3537,7 @@ function AIRBOSS:_Abeam(playerData)
-- Next step: ninety.
playerData.step=AIRBOSS.PatternStep.NINETY
playerData.warning=nil
end
end
@ -3423,6 +3584,7 @@ function AIRBOSS:_Ninety(playerData)
-- Next step: wake.
playerData.step=AIRBOSS.PatternStep.WAKE
playerData.warning=nil
elseif relheading>90 and self:_CheckLimits(X, Z, self.Wake) then
-- Message to player.
@ -3471,6 +3633,7 @@ function AIRBOSS:_Wake(playerData)
-- Next step: Final.
playerData.step=AIRBOSS.PatternStep.FINAL
playerData.warning=nil
end
end
@ -3536,6 +3699,7 @@ function AIRBOSS:_Final(playerData)
-- Next step: X start & call the ball.
playerData.step=AIRBOSS.PatternStep.GROOVE_XX
playerData.warning=nil
end
end
@ -3598,6 +3762,7 @@ function AIRBOSS:_Groove(playerData)
-- Next step: roger ball.
playerData.step=AIRBOSS.PatternStep.GROOVE_RB
playerData.warning=nil
elseif rho<=RRB and playerData.step==AIRBOSS.PatternStep.GROOVE_RB then
@ -3610,6 +3775,7 @@ function AIRBOSS:_Groove(playerData)
-- Next step: in the middle.
playerData.step=AIRBOSS.PatternStep.GROOVE_IM
playerData.warning=nil
elseif rho<=RIM and playerData.step==AIRBOSS.PatternStep.GROOVE_IM then
@ -3623,6 +3789,7 @@ function AIRBOSS:_Groove(playerData)
-- Next step: in close.
playerData.step=AIRBOSS.PatternStep.GROOVE_IC
playerData.warning=nil
elseif rho<=RIC and playerData.step==AIRBOSS.PatternStep.GROOVE_IC then
@ -3654,6 +3821,7 @@ function AIRBOSS:_Groove(playerData)
else
-- Next step: AR at the ramp.
playerData.step=AIRBOSS.PatternStep.GROOVE_AR
playerData.warning=nil
end
end
@ -3670,6 +3838,7 @@ function AIRBOSS:_Groove(playerData)
-- Next step: in the wires.
playerData.step=AIRBOSS.PatternStep.GROOVE_IW
playerData.warning=nil
end
-- Time since last LSO call.
@ -3700,7 +3869,7 @@ function AIRBOSS:_Groove(playerData)
-- Next step: debrief.
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
end
end
@ -3806,6 +3975,7 @@ function AIRBOSS:_Trapped(playerData, wire)
-- Next step: debriefing.
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -4039,6 +4209,7 @@ function AIRBOSS:_GetZoneCorridor(case)
-- Width of the box in NM.
local w=2
-- Length of the box in NM.
local l=10
@ -4050,33 +4221,38 @@ function AIRBOSS:_GetZoneCorridor(case)
-- Distance from ArcIn to ArcOut zone
local y=d*math.tan(alpha)
--local d2=math.cos(alpha)/2
-- Little extra bit along X.
local x=w/2*math.tan(alpha)
-- Get the extra bit we need to go back from the end to the arc turn in.
local C=w/math.cos(alpha)
local b=w*math.tan(alpha)
local a=C-b
local k=w/2/math.cos(alpha)
self:I(string.format("FF w = %.1f NM", w))
self:I(string.format("FF l = %.1f NM", l))
self:I(string.format("FF d = %.1f NM", d))
self:I(string.format("FF y = %.1f NM", y))
self:I(string.format("FF C = %.1f NM", C))
self:I(string.format("FF b = %.1f NM", b))
self:I(string.format("FF a = %.1f NM", a))
--self:I(string.format("FF C = %.1f NM", C))
--self:I(string.format("FF b = %.1f NM", b))
--self:I(string.format("FF a = %.1f NM", a))
self:I(string.format("FF x = %.1f NM", y))
self:I(string.format("FF k = %.1f NM", k))
-- TODO: Still not right!
-- TODO: Does this still work with alpha=0?e
local c={}
c[1]=self:GetCoordinate() -- Carrier coordinate
c[2]=c[1]:Translate( UTILS.NMToMeters(w/2), radial-90) -- 1 Right of carrier
c[3]=c[2]:Translate( UTILS.NMToMeters(d+w/2), radial) -- 13 "south" @ 1 right
c[4]=c[3]:Translate( UTILS.NMToMeters(y), radial+90) -- y left @ 13 south
c[4]=c[3]:Translate( UTILS.NMToMeters(y+x), radial+90) -- y+x left @ 13 south
c[5]=c[4]:Translate( UTILS.NMToMeters(l), offset) -- 10 NM to back wall (angled)
c[6]=c[5]:Translate( UTILS.NMToMeters(w), offset+90) -- Back wall (angled)
c[7]=c[6]:Translate(-UTILS.NMToMeters(l+a), offset) -- 10+a Back along X & Z
c[8]=c[7]:Translate( UTILS.NMToMeters(y), radial-90) -- Back along X
c[7]=c[6]:Translate(-UTILS.NMToMeters(l+k), offset) -- 10+a Back along X & Z
c[8]=c[7]:Translate( UTILS.NMToMeters(y-x), radial-90) -- y-x back along X
c[9]=c[1]:Translate( UTILS.NMToMeters(w/2), radial+90) -- 1 left of carrier
-- Create an array of a square!
@ -4090,7 +4266,7 @@ function AIRBOSS:_GetZoneCorridor(case)
-- Square zone length=10NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier.
-- So stay 0-5 NM (+1 NM error margin) port of carrier.
local zone=ZONE_POLYGON_BASE:New("CASE II/III Valid Zone", p)
local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor", p)
return zone
end
@ -4910,6 +5086,7 @@ function AIRBOSS:_AbortPattern(playerData, X, Z, posData, patternwo)
-- Next step debrief.
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
-- Message to player.
@ -4973,12 +5150,12 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt)
elseif _error<-lowscore then
hint=string.format("You're slightly low.")
else
hint=string.format("Good altitude. ")
hint=string.format("Good altitude.")
end
-- Extend or decrease depending on skill.
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal altitude is %d ft.", UTILS.MetersToFeet(altopt))
hint=hint..string.format(" Optimal altitude is %d ft.", UTILS.MetersToFeet(altopt))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
--hint=hint.."\n"
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
@ -4986,7 +5163,7 @@ function AIRBOSS:_AltitudeCheck(playerData, altopt)
end
-- Debrief text.
local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft optimum.", UTILS.MetersToFeet(altitude), _error, UTILS.MetersToFeet(altopt))
local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.", UTILS.MetersToFeet(altitude), _error, UTILS.MetersToFeet(altopt))
return hint, debrief
end
@ -5035,7 +5212,7 @@ function AIRBOSS:_DistanceCheck(playerData, optdist)
end
-- Debriefing text.
local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM optimum.",UTILS.MetersToNM(distance), _error, UTILS.MetersToNM(optdist))
local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance), _error, UTILS.MetersToNM(optdist))
return hint, debrief
end
@ -5084,7 +5261,7 @@ function AIRBOSS:_AoACheck(playerData, optaoa)
end
-- Debriefing text.
local debrief=string.format("AoA %.1f = %d%% deviation from %.1f optimum.", aoa, _error, optaoa)
local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.", aoa, _error, optaoa)
return hint, debrief
end
@ -5210,6 +5387,7 @@ function AIRBOSS:_Debrief(playerData)
-- Commencing again.
playerData.step=AIRBOSS.PatternStep.COMMENCING
playerData.warning=nil
else
-- Unit does not seem to be alive!
@ -5232,6 +5410,7 @@ function AIRBOSS:_Debrief(playerData)
-- Next step.
playerData.step=AIRBOSS.PatternStep.UNDEFINED
playerData.warning=nil
end
-- Increase number of passes.
@ -5457,6 +5636,40 @@ function AIRBOSS:GetCoordinate()
return self.carrier:GetCoordinate()
end
--- Get mission weather.
-- @param #AIRBOSS self
function AIRBOSS:_MissionWeather()
-- Weather data from mission file.
local weather=env.mission.weather
--[[
["clouds"] =
{
["thickness"] = 430,
["density"] = 7,
["base"] = 0,
["iprecptns"] = 1,
}, -- end of ["clouds"]
]]
local clouds=weather.clouds
--[[
["fog"] =
{
["thickness"] = 0,
["visibility"] = 25,
}, -- end of ["fog"]
]]
local fog=weather.fog
-- Visibilty distance in meters.
local vis=weather.visibility.distance
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- RADIO MESSAGE Functions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -5654,8 +5867,15 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration
self:I(self.lid..text)
-- TODO: Test! Need to make this better!.
-- TODO: This will fail with message to all since for each player the message will be played!
if receiver==playerData.onboard then
self:_Number2Sound(self.LSOradio, receiver, delay)
if sender then
if sender=="LSO" then
self:_Number2Sound(self.LSOradio, receiver, delay)
elseif sender=="MARSHALL" then
self:_Number2Sound(self.Carrierradio, receiver, delay)
end
end
end
if delay and delay>0 then
@ -5818,8 +6038,8 @@ function AIRBOSS:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(_gid, "Attitude Monitor ON/OFF", _helpPath, self._AttitudeMonitor, self, playername)
missionCommands.addCommandForGroup(_gid, "Smoke Marshal Zone", _helpPath, self._MarkMarshalZone, self, _unitName, false)
missionCommands.addCommandForGroup(_gid, "Flare Marshal Zone", _helpPath, self._MarkMarshalZone, self, _unitName, true)
missionCommands.addCommandForGroup(_gid, "Smoke R. Case Zones", _helpPath, self._MarkCase23Zones, self, _unitName, false)
missionCommands.addCommandForGroup(_gid, "Flare R. Case Zones", _helpPath, self._MarkCase23Zones, self, _unitName, true)
missionCommands.addCommandForGroup(_gid, "Smoke Pattern Zones", _helpPath, self._MarkCase23Zones, self, _unitName, false)
missionCommands.addCommandForGroup(_gid, "Flare Pattern Zones", _helpPath, self._MarkCase23Zones, self, _unitName, true)
missionCommands.addCommandForGroup(_gid, "[Reset My Status]", _helpPath, self._ResetPlayerStatus, self, _unitName)
-- F10/Airboss/<Carrier Name>/Kneeboard
@ -5904,20 +6124,20 @@ function AIRBOSS:_RequestMarshal(_unitName)
if self:_InQueue(self.Qmarshal, playerData.group) then
-- Flight group is already in marhal queue.
local text=string.format("%s, you are already in the Marshal queue. New marshal request denied!", playerData.name)
MESSAGE:New(text, 10, "MARSHAL"):ToClient(playerData.client)
local text=string.format("you are already in the Marshal queue. New marshal request denied!")
self:MessageToPlayer(playerData, text, "MARSHAL")
elseif self:_InQueue(self.Qpattern, playerData.group) then
-- Flight group is already in pattern queue.
local text=string.format("%s, you are already in the Pattern queue. Marshal request denied!", playerData.name)
MESSAGE:New(text, 10, "MARSHAL"):ToClient(playerData.client)
local text=string.format("you are already in the Pattern queue. Marshal request denied!")
self:MessageToPlayer(playerData, text, "MARSHAL")
elseif not _unit:InAir() then
-- Flight group is already in pattern queue.
local text=string.format("%s, you are not airborn. Marshal request denied!", playerData.name)
MESSAGE:New(text, 10, "MARSHAL"):ToClient(playerData.client)
local text=string.format("you are not airborn. Marshal request denied!")
self:MessageToPlayer(playerData, text, "MARSHAL")
else
@ -5929,8 +6149,8 @@ function AIRBOSS:_RequestMarshal(_unitName)
else
-- Flight group is not in CCA yet.
local text=string.format("%s, you are not inside CCA yet. Marshal request denied!", playerData.name)
MESSAGE:New(text, 10, "MARSHAL"):ToClient(playerData.client)
local text=string.format("you are not inside CCA yet. Marshal request denied!")
self:MessageToPlayer(playerData, text, "MARSHAL")
end
end
@ -5999,6 +6219,7 @@ function AIRBOSS:_RequestCommence(_unitName)
-- Set player step.
playerData.step=AIRBOSS.PatternStep.COMMENCING
playerData.warning=nil
-- Collaps marshal stack.
self:_CollapseMarshalStack(playerData, false)
@ -6051,11 +6272,14 @@ function AIRBOSS:_RequestRefueling(_unitName)
-- Tanker is up and running.
text=string.format("Proceed to tanker at angels %d.", angels)
--TODO: State TACAN channel of tanker if defined.
-- State TACAN channel of tanker if defined.
if self.tanker.TACANon then
text=text..string.format("\nTanker TACAN channel %d%s (%s)", self.tanker.TACANchannel, self.tanker.TACANmode, self.tanker.TACANmorse)
end
-- Tanker is currently refueling. Inform player.
if self.tanker:IsRefueling() then
text=text.."\n Tanker is currently refueling. You might have to queue up."
text=text.."\nTanker is currently refueling. You might have to queue up."
end
-- Collapse marshal stack if player is in queue.
@ -6345,14 +6569,13 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname)
local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS())
-- Tacan/ICLS.
-- TODO: adjust to option TACAN or ICLS disabled.
local tacan="unknown"
local icls="unknown"
if self.TACANchannel~=nil then
tacan=string.format("%d%s", self.TACANchannel, self.TACANmode)
if self.TACANon and self.TACANchannel~=nil then
tacan=string.format("%d%s (%s)", self.TACANchannel, self.TACANmode, self.TACANmorse)
end
if self.ICLSchannel~=nil then
icls=string.format("%d", self.ICLSchannel)
if self.ICLSon and self.ICLSchannel~=nil then
icls=string.format("%d (%s)", self.ICLSchannel, self.ICLSmorse)
end
-- Get groups, units in queues.

View File

@ -46,6 +46,7 @@
-- @field #boolean uncontrolledac If true, use and uncontrolled tanker group already present in the mission.
-- @field DCS#Vec3 orientation Orientation of the carrier. Used to monitor changes and update the pattern if heading changes significantly.
-- @field Core.Point#COORDINATE position Positon of carrier. Used to monitor if carrier significantly changed its position and then update the tanker pattern.
-- @field Core.Zone#ZONE_UNIT zoneUpdate Moving zone relative to carrier. Each time the tanker is in this zone, its pattern is updated.
-- @extends Core.Fsm#FSM
--- Recovery Tanker.
@ -204,11 +205,12 @@ RECOVERYTANKER = {
uncontrolledac = nil,
orientation = nil,
position = nil,
zoneUpdate = nil,
}
--- Class version.
-- @field #string version
RECOVERYTANKER.version="0.9.4w"
RECOVERYTANKER.version="0.9.5w"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -268,6 +270,9 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
self:SetPatternUpdateDistance()
self:SetPatternUpdateHeading()
self:SetPatternUpdateInterval()
-- Moving zone: Zone 1 NM astern the carrier with radius of 1.0 km.
self.zoneUpdate=ZONE_UNIT:New("Pattern Update Zone", self.carrier, 1*1000, {dx=-UTILS.NMToMeters(1), dy=0, relative_to_unit=true})
-----------------------
--- FSM Transitions ---
@ -707,6 +712,15 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel)
self:T(text)
-- Check if tanker flies through pattern update zone.
-- TODO: Check if this can be used to update the pattern without too much disruption.
-- Could be a problem when carrier changes course since the tanker might not fligh through the zone any more.
local inupdatezone=self.tanker:GetUnit(1):IsInZone(self.zoneUpdate)
if inupdatezone then
local clock=UTILS.SecondsToClock(timer.getAbsTime())
self:I(string.format("Recovery tanker is in pattern update zone! Time=%s", clock))
end
-- Check if tanker is running and not RTBing or refueling.
if self:IsRunning() then
@ -763,9 +777,9 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
end
-- Call status again in 1 minute.
-- Call status again in 30 seconds.
if not self:IsStopped() then
self:__Status(-60)
self:__Status(-30)
end
end
@ -910,10 +924,10 @@ function RECOVERYTANKER:_RefuelingStart(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() then
-- Unit receiving fuel.
local unit=EventData.IniUnit
local receiver=EventData.IniUnit
-- Get distance to tanker to check that unit is receiving fuel from this tanker.
local dist=unit:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
-- If distance > 100 meters, this should be another tanker.
if dist>100 then
@ -921,7 +935,7 @@ function RECOVERYTANKER:_RefuelingStart(EventData)
end
-- Info message.
self:T(string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), unit:GetName()))
self:T(string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), receiver:GetName()))
-- FMS state "Refueling".
self:RefuelStart(receiver)
@ -938,10 +952,10 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() then
-- Unit receiving fuel.
local unit=EventData.IniUnit
local receiver=EventData.IniUnit
-- Get distance to tanker to check that unit is receiving fuel from this tanker.
local dist=unit:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
-- If distance > 100 meters, this should be another tanker.
if dist>100 then
@ -949,10 +963,10 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
end
-- Info message.
self:T(string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), unit:GetName()))
self:T(string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), receiver:GetName()))
-- FSM state "Running".
self:RefuelStop(unit)
self:RefuelStop(receiver)
end
end
@ -1116,6 +1130,52 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
end
--- Calculate distances between carrier and tanker.
-- @param #AIRBOSS self
-- @return #number Distance [m] in the direction of the orientation of the carrier.
-- @return #number Distance [m] perpendicular to the orientation of the carrier.
-- @return #number Distance [m] to the carrier.
-- @return #number Angle [Deg] from carrier to plane. Phi=0 if the plane is directly behind the carrier, phi=90 if the plane is starboard, phi=180 if the plane is in front of the carrier.
function RECOVERYTANKER:_GetDistances()
-- Vector to carrier
local a=self.carrier:GetVec3()
-- Vector to player
local b=self.tanker:GetVec3()
-- Vector from carrier to player.
local c={x=b.x-a.x, y=0, z=b.z-a.z}
-- Orientation of carrier.
local x=self.carrier:GetOrientationX()
-- Projection of player pos on x component.
local dx=UTILS.VecDot(x,c)
-- Orientation of carrier.
local z=self.carrier:GetOrientationZ()
-- Projection of player pos on z component.
local dz=UTILS.VecDot(z,c)
-- Polar coordinates
local rho=math.sqrt(dx*dx+dz*dz)
local phi=math.deg(math.atan2(dz,dx))
if phi<0 then
phi=phi+360
end
-- phi=0 if the plane is directly behind the carrier, phi=180 if the plane is in front of the carrier
phi=phi-180
if phi<0 then
phi=phi+360
end
return dx,dz,rho,phi
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------