diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7c11390ce..416914a8d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -436,6 +436,11 @@ -- 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. -- +-- ### Trapsheet On/Off +-- +-- Each player can activated or deactivate the recording of his flight data (AoA, glideslope, lineup, etc.) during his landing approaches. +-- Note that this feature also has to be enabled by the mission designer. +-- -- ## Kneeboard Menu -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_MenuKneeboard.png) @@ -589,7 +594,7 @@ -- Furthermore, we have the cases: -- -- * 2.5 Points **B**: "Bolder", when the player landed but did not catch a wire. --- * 2.0 Points **PWO**: "Pattern Wave-Off", when pilot was far away from where he should be in the pattern. +-- * 2.0 Points **WOP**: "Pattern Wave-Off", when pilot was far away from where he should be in the pattern. -- * 2.0 Points **OWO**: "Own Wave-Off**, when pilot flies past the deck without touching it. -- * 1.0 Points **WO**: "Technique Wave-Off": Player got waved off in the final parts of the groove. -- * 1.0 Points **LIG**: "Long In the Groove", when pilot extents the downwind leg too far and screws up the timing for the following aircraft. @@ -819,13 +824,31 @@ -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_TrapSheetLUE.png) -- --- The graph displayes the lineup error as a function of the distance to the carrier. +-- The graph displays the lineup error (LUE) as a function of the distance to the carrier. +-- +-- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. +-- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). +-- In the middle (IM), he performs good corrections and smoothly reduces the lineup error. +-- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. -- -- ## Glideslope Error -- --- ![Banner Image](..\Presentations\AIRBOSS\Airboss_TrapSheetGSE.png) +-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_TrapSheetGLE.png) -- --- -- The graph displayes the glideslope error as a function of the distance to the carrier. +-- The graph displays the glideslope error (GSE) as a function of the distance to the carrier. +-- +-- In this case the pilot already enters the groove (X) below the optimal glideslope. He is not able to correct his height in the IM part and +-- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). +-- At his point further corrections are necessary. +-- +-- ## Angle of Attack +-- +-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_TrapSheetAoA.png) +-- +-- The graph displays the angle of attack (AoA) as a function of the distance to the carrier. +-- +-- The pilot starts off being on speed after the ball call. Then he get way to fast troughout the most part of the groove. He manages to correct +-- this somewhat short before touchdown. -- -- === -- @@ -1453,6 +1476,9 @@ AIRBOSS.Difficulty={ -- @field #number Vel Total velocity in m/s. -- @field #number Vy Vertical velocity in m/s. -- @field #number Gamma Relative heading player to carrier's runway. 0=parallel, +-90=perpendicular. +-- @field #string Grade LSO grade. +-- @field #number GradePoints LSO grade points +-- @field #string GradeDetail LSO grade details. -- @field #string FlyThrough Fly through up "/" or fly through down "\\". --- LSO grade data. @@ -1560,7 +1586,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="0.9.9" +AIRBOSS.version="0.9.9.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1802,7 +1828,7 @@ function AIRBOSS:New(carriername, alias) -- Debug trace. if false then - --self.Debug=true + self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) @@ -2524,8 +2550,12 @@ end -- @param #string path (Optional) Path where to save the trap sheets. -- @return #AIRBOSS self function AIRBOSS:SetTrapSheet(path) - self.trapsheet=true - self.trappath=path + if io then + self.trapsheet=true + self.trappath=path + else + self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.") + end return self end @@ -6782,8 +6812,14 @@ end -- @param Wrapper.Unit#UNIT unit The aircraft unit that was recovered. -- @return #AIRBOSS.FlightGroup Flight group of element. function AIRBOSS:_RecoveredElement(unit) - local element, idx, flight=self:_GetFlightElement(unit:GetName()) --#AIRBOSS.FlightElement - element.recovered=true + + -- Get element of flight. + local element, idx, flight=self:_GetFlightElement(unit:GetName()) --#AIRBOSS.FlightElement + + -- Nil check. Could be if a helo landed or something else we dont know! + if element then + element.recovered=true + end return flight end @@ -8598,7 +8634,10 @@ function AIRBOSS:_GetGrooveData(playerData) end -- Velocity vector. - local vel=playerData.unit:GetVelocityVec3() + local vel=playerData.unit:GetVelocityVec3() + + -- Grade, points, details + local Gg,Gp,Gd=self:_LSOgrade(playerData) -- Gather pilot data. local groovedata={} --#AIRBOSS.GrooveData @@ -8617,6 +8656,9 @@ function AIRBOSS:_GetGrooveData(playerData) groovedata.Vel=UTILS.VecNorm(vel) groovedata.Vy=vel.y groovedata.Gamma=self:_GetRelativeHeading(playerData.unit, true) + groovedata.Grade=Gg + groovedata.GradePoints=Gp + groovedata.GradeDetail=Gd return groovedata end @@ -8860,6 +8902,7 @@ function AIRBOSS:_Groove(playerData) -- Get current groove data. local gd=playerData.groove[gs] --#AIRBOSS.GrooveData + if gd then self:T3(gd) @@ -10282,12 +10325,13 @@ end -- @param #AIRBOSS self -- @param #number alt Altitude ASL in meters. Default 50 m. -- @param #boolean magnetic Direction including magnetic declination. +-- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position. -- @return #number Direction the wind is blowing **from** in degrees. -- @return #number Wind speed in m/s. -function AIRBOSS:GetWind(alt, magnetic) +function AIRBOSS:GetWind(alt, magnetic, coord) - -- Current position of the carrier - local cv=self:GetCoordinate() + -- Current position of the carrier or input. + local cv=coord or self:GetCoordinate() -- Wind direction and speed. By default at 50 meters ASL. local Wdir, Wspeed=cv:GetWind(alt or 50) @@ -10304,48 +10348,61 @@ function AIRBOSS:GetWind(alt, magnetic) return Wdir, Wspeed end ---- Get wind direction and speed on carrier deck. +--- Get wind speed on carrier deck parallel and perpendicular to runway. -- @param #AIRBOSS self -- @param #number alt Altitude in meters. Default 50 m. --- @return #number Direction the wind is blowing **from** in degrees. --- @return #number Wind speed in m/s. +-- @return #number Wind component parallel to runway im m/s. +-- @return #number Wind component perpendicular to runway in m/s. +-- @return #number Total wind strength in m/s. function AIRBOSS:GetWindOnDeck(alt) - -- Position of carrier + -- Position of carrier. local cv=self:GetCoordinate() -- Velocity vector of carrier. local vc=self.carrier:GetVelocityVec3() - -- Rotate to angled deck. - vc=UTILS.Rotate2D(vc, self.carrierparam.rwyangle) + -- Carrier orientation X. + local xc=self.carrier:GetOrientationX() + + -- Carrier orientation Z. + local zc=self.carrier:GetOrientationZ() + + -- Rotate back so that angled deck points to wind. + xc=UTILS.Rotate2D(xc, -self.carrierparam.rwyangle) + zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) -- Wind (from) vector local vw=cv:GetWindWithTurbulenceVec3(alt or 50) + -- Total wind velocity vector. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. - local vd=UTILS.VecSubstract(vw, vc) - - local vh=math.deg(math.atan2(vd.z, vd.x)) - if vh<0 then - vh=vh+360 - end + local vT=UTILS.VecSubstract(vw, vc) - -- Strength. - local vabs=UTILS.VecNorm(vd) + -- || Parallel component. + local vpa=UTILS.VecDot(vT,xc) - return vh, vabs + -- == Perpendicular component. + local vpp=UTILS.VecDot(vT,zc) + + -- Strength. + local vabs=UTILS.VecNorm(vT) + + -- We return positive values as head wind and negative values as tail wind. + --TODO: Check minus sign. + return -vpa, vpp, vabs end --- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway. -- @param #AIRBOSS self -- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. +-- @param Core.Point#COORDINATE coord (Optional) Coodinate from which heading is calculated. Default is current carrier position. -- @return #number Carrier heading in degrees. -function AIRBOSS:GetHeadingIntoWind(magnetic) +function AIRBOSS:GetHeadingIntoWind(magnetic, coord) -- Get direction the wind is blowing from. This is where we want to go. - local windfrom, vwind=self:GetWind() + local windfrom, vwind=self:GetWind(nil, nil, coord) -- Actually, we want the runway in the wind. local intowind=windfrom-self.carrierparam.rwyangle @@ -11686,7 +11743,7 @@ function AIRBOSS:_Debrief(playerData) mygrade.finalscore=Points end mygrade.case=playerData.case - local _,windondeck=self:GetWindOnDeck() + local windondeck=self:GetWindOnDeck() mygrade.wind=tostring(UTILS.Round(UTILS.MpsToKnots(windondeck), 1)) mygrade.modex=playerData.onboard mygrade.airframe=playerData.actype @@ -12082,8 +12139,9 @@ end -- @param #number speed Speed in knots. Default is current carrier velocity. -- @param #boolean uturn (Optional) If true, carrier will go back to where it came from before it resumes its route to the next waypoint. -- @param #number uspeed Speed in knots after U-turn. Default is same as before. +-- @param Core.Point#COORDINATE tcoord Additional coordinate to make turn smoother. -- @return #AIRBOSS self -function AIRBOSS:CarrierDetour(coord, speed, uturn, uspeed) +function AIRBOSS:CarrierDetour(coord, speed, uturn, uspeed, tcoord) -- Current coordinate of the carrier. local pos0=self:GetCoordinate() @@ -12094,17 +12152,16 @@ 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) + local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed) -- 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(cspeedkmh)) - table.insert(wp, pos1:WaypointGround(cspeedkmh)) + if tcoord then + table.insert(wp, tcoord:WaypointGround(cspeedkmh)) + end table.insert(wp, coord:WaypointGround(speedkmh)) -- If enabled, go back to where you came from. @@ -12123,6 +12180,9 @@ function AIRBOSS:CarrierDetour(coord, speed, uturn, uspeed) -- Debug mark. if self.Debug then + if tcoord then + tcoord:MarkToAll("Detour Tcoord") + end coord:MarkToAll("Detour Point") end @@ -12158,15 +12218,72 @@ function AIRBOSS:CarrierTurnIntoWind(time, vdeck, uturn) self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Distance=%.1f NM, Speed=%.1f knots, Time=%d sec.", UTILS.MpsToKnots(vwind), distNM, speedknots, time)) -- Get heading into the wind accounting for angled runway. - local intowind=self:GetHeadingIntoWind(false) - - -- Translate current position. - local pos1=self:GetCoordinate():Translate(dist, intowind) + local hiw=self:GetHeadingIntoWind() + + -- Current heading. + local hdg=self:GetHeading() + + -- Heading difference. + local deltaH=self:_GetDeltaHeading(hdg, hiw) + + local Cv=self:GetCoordinate() + + local Ctiw=nil --Core.Point#COORDINATE + local Csoo=nil --Core.Point#COORDINATE + + -- Define path depending on turn angle. + if deltaH<45 then + -- Small turn. + + -- Point in the right direction to help turning. + Csoo=Cv:Translate(750, hdg):Translate(750, hiw) + + -- Heading into wind from Csoo. + local hsw=self:GetHeadingIntoWind(false, Csoo) + + -- Into the wind coord. + Ctiw=Csoo:Translate(dist, hsw) + + elseif deltaH<90 then + -- Medium turn. + + -- Point in the right direction to help turning. + Csoo=Cv:Translate(900, hdg):Translate(900, hiw) + + -- Heading into wind from Csoo. + local hsw=self:GetHeadingIntoWind(false, Csoo) + + -- Into the wind coord. + Ctiw=Csoo:Translate(dist, hsw) + + elseif deltaH<135 then + -- Large turn backwards. + + -- Point in the right direction to help turning. + -- TODO turn left or right? + Csoo=Cv:Translate(1200, hdg-90):Translate(1200, hiw) + + -- Heading into wind from Csoo. + local hsw=self:GetHeadingIntoWind(false, Csoo) + + -- Into the wind coord. + Ctiw=Csoo:Translate(dist, hsw) + + else + -- Huge turn backwards. + + -- Point in the right direction to help turning. + -- TODO turn left or right? + Csoo=Cv:Translate(1200, hdg-90):Translate(1200, hiw) + + -- Heading into wind from Csoo. + local hsw=self:GetHeadingIntoWind(false, Csoo) + + -- Into the wind coord. + Ctiw=Csoo:Translate(dist, hsw) - -- Debug mark. - if self.Debug then - pos1:MarkToAll("Into the wind point") end + -- Return to coordinate if collision is detected. self.Creturnto=self:GetCoordinate() @@ -12178,7 +12295,7 @@ function AIRBOSS:CarrierTurnIntoWind(time, vdeck, uturn) local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity()) -- Let the carrier make a detour from its route but return to its current position. - self:CarrierDetour(pos1, speedknots, uturn, vdownwind) + self:CarrierDetour(Ctiw, speedknots, uturn, vdownwind, Csoo) -- Set switch that we are currently turning into the wind. self.turnintowind=true @@ -15190,7 +15307,7 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) end -- Wind on flight deck - local wind=UTILS.MpsToKnots(select(2, self:GetWindOnDeck())) + local wind=UTILS.MpsToKnots(select(1, self:GetWindOnDeck())) -- Get groups, units in queues. local Nmarshal,nmarshal = self:_GetQueueInfo(self.Qmarshal, playerData.case) @@ -15313,8 +15430,10 @@ function AIRBOSS:_DisplayCarrierWeather(_unitname) -- Get Beaufort wind scale. local Bn,Bd=UTILS.BeaufortScale(Ws) - -- Wind on flight deck - local Wod=UTILS.MpsToKnots(select(2, self:GetWindOnDeck())) + -- Wind on flight deck. + local WodPA,WodPP=self:GetWindOnDeck() + local WodPA=UTILS.MpsToKnots(WodPA) + local WodPP=UTILS.MpsToKnots(WodPP) local WD=string.format('%03d°', Wd) local Ts=string.format("%d°C",T) @@ -15322,13 +15441,13 @@ function AIRBOSS:_DisplayCarrierWeather(_unitname) local tT=string.format("%d°C",T) local tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) local tP=string.format("%.2f inHg", UTILS.hPa2inHg(P)) - + -- Report text. text=text..string.format("Weather Report at Carrier %s:\n", self.alias) - text=text..string.format("================================\n") + text=text..string.format("================================\n") text=text..string.format("Temperature %s\n", tT) text=text..string.format("Wind from %s at %s (%s)\n", WD, tW, Bd) - text=text..string.format("Wind on deck %.1f knots\n", Wod) + text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n", WodPA, WodPP) text=text..string.format("QFE %.1f hPa = %s", P, tP) -- More info only reliable if Mission uses static weather. @@ -15935,7 +16054,7 @@ function AIRBOSS:_SaveTrapSheet(playerData, grade) self:I(self.lid..text) -- Header line - local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step\n" + local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n" local g0=playerData.trapsheet[1] --#AIRBOSS.GrooveData local T0=g0.Time @@ -15956,9 +16075,12 @@ function AIRBOSS:_SaveTrapSheet(playerData, grade) local k=groove.Pitch local l=groove.Roll local m=groove.Yaw - local n=groove.Step - -- t a b c d e f g h i j k l m n - data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n) + local n=self:_GS(groove.Step, -1) + local o=groove.Grade + local p=groove.GradePoints + local q=groove.GradeDetail + -- t a b c d e f g h i j k l m n o p q + data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q) end -- Save file. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f9af6400a..7abf26489 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -799,6 +799,18 @@ function UTILS.VecAngle(a, b) return math.deg(alpha) end +--- Calculate "heading" of a 3D vector in the X-Z plane. +-- @param DCS#Vec3 a Vector in 3D with x, y, z components. +-- @return #number Heading in degrees in [0,360). +function UTILS.VecHdg(a) + local h=math.deg(math.atan2(a.z, a.x)) + if h<0 then + h=h+360 + end + return h +end + + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees.