From 33509a49e0c6e190b9e1883ee5f6cffd1d2132a4 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 12 Oct 2018 16:16:39 +0200 Subject: [PATCH] CT v0.0.3 --- .../Moose/Functional/CarrierTrainer.lua | 227 +++++++++++------- .../Moose/Wrapper/Positionable.lua | 99 ++++++++ 2 files changed, 245 insertions(+), 81 deletions(-) diff --git a/Moose Development/Moose/Functional/CarrierTrainer.lua b/Moose Development/Moose/Functional/CarrierTrainer.lua index 274dcebe4..1f9467890 100644 --- a/Moose Development/Moose/Functional/CarrierTrainer.lua +++ b/Moose Development/Moose/Functional/CarrierTrainer.lua @@ -55,7 +55,7 @@ CARRIERTRAINER = { --- Carrier trainer class version. -- @field #string version -CARRIERTRAINER.version="0.0.2" +CARRIERTRAINER.version="0.0.3" --- Player data table holding all important parameters for each player. -- @type CARRIERTRAINER.PlayerData @@ -72,6 +72,7 @@ CARRIERTRAINER.version="0.0.2" -- @field #string summary Result summary text. -- @field Wrapper.Client#CLIENT Client object of player. + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -281,11 +282,6 @@ end -- @param #CARRIERTRAINER self function CARRIERTRAINER:_CheckPlayerStatus() - if self.players==nil then - self:I(self.lid.."No players yet.") - return - end - -- Loop over all players. for _playerName,_playerData in pairs(self.players) do local playerData = _playerData --#CARRIERTRAINER.PlayerData @@ -303,11 +299,6 @@ function CARRIERTRAINER:_CheckPlayerStatus() self:_DetailedPlayerStatus(playerData) end - -- Check long down wind leg. - if playerData.step == 6 and not playerData.longDownwindDone and unit:IsInZone(self.giantZone) then - self:_CheckForLongDownwind(playerData) - end - if playerData.step==0 and unit:IsInZone(self.giantZone) and unit:InAir() then self:_NewRound(playerData) elseif playerData.step == 1 and unit:IsInZone(self.startZone) then @@ -321,6 +312,10 @@ function CARRIERTRAINER:_CheckPlayerStatus() elseif playerData.step == 5 and unit:IsInZone(self.giantZone) then self:_Abeam(playerData) elseif playerData.step == 6 and unit:IsInZone(self.giantZone) then + -- Check long down wind leg. + if not playerData.longDownwindDone then + self:_CheckForLongDownwind(playerData) + end self:_Ninety(playerData) elseif playerData.step == 7 and unit:IsInZone(self.giantZone) then self:_Wake(playerData) @@ -337,6 +332,37 @@ function CARRIERTRAINER:_CheckPlayerStatus() end +--- Get name of the current pattern step. +-- @param #CARRIERTRAINER self +-- @param #number step Step +-- @return #string Name of the step +function CARRIERTRAINER:_StepName(step) + + local name="unknown" + if step==0 then + name="Unregistered" + elseif step==1 then + name="entering pattern" + elseif step==2 then + name="on upwind leg" + elseif step==3 then + name="early break" + elseif step==4 then + name="late break" + elseif step==5 then + name="abeam" + elseif step==6 then + name="at the wake" + elseif step==7 then + name="at the ninety" + elseif step==8 then + name="in the groove" + elseif step==9 then + name="trapped" + end + +end + --- Provide info about player status on the fly. -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data. @@ -345,12 +371,11 @@ function CARRIERTRAINER:_DetailedPlayerStatus(playerData) local unit=playerData.unit local aoa=unit:GetAoA() + local yaw=unit:GetYaw() + local roll=unit:GetRoll() + local pitch=unit:GetPitch() local dist=playerData.unit:GetCoordinate():Get2DDistance(self.carrier:GetCoordinate()) - local dz,dx=self:_GetDistances(unit) - - local text=string.format("%s, current AoA=%.1f\n", playerData.callsign, aoa) - text=text..string.format("Carrier distance: d=%d m\n", dist) - text=text..string.format("Carrier distance: z=%d m x=%d m sum=%d", dz, dx, math.abs(dz)+math.abs(dx)) + local dx,dz=self:_GetDistances(unit) -- Player and carrier position vector. local playerPosition = playerData.unit:GetVec3() @@ -358,11 +383,17 @@ function CARRIERTRAINER:_DetailedPlayerStatus(playerData) local diffZ = playerPosition.z - carrierPosition.z local diffX = playerPosition.x - carrierPosition.x - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) - MESSAGE:New(text, 5, nil ,true):ToClient(playerData.client) + local heading=unit:GetCoordinate():HeadingTo(self.startZone:GetCoordinate()) + + local text=string.format("%s, current AoA=%.1f\n", playerData.callsign, aoa) + text=text..string.format("roll=%.1f yaw=%.1f pitch=%.1f\n", roll, yaw, pitch) + text=text..string.format("current step = %d %s\n", playerData.step, self:_StepName(playerData.step)) + text=text..string.format("Carrier distance: d=%d m\n", dist) + text=text..string.format("Carrier distance: x=%d m z=%d m sum=%d (old)", diffX, diffZ, math.abs(diffX)+math.abs(diffZ)) + text=text..string.format("Carrier distance: x=%d m z=%d m sum=%d (new)", dx, dz, math.abs(dz)+math.abs(dx)) + + MESSAGE:New(text, 1, nil ,true):ToClient(playerData.client) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -386,7 +417,7 @@ end -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. function CARRIERTRAINER:_Start(playerData) - local hint = "Entering the pattern, " .. playerData.callsign .. "! Aim for 800 feet and 350-400 kts on the upwind." + local hint = string.format("Entering the pattern, %s! Aim for 800 feet and 350-400 kts on the upwind.", playerData.callsign) self:_SendMessageToPlayer(hint, 8, playerData) playerData.score = 0 playerData.step = 2 @@ -405,52 +436,56 @@ function CARRIERTRAINER:_Upwind(playerData) local diffX = position.x - carrierPosition.x -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) - if(diffZ > 500 or diffZ < 0 or diffX < -4000) then + -- Too far away. + -- Should be between 0-500 meters right of carrier. + -- TODO Should be withing 4 km behind carrier. Why? + if (diffZ > 500 or diffZ < 0 or diffX < -4000) then self:_AbortPattern(playerData) return end - if (diffX < 0) then - return - end + -- Now before the boat. + if diffX > 0 then - local idealAltitude = 800 - local altitude = UTILS.Round( UTILS.MetersToFeet( position.y ) ) - - local hint = "" - local score = 0 - - if (altitude > 850) then - score = 5 - hint = "You're high on the upwind." - elseif (altitude > 830) then - score = 7 - hint = "You're slightly high on the upwind." - elseif (altitude < 750) then - score = 5 - hint = "You're low on the upwind." - elseif (altitude < 770) then - score = 7 - hint = "You're slightly low on the upwind." - else - score = 10 - hint = "Good altitude on the upwind." - end - - -- Increase score. - self:_IncreaseScore(playerData, score) - - self:_SendMessageToPlayer(hint, 8, playerData) - - self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) - self:_PrintScore(score, playerData, true) + local idealAltitude = 800 + local altitude = UTILS.MetersToFeet(position.y) - self:_AddToSummary(playerData, hint) - - -- Set step. - playerData.step = 3 + local hint = "" + local score = 0 + + if (altitude > 850) then + score = 5 + hint = "You're high on the upwind." + elseif (altitude > 830) then + score = 7 + hint = "You're slightly high on the upwind." + elseif (altitude < 750) then + score = 5 + hint = "You're low on the upwind." + elseif (altitude < 770) then + score = 7 + hint = "You're slightly low on the upwind." + else + score = 10 + hint = "Good altitude on the upwind." + end + + -- Increase score. + self:_IncreaseScore(playerData, score) + + self:_SendMessageToPlayer(hint, 8, playerData) + + self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) + self:_PrintScore(score, playerData, true) + + self:_AddToSummary(playerData, hint) + + -- Set step. + playerData.step = 3 + + end end --- Calculate distances between carrier and player unit. @@ -482,20 +517,31 @@ function CARRIERTRAINER:_Break(playerData, part) local diffX = playerPosition.x - carrierPosition.x -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) - if(diffZ > 1500 or diffZ < -3700 or diffX < -500) then + -- Abort when + -- > 1.5 km right of carrier + -- > 3.7 km left of carrier + -- > 0.5 km behind carrier + if (diffZ > 1500 or diffZ < -3700 or diffX < -500) then self:_AbortPattern(playerData) return end + + -- Break + -- z= -370 + -- y= 800 + -- x> -500 - local limit = -370 + local limit = -370 --0.2 NM if part == "late" then - limit = -1470 + limit = -1470 -- 0.8 NM end + -- if diffZ < limit then + local idealAltitude = 800 local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) ) @@ -546,17 +592,27 @@ function CARRIERTRAINER:_Abeam(playerData) local diffX = playerPosition.x - carrierPosition.x -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) + -- Abort if + -- less than 1.0 km left of boat (no closer than 1 km to boat + -- more than 3.7 km left of boat if (diffZ > -1000 or diffZ < -3700) then self:_AbortPattern(playerData) return end + -- Abeam pos: + -- x= -200 + -- z=-2160 + -- y= 600 + + -- Abeam pos 200 meters behind ship local limit = -200 if diffX < limit then - --local aoa = math.deg(mist.getAoA(playerData.mistUnit)) + + -- Get AoA. local aoa = playerData.unit:GetAoA() local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData) @@ -594,7 +650,7 @@ function CARRIERTRAINER:_Abeam(playerData) local roundedNm = UTILS.Round(nm, 2) - if(nm < 1.0) then + if (nm < 1.0) then distanceScore = 0 distanceHint = "too close to the boat (" .. roundedNm .. " nm)" elseif(nm < 1.1) then @@ -628,21 +684,23 @@ end -- @param #CARRIERTRAINER self -- @param #CARRIERTRAINER.PlayerData playerData Player data table. function CARRIERTRAINER:_CheckForLongDownwind(playerData) + local playerPosition = playerData.unit:GetVec3() local carrierPosition = self.carrier:GetVec3() - local limit = 1500 + local limit = -1500 -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffZ, diffX = self:_GetDistances(playerData.unit) + local diffX, diffZ = self:_GetDistances(playerData.unit) - --TODO if diffX + --if carrierPosition.x - playerPosition.x > limit then + if diffX > limit then - if carrierPosition.x - playerPosition.x > limit then - - local heading = playerData.unit:GetHeading() + local headingPlayer = playerData.unit:GetHeading() + local headingCarrier = self.carrier:GetHeading() + --TODO: Take carrier heading != 0 into account! + if (headingPlayer > 170) then - if (heading > 170) then local hint = "Too long downwind. Turn final earlier next time." self:_SendMessageToPlayer( hint, 8, playerData ) local score = -40 @@ -651,6 +709,7 @@ function CARRIERTRAINER:_CheckForLongDownwind(playerData) self:_AddToSummary(playerData, hint) playerData.longDownwindDone = true end + end end @@ -665,7 +724,7 @@ function CARRIERTRAINER:_Ninety(playerData) local diffX = playerPosition.x - carrierPosition.x -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) if(diffZ < -3700 or diffX < -3700 or diffX > 0) then self:_AbortPattern(playerData) @@ -728,7 +787,7 @@ function CARRIERTRAINER:_Wake(playerData) local diffX = playerPosition.x - carrierPosition.x -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) if(diffZ < -2000 or diffX < -4000 or diffX > 0) then self:_AbortPattern(playerData) @@ -788,20 +847,25 @@ function CARRIERTRAINER:_Groove(playerData) --TODO -100?! -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) + diffX, diffZ = self:_GetDistances(playerData.unit) - if(diffX > 0 or diffX < -4000) then + diffX=diffX+100 + + -- In front of carrier or more than 4 km behind carrier. + if (diffX > 0 or diffX < -4000) then self:_AbortPattern(playerData) return end - if(diffX > -500) then --Reached in close before groove + --TODO: + if (diffX > -500) then --Reached in close before groove local hint = "You're too far left and never reached the groove." self:_SendMessageToPlayer( hint, 8, playerData ) self:_PrintScore(0, playerData, true) self:_AddToSummary(playerData, hint) playerData.step = 9 - else + else + local limitDeg = 8.0 local fraction = diffZ / (-diffX) @@ -958,7 +1022,8 @@ end -- @param #number idealAltitude Ideal altitude. -- @param #CARRIERTRAINER.PlayerData playerData Player data. function CARRIERTRAINER:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) - self:_SendMessageToPlayer( "Alt: " .. altitude .. " (Target: " .. idealAltitude .. ")", 8, playerData ) + local text=string.format("Alt: %d feet (Target: %d feet)", altitude, idealAltitude) + self:_SendMessageToPlayer(text, 8, playerData) end --- Score for correct AoA. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 2b8170ba9..a9e8ee026 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -696,6 +696,105 @@ function POSITIONABLE:GetAoA() return nil end +--- Returns the unit's climb or descent angle. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Climb or descent angle in degrees. +function POSITIONABLE:GetAoA() + + -- Get position of the unit. + local unitpos = self:GetPosition() + + if unitpos then + + -- Get velocity vector of the unit. + local unitvel = self:GetVelocityVec3() + + if unitvel and UTILS.VecNorm(unitvel)~=0 then + + return math.asin(unitvel.y/UTILS.VecNorm(unitvel)) + + end + end +end + +--- Returns the pitch angle of a unit. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Pitch ange in degrees. +function POSITIONABLE:GetPitch() + + -- Get position of the unit. + local unitpos = self:GetPosition() + + if unitpos then + return math.deg(math.asin(unitpos.x.y)) + end + + return nil +end + +--- Returns the roll angle of a unit. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Pitch ange in degrees. +function POSITIONABLE:GetRoll() + + -- Get position of the unit. + local unitpos = self:GetPosition() + + if unitpos then + + --first, make a vector that is perpendicular to y and unitpos.x with cross product + local cp = UTILS.VecCross(unitpos.x, {x = 0, y = 1, z = 0}) + + --now, get dot product of of this cross product with unitpos.z + local dp = UTILS.VecDot(cp, unitpos.z) + + --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) + local Roll = math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z))) + + --now, have to get sign of roll. + -- by convention, making right roll positive + -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. + + if unitpos.z.y > 0 then -- left roll, flip the sign of the roll + Roll = -Roll + end + + return math.deg(Roll) + end +end + +--- Returns the yaw angle of a unit. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Yaw ange in degrees. +function POSITIONABLE:GetYaw() + + local unitpos = self:GetPosition() + if unitpos then + -- get unit velocity + local unitvel = self:GetVelocityVec3() + + if unitvel and UTILS.VecNorm(unitvel) ~= 0 then --must have non-zero velocity! + local AxialVel = {} --unit velocity transformed into aircraft axes directions + + --transform velocity components in direction of aircraft axes. + AxialVel.x = UTILS.VecDot(unitpos.x, unitvel) + AxialVel.y = UTILS.VecDot(unitpos.y, unitvel) + AxialVel.z = UTILS.VecDot(unitpos.z, unitvel) + + --Yaw is the angle between unitpos.x and the x and z velocities + --define right yaw as positive + local Yaw = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/UTILS.VecNorm({x = AxialVel.x, y = 0, z = AxialVel.z})) + + --now set correct direction: + if AxialVel.z > 0 then + Yaw = -Yaw + end + return Yaw + end + end + +end + --- Returns the message text with the callsign embedded (if there is one). -- @param #POSITIONABLE self