CT v0.1.0

This commit is contained in:
Frank 2018-10-22 23:41:48 +02:00
parent 2ea8487e04
commit 19520f9688

View File

@ -27,7 +27,8 @@
-- @field Wrapper.Unit#UNIT carrier Aircraft carrier unit on which we want to practice.
-- @field #string carriertype Type name of aircraft carrier.
-- @field Core.Zone#ZONE_UNIT startZone Zone in which the pattern approach starts.
-- @field Core.Zone#ZONE_UNIT giantZone Zone around the carrier to register a new player.
-- @field Core.Zone#ZONE_UNIT giantZone Large zone around the carrier to welcome players.
-- @field Core.Zone#ZONE_UNIT registerZone Zone behind the carrier to register for a new approach.
-- @field #table players Table of players.
-- @field #table menuadded Table of units where the F10 radio menu was added.
-- @field #CARRIERTRAINER.Checkpoint Upwind Upwind checkpoint.
@ -122,6 +123,7 @@ CARRIERTRAINER.Difficulty={
-- @field #string summary Result summary text.
-- @field Wrapper.Client#CLIENT client object of player.
-- @field #string difficulty Difficulty level.
-- @field #boolean inbigzone If true, player is in the big zone.
--- Checkpoint parameters triggering the next step in the pattern.
-- @type CARRIERTRAINER.Checkpoint
@ -146,7 +148,7 @@ CARRIERTRAINER.MenuF10={}
--- Carrier trainer class version.
-- @field #string version
CARRIERTRAINER.version="0.0.9"
CARRIERTRAINER.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@ -185,6 +187,22 @@ function CARRIERTRAINER:New(carriername, alias)
-- Set alias.
self.alias=alias or carriername
if self.carriertype==CARRIERTRAINER.CarrierType.STENNIS then
self:_InitStennis()
elseif self.carriertype==CARRIERTRAINER.CarrierType.VINSON then
-- TODO: Carl Vinson parameters.
self:_InitStennis()
elseif self.carriertype==CARRIERTRAINER.CarrierType.TARAWA then
-- TODO: Tarawa parameters.
self:_InitStennis()
elseif self.carriertype==CARRIERTRAINER.CarrierType.KUZNETSOV then
-- TODO: Kusnetsov parameters - maybe...
self:_InitStennis()
else
self:E(self.lid.."ERROR: Unknown carrier type!")
return nil
end
-----------------------
--- FSM Transitions ---
-----------------------
@ -237,7 +255,7 @@ function CARRIERTRAINER:onafterStart(From, Event, To)
-- Handle events.
self:HandleEvent(EVENTS.Birth)
--self:HandleEvent(EVENTS.Lan)
self:HandleEvent(EVENTS.Land)
-- Init status check
self:__Status(5)
@ -264,6 +282,7 @@ end
-- @param #string To To state.
function CARRIERTRAINER:onafterStop(From, Event, To)
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Land)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -307,6 +326,39 @@ function CARRIERTRAINER:OnEventBirth(EventData)
end
end
--- Carrier trainer event handler for event land.
-- @param #CARRIERTRAINER self
-- @param Core.Event#EVENTDATA EventData
function CARRIERTRAINER:OnEventLand(EventData)
self:F3({eventland = EventData})
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."LAND: unit = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."LAND: group = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."LAND: player = "..tostring(_playername))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
-- Debug output.
local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed.", _playername, _callsign, _unitName, _uid, _group:GetName())
self:T(self.lid..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Check if we caught a wire after one second.
-- TODO: test this!
local playerData=self.players[_playername]
SCHEDULER:New(nil, self._Trapped,{self, playerData}, 1)
end
end
--- Initialize player data.
-- @param #CARRIERTRAINER self
-- @param #string unitname Name of the player unit.
@ -326,6 +378,8 @@ function CARRIERTRAINER:_InitNewPlayer(unitname)
playerData.difficulty=CARRIERTRAINER.Difficulty.NORMAL
playerData.inbigzone=playerData.unit:IsInZone(self.giantZone)
return playerData
end
@ -334,9 +388,9 @@ end
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
-- @return #CARRIERTRAINER.PlayerData Initialized player data.
function CARRIERTRAINER:_InitNewRound(playerData)
playerData.score = 0
playerData.summary = "SUMMARY:\n"
playerData.step = 0
playerData.score = 0
playerData.summary = "Debriefing:\n"
playerData.longDownwindDone = false
playerData.highestCarrierXDiff = -9999999
playerData.secondsStandingStill = 0
@ -392,38 +446,52 @@ function CARRIERTRAINER:_CheckPlayerStatus()
if unit:IsAlive() then
--self:_SendMessageToPlayer("current step "..self:_StepName(playerData.step),1,playerData)
--self:_DetailedPlayerStatus(playerData)
--self:_DetailedPlayerStatus(playerData)
if unit:IsInZone(self.giantZone) then
--self:_DetailedPlayerStatus(playerData)
-- Check if player was previously not inside the zone.
if playerData.inbigzone==false then
local text=string.format("Welcome back, %s! TCN 1X, BRC 354 (MAG HDG).\n", playerData.callsign)
local heading=playerData.unit:GetCoordinate():HeadingTo(self.registerZone:GetCoordinate())
local distance=playerData.unit:GetCoordinate():Get2DDistance(self.registerZone:GetCoordinate())
text=text..string.format("Fly heading %d for %.1f NM to begin your approach.", heading, distance)
MESSAGE:New(text, 5):ToClient(playerData.client)
end
if playerData.step==0 and unit:InAir() then
self:_NewRound(playerData)
elseif playerData.step == 1 then
self:_Start(playerData)
elseif playerData.step == 2 then
self:_Upwind(playerData)
elseif playerData.step == 3 then
self:_Break(playerData, "early")
elseif playerData.step == 4 then
self:_Break(playerData, "late")
elseif playerData.step == 5 then
self:_Abeam(playerData)
elseif playerData.step == 6 then
-- Check long down wind leg.
if not playerData.longDownwindDone then
self:_CheckForLongDownwind(playerData)
end
self:_Ninety(playerData)
elseif playerData.step == 7 then
self:_Wake(playerData)
elseif playerData.step == 8 then
self:_Groove(playerData)
elseif playerData.step == 9 then
self:_Trap(playerData)
end
else
playerData.inbigzone=false
end
if playerData.step==0 and unit:IsInZone(self.giantZone) and unit:InAir() then
self:_NewRound(playerData)
self:_InitStennis()
elseif playerData.step == 1 and unit:IsInZone(self.startZone) then
self:_Start(playerData)
elseif playerData.step == 2 and unit:IsInZone(self.giantZone) then
self:_Upwind(playerData)
elseif playerData.step == 3 and unit:IsInZone(self.giantZone) then
self:_Break(playerData, "early")
elseif playerData.step == 4 and unit:IsInZone(self.giantZone) then
self:_Break(playerData, "late")
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)
elseif playerData.step == 8 and unit:IsInZone(self.giantZone) then
self:_Groove(playerData)
elseif playerData.step == 9 and unit:IsInZone(self.giantZone) then
self:_Trap(playerData)
end
else
-- Unit not alive.
--playerDatas[i] = nil
@ -445,7 +513,7 @@ function CARRIERTRAINER:_StepName(step)
elseif step==1 then
name="when entering pattern"
elseif step==2 then
name="on upwind leg"
name="in the break entry"
elseif step==3 then
name="at the early break"
elseif step==4 then
@ -453,9 +521,9 @@ function CARRIERTRAINER:_StepName(step)
elseif step==5 then
name="in the abeam position"
elseif step==6 then
name="at the wake"
elseif step==7 then
name="at the ninety"
elseif step==7 then
name="at the wake"
elseif step==8 then
name="in the groove"
elseif step==9 then
@ -468,8 +536,10 @@ end
--- Calculate distances between carrier and player unit.
-- @param #CARRIERTRAINER self
-- @param Wrapper.Unit#UNIT unit Player unit
-- @return #number Distance in the direction of the orientation of the carrier.
-- @return #number Distance perpendicular to the orientation of the carrier.
-- @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 CARRIERTRAINER:_GetDistances(unit)
-- Vector to carrier
@ -493,7 +563,16 @@ function CARRIERTRAINER:_GetDistances(unit)
-- Projection of player pos on z component.
local dz=UTILS.VecDot(z,c)
return dx,dz
-- 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
return dx,dz,rho,phi
end
--- Check if a player is within the right area.
@ -568,7 +647,7 @@ function CARRIERTRAINER:_AbortPattern(playerData, X, Z, posData)
local text=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s", X, tostring(posData.Xmin), tostring(posData.Xmax), Z, tostring(posData.Zmin), tostring(posData.Zmax))
self:E(self.lid..text)
MESSAGE:New(text, 60):ToAllIf(self.Debug)
--MESSAGE:New(text, 60):ToAllIf(self.Debug)
self:_AddToSummary(playerData, "Approach aborted.")
@ -592,7 +671,7 @@ function CARRIERTRAINER:_DetailedPlayerStatus(playerData)
local roll=unit:GetRoll()
local pitch=unit:GetPitch()
local dist=playerData.unit:GetCoordinate():Get2DDistance(self.carrier:GetCoordinate())
local dx,dz=self:_GetDistances(unit)
local dx,dz,rho,phi=self:_GetDistances(unit)
-- Player and carrier position vector.
local playerPosition = playerData.unit:GetVec3()
@ -612,7 +691,8 @@ function CARRIERTRAINER:_DetailedPlayerStatus(playerData)
text=text..string.format("velo x=%.1f y=%.1f z=%.1f\n", velo.x, velo.y, velo.z)
text=text..string.format("wind x=%.1f y=%.1f z=%.1f\n", wind.x, wind.y, wind.z)
text=text..string.format("pitch=%.1f | roll=%.1f | yaw=%.1f | climb=%.1f\n", pitch, roll, yaw, unit:GetClimbAnge())
text=text..string.format("relheading %.1f degrees", relhead)
text=text..string.format("relheading=%.1f degrees\n", relhead)
text=text..string.format("rho=%.1f m phi=%.1f degrees\n", rho,phi)
--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)\n", diffX, diffZ, math.abs(diffX)+math.abs(diffZ))
@ -647,7 +727,7 @@ function CARRIERTRAINER:_InitStennis()
self.BreakEarly.Zmax=1500
self.BreakEarly.LimitXmin=0
self.BreakEarly.LimitXmax=nil
self.BreakEarly.LimitZmin=-370 --0.2 NM
self.BreakEarly.LimitZmin=-370 -- 0.2 NM port of carrier
self.BreakEarly.LimitZmax=nil
self.BreakEarly.Altitude=UTILS.FeetToMeters(800)
self.BreakEarly.AoA=8.1
@ -660,9 +740,9 @@ function CARRIERTRAINER:_InitStennis()
self.BreakLate.Zmin=-3700
self.BreakLate.Zmax=1500
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=10000
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-1470 --0.8 NM
self.BreakLate.LimitZmax=10000
self.BreakLate.LimitZmax=nil
self.BreakLate.Altitude=UTILS.FeetToMeters(800)
self.BreakLate.AoA=8.1
self.BreakLate.Distance=nil
@ -671,19 +751,19 @@ function CARRIERTRAINER:_InitStennis()
self.Abeam.name="Abeam Position"
self.Abeam.Xmin=nil
self.Abeam.Xmax=nil
self.Abeam.Zmin=-3700
self.Abeam.Zmin=-4000
self.Abeam.Zmax=-1000
self.Abeam.LimitXmin=-200
self.Abeam.LimitXmax=10000
self.Abeam.LimitZmin=0
self.Abeam.LimitZmax=10000
self.Abeam.LimitXmax=nil
self.Abeam.LimitZmin=nil
self.Abeam.LimitZmax=nil
self.Abeam.Altitude=UTILS.FeetToMeters(600)
self.Abeam.AoA=8.1
self.Abeam.Distance=nil
self.Abeam.Distance=UTILS.NMToMeters(1.2)
-- At the ninety
self.Ninety.name="Ninety"
self.Ninety.Xmin=-3700
self.Ninety.Xmin=-4000
self.Ninety.Xmax=0
self.Ninety.Zmin=-3700
self.Ninety.Zmax=nil
@ -692,8 +772,8 @@ function CARRIERTRAINER:_InitStennis()
self.Ninety.LimitZmin=nil
self.Ninety.LimitZmax=-1111
self.Ninety.Altitude=UTILS.FeetToMeters(500)
self.Abeam.AoA=8.1
self.Abeam.Distance=nil
self.Ninety.AoA=8.1
self.Ninety.Distance=nil
-- Wake position
self.Wake.name="Wake"
@ -713,6 +793,15 @@ function CARRIERTRAINER:_InitStennis()
self.Groove.name="Groove"
self.Groove.Xmin=-4000
self.Groove.Xmax=100
self.Groove.Zmin=-2000
self.Groove.Zmax=nil
self.Groove.LimitXmin=nil
self.Groove.LimitXmax=nil
self.Groove.LimitZmin=nil
self.Groove.LimitZmax=nil
self.Groove.Altitude=UTILS.FeetToMeters(300)
self.Groove.AoA=8.1
self.Groove.Distance=nil
-- Landing trap
self.Trap.name="Trap"
@ -720,8 +809,13 @@ function CARRIERTRAINER:_InitStennis()
self.Trap.Xmax=nil
self.Trap.Zmin=-2000
self.Trap.Zmax=2000
--self.Trap.Limit=nil
self.Trap.Altitude=nil
self.Trap.LimitXmin=nil
self.Trap.LimitXmax=nil
self.Trap.LimitZmin=nil
self.Trap.LimitZmax=nil
self.Trap.Altitude=nil
self.Trap.AoA=nil
self.Trap.Distance=nil
end
@ -733,25 +827,6 @@ end
-- @return #boolean If true, checkpoint condition for next step was reached.
function CARRIERTRAINER:_CheckLimits(X, Z, check)
--[[
local next=true
if check.LimitXmin and X<check.LimitXmin then
next=false
elseif check.LimitXmax and X>check.LimitXmax then
next=false
elseif check.LimitZmin and Z<check.LimitZmin then
next=false
elseif check.LimitZmax and Z>check.LimitZmax then
next=false
end
local next = ((check.LimitXmin and math.abs(X)>=math.abs(check.LimitXmin)) or check.LimitXmin==nil) and
((check.LimitXmax and math.abs(X)<=math.abs(check.LimitXmax)) or check.LimitXmax==nil) and
((check.LimitZmin and math.abs(Z)>=math.abs(check.LimitZmin)) or check.LimitZmin==nil) and
((check.LimitZmax and math.abs(Z)<=math.abs(check.LimitZmax)) or check.LimitZmax==nil)
]]
local nextXmin=check.LimitXmin==nil or (check.LimitXmin and (check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin))
local nextXmax=check.LimitXmax==nil or (check.LimitXmax and (check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax))
local nextZmin=check.LimitZmin==nil or (check.LimitZmin and (check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin))
@ -759,12 +834,11 @@ function CARRIERTRAINER:_CheckLimits(X, Z, check)
local next=nextXmin and nextXmax and nextZmin and nextZmax
--self:E({next=next, X=X, Z=Z, check=check})
local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s ||| Z=%d Zmin=%s Zmax=%s",
local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s",
check.name, tostring(next), X, tostring(check.LimitXmin), tostring(check.LimitXmax), Z, tostring(check.LimitZmin), tostring(check.LimitZmax))
self:E(self.lid..text)
MESSAGE:New(text,1):ToAllIf(self.Debug)
self:T(self.lid..text)
--MESSAGE:New(text, 1):ToAllIf(self.Debug)
return next
end
@ -778,11 +852,15 @@ end
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
function CARRIERTRAINER:_NewRound(playerData)
local text=string.format("Welcome back, %s! Cleared for approach. TCN 1X, BRC 354 (MAG HDG).", playerData.callsign)
MESSAGE:New(text, 5):ToClient(playerData.client)
if playerData.unit:IsInZone(self.registerZone) then
local text="Cleared for approach."
self:_SendMessageToPlayer(text, 10,playerData)
self:_InitNewRound(playerData)
playerData.step = 1
self:_InitNewRound(playerData)
-- Next step: start of pattern.
playerData.step = 1
end
end
--- Start landing pattern, when player enters the start zone.
@ -790,16 +868,18 @@ end
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
function CARRIERTRAINER:_Start(playerData)
local hint = string.format("Entering the pattern, %s! Aim for 800 feet and 350-400 kts in the break entry.", playerData.callsign)
self:_SendMessageToPlayer(hint, 8, playerData)
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)
self:_SendMessageToPlayer(hint, 8, playerData)
-- Next step: upwind.
playerData.step = 2
end
-- TODO: Check for correct player heading!
playerData.score = 0
playerData.step = 2
end
--- Upwind leg.
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
@ -819,16 +899,15 @@ function CARRIERTRAINER:_Upwind(playerData)
local altitude=playerData.unit:GetAltitude()
-- Get altutide.
-- Get altitude.
local hint=self:_AltitudeCheck(playerData, self.Upwind, altitude)
self:_SendMessageToPlayer(hint, 8, playerData)
self:_AddToSummary(playerData, hint)
-- Next step.
playerData.step = 3
playerData.step = 3
end
end
@ -847,28 +926,29 @@ function CARRIERTRAINER:_Break(playerData, part)
if part == "late" then
breakpoint = self.BreakLate
end
-- Check abort conditions.
if self:_CheckAbort(diffX, diffZ, breakpoint) then
self:_AbortPattern(playerData, diffX, diffZ, breakpoint)
return
end
-- Check if too far left
--if diffZ < limit then
-- Check limits.
if self:_CheckLimits(diffX, diffZ, breakpoint) then
-- Get current altitude.
local altitude=playerData.unit:GetAltitude()
-- Check altitude.
-- Grade altitude.
local hint=self:_AltitudeCheck(playerData, breakpoint, altitude)
self:_SendMessageToPlayer(hint, 8, playerData)
-- Send message to player.
self:_SendMessageToPlayer(hint, 10, playerData)
-- Add hint to summary.
self:_AddToSummary(playerData, hint)
-- Nest step: late break or abeam.
if (part == "early") then
playerData.step = 4
else
@ -877,10 +957,46 @@ function CARRIERTRAINER:_Break(playerData, part)
end
end
--- Long downwind leg check.
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
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 limit = -1500
-- Check we are not too far out w.r.t back of the boat.
if diffX < limit then
-- Get relative heading.
local relhead=self:_GetRelativeHeading(playerData.unit)
if relhead<45 then
-- Message to player.
local hint = "Your downwind leg is too long. Turn to final earlier next time."
self:_SendMessageToPlayer(hint, 10, playerData)
-- Add to debrief.
self:_AddToSummary(playerData, hint)
-- Decrease score.
playerData.score=playerData.score-40
-- Long downwind done!
playerData.longDownwindDone = true
end
end
end
--- Abeam.
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
function CARRIERTRAINER:_Abeam(playerData)
function CARRIERTRAINER:_Abeam(playerData)
-- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier)
local diffX, diffZ = self:_GetDistances(playerData.unit)
@ -903,18 +1019,22 @@ function CARRIERTRAINER:_Abeam(playerData)
local aoa = playerData.unit:GetAoA()
local alt = playerData.unit:GetAltitude()
-- Grade AoA.
local hintAoA=self:_AoACheck(playerData, self.Abeam, aoa)
-- Check Altitude
-- Grade Altitude.
local hintAlt=self:_AltitudeCheck(playerData, self.Abeam, alt)
-- Check distance.
local hintDist=self:_DistanceCheck(playerData, self.Abeam, diffZ)
-- Grade distance to carrier.
local hintDist=self:_DistanceCheck(playerData, self.Abeam, math.abs(diffZ))
local hintFull=string.format("%s.\n%s.\n%s.", hintAoA, hintAlt, hintDist)
-- Compile full hint.
local hintFull=string.format("%s\n%s\n%s", hintAlt, hintAoA, hintDist)
self:_SendMessageToPlayer(hintFull, 8, playerData )
-- Send message to playerr.
self:_SendMessageToPlayer(hintFull, 10, playerData)
-- Add to debrief.
self:_AddToSummary(playerData, hintFull)
-- Proceed to next step.
@ -922,37 +1042,6 @@ function CARRIERTRAINER:_Abeam(playerData)
end
end
--- Down wind long check.
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
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 limit = -1500
-- Check we are not too far out w.r.t back of the boat.
if diffX < limit then
local relhead=self:_GetRelativeHeading(playerData.unit)
if relhead<45 then
local hint = "Too long downwind. Turn final earlier next time."
self:_SendMessageToPlayer(hint, 8, playerData)
self:_AddToSummary(playerData, hint)
playerData.score=playerData.score-40
-- Long downwind done!
playerData.longDownwindDone = true
end
end
end
--- Ninety.
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
@ -967,24 +1056,34 @@ function CARRIERTRAINER:_Ninety(playerData)
return
end
local limitEast = -1111 --0.6nm
-- Get Realtive heading player to carrier.
local relheading=self:_GetRelativeHeading(playerData.unit)
--if diffZ > limitEast then
if self:_CheckLimits(diffX, diffZ, self.Ninety) then
-- At the 90, i.e. 90 degrees between player heading and BRC of carrier.
if relheading<=90 then
local alt=playerData.unit:GetAltitude()
local aoa=playerData.unit:GetAoA()
-- Grade altitude.
local hintAlt=self:_AltitudeCheck(playerData, self.Ninety, alt)
-- Grade AoA.
local hintAoA=self:_AoACheck(playerData, self.Ninety, aoa)
-- Compile full hint.
local hintFull=string.format("%s\n%s", hintAlt, hintAoA)
local hintFull=string.format("%s.\n%s.", hintAoA, hintAlt)
-- Message to player.
self:_SendMessageToPlayer(hintFull, 10, playerData)
-- Add to debrief.
self:_AddToSummary(playerData, hintFull)
-- Long downwind not an issue any more
playerData.longDownwindDone = true
-- Next step.
-- Next step: wake.
playerData.step = 7
end
end
@ -997,26 +1096,34 @@ 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)
--if (diffZ < -2000 or diffX < -4000 or diffX > 0) then
-- Check abort conditions.
if self:_CheckAbort(diffX, diffZ, self.Wake) then
self:_AbortPattern(playerData, diffX, diffZ, self.Wake)
return
end
--if diffZ > 0 then
-- Right behind the wake of the carrier dZ>0.
if self:_CheckLimits(diffX, diffZ, self.Wake) then
local alt=playerData.unit:GetAltitude()
local aoa=playerData.unit:GetAoA()
-- Grade altitude.
local hintAlt=self:_AltitudeCheck(playerData, self.Wake, alt)
-- Grade AoA.
local hintAoA=self:_AoACheck(playerData, self.Wake, aoa)
-- Compile full hint.
local hintFull=string.format("%s\n%s", hintAlt, hintAoA)
local hintFull=string.format("%s.\n%s.", hintAoA, hintAlt)
-- Message to player.
self:_SendMessageToPlayer(hintFull, 10, playerData)
-- Add to debrief.
self:_AddToSummary(playerData, hintFull)
-- Next step.
-- Next step: Groove.
playerData.step = 8
end
end
@ -1026,58 +1133,51 @@ end
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
function CARRIERTRAINER:_Groove(playerData)
--TODO -100?!
-- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier)
local diffX, diffZ = self:_GetDistances(playerData.unit)
--diffX=diffX+100
-- In front of carrier or more than 4 km behind carrier.
--if (diffX > 0 or diffX < -4000) then
if self:_CheckAbort(diffX, diffZ, self.Groove) then
self:_AbortPattern(playerData, diffX, diffZ, self.Groove)
return
end
-- Get heading of runway.
local brc=self.carrier:GetHeading()
local rwy=brc-10 --runway heading is -10 degree from carrier BRC.
if rwy<0 then
rwy=rwy+360
end
-- Radial (inverse heading).
rwy=rwy-180
--TODO:
if (diffX > -500) then --Reached in close before groove
-- 0 means player is on BRC course but runway heading is -10 degrees.
local heading=self:_GetRelativeHeading(playerData.unit)-10
local hint = "You're too far left and never reached the groove."
self:_SendMessageToPlayer( hint, 8, playerData )
if diffZ>-1300 and heading<10 then
local alt = playerData.unit:GetAltitude()
local aoa = playerData.unit:GetAoA()
-- Grade altitude.
local hintAlt=self:_AltitudeCheck(playerData, self.Groove, alt)
-- AoA feed back
local hintAoA=self:_AoACheck(playerData, self.Groove, aoa)
-- zero score
self:_PrintScore(0, playerData, true)
self:_AddToSummary(playerData, hint)
-- Compile full hint.
local hintFull=string.format("%s\n%s", hintAlt, hintAoA)
-- Message to player.
self:_SendMessageToPlayer(hintFull, 10, playerData)
-- Add to debrief.
self:_AddToSummary(playerData, hintFull)
-- Next step.
playerData.step = 9
else
local limitDeg = 8.0
-- TODO: what is this angle? Does not make sense!
local fraction = diffZ / (-diffX)
local asinValue = math.asin(fraction)
local angle = math.deg(asinValue)
if diffZ > -1300 and angle > limitDeg then
-- Altitude check.
local score, hint=self:_AltitudeCheck(playerData, self.Groove)
-- AoA feed back
local aoa = playerData.unit:GetAoA()
local score, hint=self:_AoACheck(aoa, self.Groove,playerData)
-- TODO
--local fullHint = hint .. " (" .. aoaFeedback .. ")"
--self:_AddToSummary(playerData, fullHint)
-- Next step.
playerData.step = 9
end
end
end
--- Trap.
@ -1086,16 +1186,16 @@ end
function CARRIERTRAINER:_Trap(playerData)
-- 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, rho, phi = self:_GetDistances(playerData.unit)
-- Player altitude
local alt=playerData.unit:GetAltitude()
-- Get velocities.
local playerVelocity = playerData.unit:GetVelocityKMH()
local playerVelocity = playerData.unit:GetVelocityKMH()
local carrierVelocity = self.carrier:GetVelocityKMH()
--if(diffZ < -2000 or diffZ > 2000 or diffX < -3000) then
-- Check abort conditions.
if self:_CheckAbort(diffX, diffZ, self.Trap) then
self:_AbortPattern(playerData, diffX, diffZ, self.Trap)
return
@ -1109,48 +1209,56 @@ function CARRIERTRAINER:_Trap(playerData)
playerData.lowestAltitude = alt
end
if math.abs(playerVelocity-carrierVelocity) < 0.01 then
-- Lineup.
local lineup = math.asin(diffZ/(-(diffX-100)))
local lineuperror = math.deg(lineup)-10
playerData.secondsStandingStill = playerData.secondsStandingStill + 1
-- Glideslope.
local glideslope = math.atan((playerData.unit:GetAltitude()-22)/(-diffX))
local glideslopeError = math.deg(glideslope) - 3.5
if diffX < playerData.highestCarrierXDiff or playerData.secondsStandingStill > 5 then
env.info("Trap identified! diff " .. diffX .. ", highestCarrierXDiff" .. playerData.highestCarrierXDiff .. ", secondsStandingStill: " .. playerData.secondsStandingStill);
local wire = 1
local score = -10
-- Which wire
if(diffX < -14) then
wire = 1
score = -15
elseif(diffX < -3) then
wire = 2
score = 10
elseif (diffX < 10) then
wire = 3
score = 20
else
wire = 4
score = 7
end
self:_IncreaseScore(playerData, score)
self:_SendMessageToPlayer( "TRAPPED! " .. wire .. "-wire!", 30, playerData )
self:_PrintScore(score, playerData, false)
env.info("Distance! " .. diffX .. " meters resulted in a " .. wire .. "-wire estimation.");
local fullHint = "Trapped catching the " .. wire .. "-wire."
self:_AddToSummary(playerData, fullHint)
self:_PrintFinalScore(playerData, 60, wire)
self:_HandleCollectedResult(playerData, wire)
playerData.step = 0
if diffX<100 then
local text="Good height."
if glideslopeError>1 then
text="You're too high! Throttles back!"
elseif glideslopeError>0.5 then
text="You're slightly high. Decrease power."
elseif glideslopeError<1.0 then
text="Power! You're way too low."
elseif glideslopeError<0.5 then
text="You're slightly low. Increase power."
else
end
local aoa=playerData.unit:GetAoA()
if aoa>=9.0 then
text=text.." You're way too slow!"
elseif aoa>=8.5 then
text=text.." You're slow."
elseif aoa<6.9 then
text=text.." You're too fast!"
elseif aoa<7.7 then
text=text.." You're slightly fast."
else
text=text.." Looking good on speed."
end
text=text.."\n"
if lineuperror>3 then
text=text.."Come left!"
elseif lineuperror >1 then
text=text.."Come left..."
elseif lineuperror <3 then
text=text.."Right for lineup!"
elseif lineuperror <1 then
text=text.."Right for lineup.."
else
text=text.."Good on lineup."
end
self:_SendMessageToPlayer(text, 8,playerData)
elseif (diffX > 150) then
local wire = 0
@ -1176,6 +1284,49 @@ function CARRIERTRAINER:_Trap(playerData)
end
end
--- Trapped?
-- @param #CARRIERTRAINER self
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
function CARRIERTRAINER:_Trapped(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)
-- Get velocities.
local playerVelocity = playerData.unit:GetVelocityKMH()
local carrierVelocity = self.carrier:GetVelocityKMH()
if math.abs(playerVelocity-carrierVelocity) < 0.01 then
env.info("Trap identified! diff " .. diffX .. ", highestCarrierXDiff" .. playerData.highestCarrierXDiff .. ", secondsStandingStill: " .. playerData.secondsStandingStill)
local wire = 1
local score = -10
-- Which wire
if(diffX < -14) then
wire = 1
score = -15
elseif(diffX < -3) then
wire = 2
score = 10
elseif (diffX < 10) then
wire = 3
score = 20
else
wire = 4
score = 7
end
self:_SendMessageToPlayer( "TRAPPED! " .. wire .. "-wire!", 30, playerData )
self:_PrintScore(score, playerData, false)
env.info("Distance! " .. diffX .. " meters resulted in a " .. wire .. "-wire estimation.");
local fullHint = "Trapped catching the " .. wire .. "-wire."
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Menu Functions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1364,7 +1515,7 @@ function CARRIERTRAINER:_AltitudeCheck(playerData, checkpoint, altitude)
hint = string.format("Good altitude %s. ", steptext)
end
hint=hint..string.format(" %d deviation from %d ft target alt.", _error, UTILS.MetersToFeet(checkpoint.Altitude))
hint=hint..string.format(" Altitude %d ft = %d%% deviation from %d ft target alt.", UTILS.MetersToFeet(altitude), _error, UTILS.MetersToFeet(checkpoint.Altitude))
-- Set score.
playerData.score=playerData.score+score
@ -1388,7 +1539,6 @@ function CARRIERTRAINER:_DistanceCheck(playerData, checkpoint, distance)
local score
local hint
local dnm=UTILS.MetersToNM(distance)
local steptext=self:_StepName(playerData.step)
if _error>badscore then
score = -10
@ -1407,7 +1557,7 @@ function CARRIERTRAINER:_DistanceCheck(playerData, checkpoint, distance)
hint = string.format("with perfect distance to the boat.")
end
hint=hint..string.format(" %d\% deviation from %d target distance %.1f NM.", _error, UTILS.MetersToNM(checkpoint.Distance))
hint=hint..string.format(" Distance %.1f NM = %d%% deviation from %.1f NM optimal distance.",UTILS.MetersToNM(distance), _error, UTILS.MetersToNM(checkpoint.Distance))
-- Set score.
playerData.score=playerData.score+score
@ -1448,7 +1598,7 @@ function CARRIERTRAINER:_AoACheck(playerData, checkpoint, aoa)
hint = "You're on speed!"
end
hint=hint..string.format(" %d\% deviation from %d target AoA.", _error, checkpoint.AoA)
hint=hint..string.format(" AoA %.1f = %d %% deviation from %.1f target AoA.", aoa, _error, checkpoint.AoA)
-- Set score.
playerData.score=playerData.score+score