From 8c320f729ab5d2de497e440997b41d394f12cdc6 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 1 Nov 2018 16:20:49 +0100 Subject: [PATCH] CT v0.1.4w --- .../Moose/Functional/CarrierTrainer.lua | 240 ++++++++++++------ 1 file changed, 163 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Functional/CarrierTrainer.lua b/Moose Development/Moose/Functional/CarrierTrainer.lua index 8480f5948..04675e1d7 100644 --- a/Moose Development/Moose/Functional/CarrierTrainer.lua +++ b/Moose Development/Moose/Functional/CarrierTrainer.lua @@ -160,6 +160,13 @@ CARRIERTRAINER.Difficulty={ HARD="TOPGUN Graduate", } +--- Groove data. +-- @type CARRIERTRAINER.GrooveData +-- @field #number AoA Angle of Attack. +-- @field #number Alt Altitude in meters. +-- @field #number GSE Glide slope error in degrees. +-- @field #number LUE Lineup error in degrees. + --- Player data table holding all important parameters for each player. -- @type CARRIERTRAINER.PlayerData -- @field #number id Player ID. @@ -177,6 +184,7 @@ CARRIERTRAINER.Difficulty={ -- @field #boolean waveoff If true, player was waved off. -- @field #boolean calledball If true, player called the ball. -- @field #number Tlso Last time the LSO gave an advice. +-- @field #CARRIERTRAINER.GrooveData Groove data table with elemets of type @{#CARRIERTRAINER.GrooveData}. --- Checkpoint parameters triggering the next step in the pattern. -- @type CARRIERTRAINER.Checkpoint @@ -201,7 +209,7 @@ CARRIERTRAINER.MenuF10={} --- Carrier trainer class version. -- @field #string version -CARRIERTRAINER.version="0.1.4" +CARRIERTRAINER.version="0.1.4w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -503,7 +511,7 @@ function CARRIERTRAINER:_NewRound(playerData) if playerData.unit:IsInZone(self.registerZone) then local text="Cleared for approach." - self:_SendMessageToPlayer(text, 10,playerData) + self:_SendMessageToPlayer(text, 10, playerData) self:_InitNewRound(playerData) @@ -512,14 +520,21 @@ function CARRIERTRAINER:_NewRound(playerData) end end ---- Start landing pattern, when player enters the start zone. +--- Start pattern when player enters the start zone. -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. function CARRIERTRAINER:_Start(playerData) + -- Check if player is in start zone and about to enter the pattern. if playerData.unit:IsInZone(self.startZone) then - local hint = string.format("Entering the pattern, %s! Aim for 800 feet and 350 kts in the break entry.", playerData.callsign) + -- Inform player. + local hint = string.format("Entering the pattern.") + if playerData.difficulty==CARRIERTRAINER.Difficulty.EASY then + hint=hint.."Aim for 800 feet and 350 kts in the break entry." + end + + -- Send message. self:_SendMessageToPlayer(hint, 8, playerData) -- Next step: upwind. @@ -534,17 +549,18 @@ end function CARRIERTRAINER:_Upwind(playerData) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances(playerData.unit) -- Abort condition check. - if self:_CheckAbort(diffX,diffZ, self.Upwind) then - self:_AbortPattern(playerData, diffX, diffZ, self.Upwind) + if self:_CheckAbort(X, Z, self.Upwind) then + self:_AbortPattern(playerData, X, Z, self.Upwind) return end -- Check if we are in front of the boat (diffX > 0). - if self:_CheckLimits(diffX, diffZ, self.Upwind) then + if self:_CheckLimits(X, Z, self.Upwind) then + -- Get altitiude. local altitude=playerData.unit:GetAltitude() -- Get altitude. @@ -569,22 +585,22 @@ end function CARRIERTRAINER:_Break(playerData, part) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances(playerData.unit) -- Early or late break. local breakpoint = self.BreakEarly - if part == "late" then + if part=="late" then breakpoint = self.BreakLate end -- Check abort conditions. - if self:_CheckAbort(diffX, diffZ, breakpoint) then - self:_AbortPattern(playerData, diffX, diffZ, breakpoint) + if self:_CheckAbort(X, Z, breakpoint) then + self:_AbortPattern(playerData, X, Z, breakpoint) return end -- Check limits. - if self:_CheckLimits(diffX, diffZ, breakpoint) then + if self:_CheckLimits(X, Z, breakpoint) then -- Get current altitude. local altitude=playerData.unit:GetAltitude() @@ -596,14 +612,14 @@ function CARRIERTRAINER:_Break(playerData, part) self:_SendMessageToPlayer(hint, 10, playerData) -- Debrief - if part =="late" then + if part=="late" then self:_AddToSummary(playerData, "Late Break", hint) else - self:_AddToSummary(playerData, "Early Entry", hint) + self:_AddToSummary(playerData, "Early Break", hint) end -- Nest step: late break or abeam. - if (part == "early") then + if part=="early" then playerData.step = 4 else playerData.step = 5 @@ -617,7 +633,7 @@ end function CARRIERTRAINER:_CheckForLongDownwind(playerData) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances(playerData.unit) -- Get relative heading. local relhead=self:_GetRelativeHeading(playerData.unit) @@ -625,21 +641,21 @@ function CARRIERTRAINER:_CheckForLongDownwind(playerData) -- One NM from carrier is way too far. local limit = -UTILS.NMToMeters(1) - local text=string.format("Long groove check: diffX=%d, relhead=%.1f", diffX, relhead) + local text=string.format("Long groove check: X=%d, relhead=%.1f", X, relhead) self:T(text) --MESSAGE:New(text, 1):ToAllIf(self.Debug) -- Check we are not too far out w.r.t back of the boat. - if diffX 0) then - if self:_CheckAbort(diffX, diffZ, self.Ninety) then - self:_AbortPattern(playerData, diffX, diffZ, self.Ninety) + --if(Z < -3700 or X < -3700 or X > 0) then + if self:_CheckAbort(X, Z, self.Ninety) then + self:_AbortPattern(playerData, X, Z, self.Ninety) return end @@ -726,6 +741,7 @@ function CARRIERTRAINER:_Ninety(playerData) -- At the 90, i.e. 90 degrees between player heading and BRC of carrier. if relheading<=90 then + -- Get altitude and aoa. local alt=playerData.unit:GetAltitude() local aoa=playerData.unit:GetAoA() @@ -745,12 +761,12 @@ function CARRIERTRAINER:_Ninety(playerData) self:_AddToSummary(playerData, "At the 90", hintFull) -- Long downwind not an issue any more - playerData.longDownwindDone = true + playerData.longDownwindDone=true -- Next step: wake. playerData.step = 7 - elseif relheading>90 and self:_CheckLimits(diffX, diffZ, self.Wake) then + elseif relheading>90 and self:_CheckLimits(X, Z, self.Wake) then -- Message to player. self:_SendMessageToPlayer("You are already at the wake and have not passed the 90! Turn faster next time!", 10, playerData) end @@ -762,16 +778,16 @@ end function CARRIERTRAINER:_Wake(playerData) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) + local X, Z = self:_GetDistances(playerData.unit) -- Check abort conditions. - if self:_CheckAbort(diffX, diffZ, self.Wake) then - self:_AbortPattern(playerData, diffX, diffZ, self.Wake) + if self:_CheckAbort(X, Z, self.Wake) then + self:_AbortPattern(playerData, X, Z, self.Wake) return end -- Right behind the wake of the carrier dZ>0. - if self:_CheckLimits(diffX, diffZ, self.Wake) then + if self:_CheckLimits(X, Z, self.Wake) then local alt=playerData.unit:GetAltitude() local aoa=playerData.unit:GetAoA() @@ -802,11 +818,11 @@ end function CARRIERTRAINER:_Groove(playerData) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ, rho, phi = self:_GetDistances(playerData.unit) + local X, Z, rho, phi = self:_GetDistances(playerData.unit) -- In front of carrier or more than 4 km behind carrier. - if self:_CheckAbort(diffX, diffZ, self.Groove) then - self:_AbortPattern(playerData, diffX, diffZ, self.Groove) + if self:_CheckAbort(X, Z, self.Groove) then + self:_AbortPattern(playerData, X, Z, self.Groove) return end @@ -839,6 +855,14 @@ function CARRIERTRAINER:_Groove(playerData) -- Add to debrief. self:_AddToSummary(playerData, "Calling the ball", hintFull) + local groovedata={} --#CARRIERTRAINER.GrooveData + groovedata.Alt=alt + groovedata.AoA=aoa + groovedata.GSE=self:_Glideslope(playerData)-3.5 + groovedata.LUE=self:_Lineup(playerData)-10 + groovedata.Step=playerData.step + table.insert(playerData.Groove, groovedata) + -- Next step. playerData.step = 90 end @@ -849,10 +873,10 @@ end --- Call the ball, i.e. 3/4 NM distance between aircraft and carrier. -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. -function CARRIERTRAINER:_CallTheBall(playerData) +function CARRIERTRAINER:_CallTheBall(playerData) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ, rho, phi = self:_GetDistances(playerData.unit) + local X, Z, rho, phi = self:_GetDistances(playerData.unit) -- Player altitude local alt=playerData.unit:GetAltitude() @@ -861,8 +885,8 @@ function CARRIERTRAINER:_CallTheBall(playerData) local player=playerData.unit:GetGroup() -- Check abort conditions. - if self:_CheckAbort(diffX, diffZ, self.Trap) then - self:_AbortPattern(playerData, diffX, diffZ, self.Trap) + if self:_CheckAbort(X, Z, self.Trap) then + self:_AbortPattern(playerData, X, Z, self.Trap) return end @@ -872,20 +896,28 @@ function CARRIERTRAINER:_CallTheBall(playerData) local deckheight=22 local tailpos=-100 + -- Lineup with runway centerline. local lineup=self:_Lineup(playerData) local lineupError=lineup-rwyangle - -- Glideslope. Wee need to correct for the height of the deck. The ideal glide slope is 3.5 degrees. - local h=playerData.unit:GetAltitude()-deckheight - local x=math.abs(diffX-tailpos) - local glideslope=math.atan(h/x) - local glideslopeError=math.deg(glideslope)-3.5 + -- Glide slope. + local glideslope=self:_Glideslope(playerData) + local glideslopeError=glideslope-3.5 --TODO: maybe 3.0? -- Ranges in the groove. - local RRB=UTILS.NMToMeters(0.500) - local RIM=UTILS.NMToMeters(0.375) --0.75/2 - local RIC=UTILS.NMToMeters(0.100) - local RAR=UTILS.NMToMeters(0.050) + local RRB=UTILS.NMToMeters(0.500) -- Roger Ball! call. + local RIM=UTILS.NMToMeters(0.375) -- In the Middle 0.75/2. + local RIC=UTILS.NMToMeters(0.100) -- In Close. + local RAR=UTILS.NMToMeters(0.050) -- At the Ramp. + + -- Data + local groovedata={} --#CARRIERTRAINER.GrooveData + groovedata.Alt=alt + groovedata.AoA=playerData.unit:GetAoA() + groovedata.GSE=glideslopeError + groovedata.LUE=lineupError + groovedata.Step=playerData.step + table.insert(playerData.Groove, groovedata) if rho<=RRB and playerData.step==90 then @@ -926,7 +958,7 @@ function CARRIERTRAINER:_CallTheBall(playerData) self:_LSOcall(playerData, glideslopeError, lineupError) - elseif diffX > 150 then + elseif X > 150 then local wire = 0 local hint = "" @@ -950,6 +982,29 @@ function CARRIERTRAINER:_CallTheBall(playerData) end end +--- Get name of the current pattern step. +-- @param #CARRIERTRAINER self +-- @param #number step Step +-- @return #string Name of the step +function CARRIERTRAINER:_GS(step) + local gp + if step==9 then + gp="X" + elseif step==90 then + gp="IM" + elseif step==91 then + gp="IC" + elseif step==92 then + gp="AR" + elseif step==93 then + + elseif step==94 then + elseif step==95 then + elseif step==96 then + elseif step==97 then + end +end + --- Trapped? -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. @@ -957,7 +1012,7 @@ end function CARRIERTRAINER:_Trapped(playerData, pos) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ, rho, phi = self:_GetDistances(pos) + local X, Z, rho, phi = self:_GetDistances(pos) -- Get velocities. local playerVelocity = playerData.unit:GetVelocityKMH() @@ -970,13 +1025,13 @@ function CARRIERTRAINER:_Trapped(playerData, pos) local score = -10 -- Which wire - if(diffX < -14) then + if X < -14 then wire = 1 score = -15 - elseif(diffX < -3) then + elseif X < -3 then wire = 2 score = 10 - elseif (diffX < 10) then + elseif X < 10 then wire = 3 score = 20 else @@ -987,7 +1042,7 @@ function CARRIERTRAINER:_Trapped(playerData, pos) local text=string.format("TRAPPED! %d-wire.", wire) self:_SendMessageToPlayer(text, 30, playerData) - local text2=string.format("Distance %.1f meters resulted in a %d-wire estimate.", diffX, wire) + local text2=string.format("Distance %.1f meters resulted in a %d-wire estimate.", X, wire) MESSAGE:New(text,30):ToAllIf(self.Debug) env.info(text2) @@ -1009,7 +1064,7 @@ function CARRIERTRAINER:_LSOcall(playerData, glideslopeError, lineupError) local text="" -- Player group. - local player=playerData.unit:GetGroup() + local player=playerData.unit:GetGroup() -- Glideslope high/low calls. if glideslopeError>1 then @@ -1054,6 +1109,23 @@ function CARRIERTRAINER:_LSOcall(playerData, glideslopeError, lineupError) text=text.."Good lineup." end + -- Get AoA. + local aoa=playerData.unit:GetAoA() + + if aoa>=9.3 then + text="Your're slow!" + elseif aoa>=8.8 and aoa<9.3 then + text="Your're slightly slow." + elseif aoa>=7.4 and aoa<8.8 then + text="You're on speed." + elseif aoa>=6.9 and aoa<7.4 then + text="You're slightly fast." + elseif aoa>=0 and aoa<6.9 then + text="You're fast!" + else + text="Unknown AoA state." + end + text=text..string.format(" Lineup Error = %.1f %%", lineupError) -- LSO Message to player. @@ -1064,11 +1136,32 @@ function CARRIERTRAINER:_LSOcall(playerData, glideslopeError, lineupError) end +--- Get glide slope of aircraft. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data table. +-- @return #number Glide slope angle in degrees measured from the +function CARRIERTRAINER:_Glideslope(playerData) + + -- Carrier parameters. + local deckheight=22 + local tailpos=-100 + + -- 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) + + -- Glideslope. Wee need to correct for the height of the deck. The ideal glide slope is 3.5 degrees. + local h=playerData.unit:GetAltitude()-deckheight + local x=math.abs(X-tailpos) + local glideslope=math.atan(h/x) + + return math.deg(glideslope) +end + --- Get line up of player wrt to carrier runway. -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. -- @return #number Line up with runway heading in degrees. 0 degrees = perfect line up. +1 too far left. -1 too far right. --- @return #number range from Carrier tail to player aircraft in meters. +-- @return #number Distance from carrier tail to player aircraft in meters. function CARRIERTRAINER:_Lineup(playerData) -- Runway is at an angle of -10 degrees wrt to carrier X direction. @@ -1078,18 +1171,14 @@ function CARRIERTRAINER:_Lineup(playerData) local tailpos=-100 -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ, rho, phi = self:_GetDistances(playerData.unit) + local X, Z, rho, phi = self:_GetDistances(playerData.unit) -- Position at the end of the deck. From there we calculate the angle. -- TODO: Check exact number and make carrier dependent. - local b={} - b.x=tailpos - b.z=0 + local b={x=tailpos, z=0} -- Position of the aircraft wrt carrier coordinates. - local a={} - a.x=diffX - a.z=diffZ + local a={x=X, z=Z} --a.x=-200 --a.y= 0 @@ -1098,10 +1187,7 @@ function CARRIERTRAINER:_Lineup(playerData) --print(a.z) -- Vector from plane to ref point on boad. - local c={} - c.x=b.x-a.x - c.y=0 - c.z=b.z-a.z + local c={x=b.x-a.x, y=0, z=b.z-a.z} -- Current line up and error wrt to final heading of the runway. local lineup=math.atan2(c.z, c.x) @@ -1732,7 +1818,7 @@ function CARRIERTRAINER:_AoACheck(playerData, checkpoint, aoa) hint = "You're slightly fast." else --On speed score = 0 - hint = "You're on speed!" + hint = "You're on speed." end hint=hint..string.format(" AoA %.1f = %d %% deviation from %.1f target AoA.", aoa, _error, checkpoint.AoA) @@ -1754,7 +1840,7 @@ function CARRIERTRAINER:_SendMessageToPlayer(message, duration, playerData, clea if playerData.client then --MESSAGE:New(string.format("%s, %s, ", self.alias, playerData.callsign)..message, duration, nil, clear):ToClient(playerData.client) end - MESSAGE:New(string.format("%s, %s, ", self.alias, playerData.callsign)..message, duration, nil, clear):ToAll() + MESSAGE:New(string.format("%s, %s, %s", self.alias, playerData.callsign, message), duration, nil, clear):ToAll() end --- Display final score.