mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Added AoA function and carrier training WIP
This commit is contained in:
parent
f35b27451f
commit
fa4abed028
931
Moose Development/Moose/Functional/CarrierTraining.lua
Normal file
931
Moose Development/Moose/Functional/CarrierTraining.lua
Normal file
@ -0,0 +1,931 @@
|
||||
--- **Functional** - (R2.4) - Carrier CASE I Recovery Practice
|
||||
--
|
||||
-- Practice carrier landings.
|
||||
--
|
||||
-- Features:
|
||||
--
|
||||
-- * CASE I recovery.
|
||||
-- * Performance evaluation.
|
||||
-- * Feedback about performance during flight.
|
||||
--
|
||||
-- Please not that his class is work in progress and in an **alpha** stage.
|
||||
-- At the moment training parameters are optimized for F/A-18C Hornet as aircraft and USS Stennis as carrier.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Bankler** (original idea and script)
|
||||
-- ### Co-author: **funkyfranky** (implementation as MOOSE class)
|
||||
--
|
||||
-- @module Functional.CarrierTrainer
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- CARRIERTRAINER class.
|
||||
-- @type CARRIERTRAINER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field Wrapper.Unit#UNIT carrier Aircraft carrier unit on which we want to practice.
|
||||
-- @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.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Practice Carrier Landings
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The Trainer Concept
|
||||
--
|
||||
--
|
||||
-- bla bla
|
||||
--
|
||||
-- @field #CARRIERTRAINER
|
||||
CARRIERTRAINER = {
|
||||
ClassName = "CARRIERTRAINER",
|
||||
carrier = nil,
|
||||
}
|
||||
|
||||
--- Player data.
|
||||
-- @type CARRIERTRAINER.PlayerData
|
||||
-- @field #number id Player ID.
|
||||
-- @field #string callsign Callsign of player.
|
||||
-- @field #number score Player score.
|
||||
-- @field Wrapper.Unit#UNIT unit Aircraft unit of the player.
|
||||
-- @field #number lowestAltitude Lowest altitude.
|
||||
-- @field #number highestCarrierXDiff
|
||||
-- @field #number secondsStandingStill Time player does not move after a landing attempt.
|
||||
|
||||
|
||||
--- Create new carrier trainer.
|
||||
-- @param carriername Name of the aircraft carrier unit.
|
||||
function CARRIERTRAINER:New(carriername)
|
||||
|
||||
-- Set carrier unit.
|
||||
self.carrier=UNIT:FindByName(carriername)
|
||||
|
||||
if self.carrier then
|
||||
self.startZone = ZONE_UNIT:New("startZone", self.carrier, 1000, { dx = -2000, dy = 100, relative_to_unit = true })
|
||||
self.giantZone = ZONE_UNIT:New("giantZone", self.carrier, 30000, { dx = 0, dy = 0, relative_to_unit = true })
|
||||
else
|
||||
self:E("ERROR: Carrier unit could not be found!")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM states
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function CARRIERTRAINER:onafterStart(From, Event, To)
|
||||
|
||||
-- Events are handled my MOOSE.
|
||||
self:T(CARRIERTRAINER.id.."Events are handled by MOOSE.")
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- EVENT functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Carrier trainer event handler for event birth.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CARRIERTRAINER:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
local _unitName=EventData.IniUnitName
|
||||
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
self:T3(CARRIERTRAINER.id.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T3(CARRIERTRAINER.id.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T3(CARRIERTRAINER.id.."BIRTH: player = "..tostring(_playername))
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
local _uid=_unit:GetID()
|
||||
local _group=_unit:GetGroup()
|
||||
local _gid=_group:GetID()
|
||||
local _callsign=_unit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid)
|
||||
self:T(CARRIERTRAINER.id..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
local playerdata={} --#CARRIERTRAINER.PlayerData
|
||||
|
||||
playerdata.callsign=_callsign
|
||||
|
||||
-- By default, some bomb impact points and do not flare each hit on target.
|
||||
self.Player[_playername]=playerdata
|
||||
|
||||
-- Start check in zone timer.
|
||||
if self.planes[_uid] ~= true then
|
||||
SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1)
|
||||
self.planes[_uid] = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Carrier trainer event handler for event birth.
|
||||
-- @param #CARRIERTRAINER self
|
||||
function CARRIERTRAINER:_CheckPlayerStatus()
|
||||
|
||||
-- Loop over all players.
|
||||
for _playerName,_playerData in pairs(self.Player) do
|
||||
local playerData = _playerData --#CARRIERTRAINER.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Player unit.
|
||||
local unit = playerData.unit
|
||||
|
||||
if unit:IsAlive() then
|
||||
|
||||
if unit:IsInZone(self.giantZone) then
|
||||
--Tick(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 == 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
|
||||
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
|
||||
--playerDatas[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- CARRIER TRAINING functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Initialize player data.
|
||||
-- @param #CARRIERTRAINER self
|
||||
function CARRIERTRAINER:_Init(id)
|
||||
local playerData = nil
|
||||
|
||||
local existingData = playerDatas[id]
|
||||
if(existingData and existingData.unit:IsAlive()) then
|
||||
playerData = playerDatas[id]
|
||||
else
|
||||
playerData = PlayerData:New(id)
|
||||
end
|
||||
|
||||
playerData:InitNewRound()
|
||||
|
||||
playerDatas[id] = playerData
|
||||
env.info("Created playerData object for " .. playerData.unit.UnitName)
|
||||
|
||||
MessageToAll( "Pilot ID: " .. id .. ". Welcome back, " .. playerData.callsign .. "! Cleared for approach! TCN 1X, BRC 354 (MAG HDG).", 5, "InitZoneMessage" )
|
||||
|
||||
playerData.step = 1 -- 1 !!
|
||||
playerData.highestCarrierXDiff = -9999999
|
||||
playerData.secondsStandingStill = 0
|
||||
playerData.summary = "SUMMARY:\n"
|
||||
end
|
||||
|
||||
--- Start landing pattern.
|
||||
-- @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."
|
||||
self:_SendMessageToPlayer(hint, 8, playerData)
|
||||
playerData.score = 0
|
||||
playerData.step = 2
|
||||
end
|
||||
|
||||
--- Upwind leg.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Upwind(playerData)
|
||||
|
||||
-- Player and carrier position vector.
|
||||
local position = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local diffZ = position.z - carrierPosition.z
|
||||
local diffX = position.x - carrierPosition.x
|
||||
if(diffZ > 500 or diffZ < 0 or diffX < -4000) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
if (diffX < 0) then
|
||||
return
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
playerData:IncreaseScore(score)
|
||||
|
||||
self:_SendMessageToPlayer(hint, 8, playerData)
|
||||
|
||||
self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData)
|
||||
self:_PrintScore(score, playerData, true)
|
||||
|
||||
playerData:AddToSummary(hint)
|
||||
playerData.step = 3
|
||||
end
|
||||
|
||||
--- Break.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
-- @param #string part Part of the break.
|
||||
function CARRIERTRAINER:_Break(playerData, part)
|
||||
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
local diffX = playerPosition.x - carrierPosition.x
|
||||
|
||||
if(diffZ > 1500 or diffZ < -3700 or diffX < -500) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
local limit = -370
|
||||
|
||||
if (part == "late") then
|
||||
limit = -1470
|
||||
end
|
||||
|
||||
if diffZ < limit then
|
||||
local idealAltitude = 800
|
||||
local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) )
|
||||
|
||||
local hint = ""
|
||||
local score = 0
|
||||
|
||||
if(altitude > 880) then
|
||||
score = 5
|
||||
hint = "You're high in the " .. part .. " break."
|
||||
elseif(altitude > 850) then
|
||||
score = 7
|
||||
hint = "You're slightly high in the " .. part .. " break."
|
||||
elseif (altitude < 720) then
|
||||
score = 5
|
||||
hint = "You're low in the " .. part .. " break."
|
||||
elseif (altitude < 750) then
|
||||
score = 7
|
||||
hint = "You're slightly low in the " .. part .. " break."
|
||||
else
|
||||
score = 10
|
||||
hint = "Good altitude in the " .. part .. " break!"
|
||||
end
|
||||
|
||||
playerData:IncreaseScore(score)
|
||||
|
||||
self:_SendMessageToPlayer( hint, 8, playerData )
|
||||
self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData)
|
||||
self:_PrintScore(score, playerData, true)
|
||||
|
||||
playerData:AddToSummary(hint)
|
||||
|
||||
if (part == "early") then
|
||||
playerData.step = 4
|
||||
else
|
||||
playerData.step = 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Break.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Abeam(playerData)
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
local diffX = playerPosition.x - carrierPosition.x
|
||||
if(diffZ > -1000 or diffZ < -3700) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
local limit = -200
|
||||
|
||||
if diffX < limit then
|
||||
--local aoa = math.deg(mist.getAoA(playerData.mistUnit))
|
||||
local aoa = playerData.unit:GetAoA()
|
||||
local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData)
|
||||
|
||||
local onSpeedScore = self:_GetOnSpeedScore(aoa)
|
||||
|
||||
local idealAltitude = 600
|
||||
local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) )
|
||||
|
||||
local hint = ""
|
||||
local score = 0
|
||||
|
||||
if(altitude > 700) then
|
||||
score = 5
|
||||
hint = "You're high (" .. altitude .. " ft) abeam"
|
||||
elseif(altitude > 650) then
|
||||
score = 7
|
||||
hint = "You're slightly high (" .. altitude .. " ft) abeam"
|
||||
elseif (altitude < 540) then
|
||||
score = 5
|
||||
hint = "You're low (" .. altitude .. " ft) abeam"
|
||||
elseif (altitude < 570) then
|
||||
score = 7
|
||||
hint = "You're slightly low (" .. altitude .. " ft) abeam"
|
||||
else
|
||||
score = 10
|
||||
hint = "Good altitude (" .. altitude .. " ft) abeam"
|
||||
end
|
||||
|
||||
local distanceHint = ""
|
||||
local distanceScore
|
||||
local diffEast = carrierPosition.z - playerPosition.z
|
||||
|
||||
local nm = diffEast / 1852 --nm conversion
|
||||
local idealDistance = 1.2
|
||||
|
||||
local roundedNm = UTILS.Round(nm, 2)
|
||||
|
||||
if(nm < 1.0) then
|
||||
distanceScore = 0
|
||||
distanceHint = "too close to the boat (" .. roundedNm .. " nm)"
|
||||
elseif(nm < 1.1) then
|
||||
distanceScore = 5
|
||||
distanceHint = "slightly too close to the boat (" .. roundedNm .. " nm)"
|
||||
elseif(nm < 1.3) then
|
||||
distanceScore = 10
|
||||
distanceHint = "with perfect distance to the boat (" .. roundedNm .. " nm)"
|
||||
elseif(nm < 1.4) then
|
||||
distanceScore = 5
|
||||
distanceHint = "slightly too far from the boat (" .. roundedNm .. " nm)"
|
||||
else
|
||||
distanceScore = 0
|
||||
distanceHint = "too far from the boat (" .. roundedNm .. " nm)"
|
||||
end
|
||||
|
||||
local fullHint = hint .. ", " .. distanceHint
|
||||
|
||||
self:_SendMessageToPlayer( fullHint, 8, playerData )
|
||||
self:_SendMessageToPlayer( "(Target: 600 ft and 1.2 nm).", 8, playerData )
|
||||
|
||||
playerData:IncreaseScore(score + distanceScore + onSpeedScore)
|
||||
self:_PrintScore(score + distanceScore + onSpeedScore, playerData, true)
|
||||
|
||||
playerData:AddToSummary(fullHint .. " (" .. aoaFeedback .. ")")
|
||||
playerData.step = 6
|
||||
end
|
||||
end
|
||||
|
||||
--- Down wind long check.
|
||||
-- @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
|
||||
|
||||
if carrierPosition.x - playerPosition.x > limit then
|
||||
--local heading = math.deg(mist.getHeading(playerData.mistUnit))
|
||||
local heading = playerData.unit:GetHeading()
|
||||
|
||||
if(heading > 170) then
|
||||
local hint = "Too long downwind. Turn final earlier next time."
|
||||
self:_SendMessageToPlayer( hint, 8, playerData )
|
||||
local score = -40
|
||||
playerData:IncreaseScore(score)
|
||||
self:_PrintScore(score, playerData, true)
|
||||
playerData:AddToSummary(hint)
|
||||
playerData.longDownwindDone = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Ninety.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Ninety(playerData)
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
local diffX = playerPosition.x - carrierPosition.x
|
||||
if(diffZ < -3700 or diffX < -3700 or diffX > 0) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
local limitEast = -1111 --0.6nm
|
||||
|
||||
if diffZ > limitEast then
|
||||
local idealAltitude = 500
|
||||
local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) )
|
||||
|
||||
local hint = ""
|
||||
local score = 0
|
||||
|
||||
if(altitude > 600) then
|
||||
score = 5
|
||||
hint = "You're high at the 90."
|
||||
elseif(altitude > 550) then
|
||||
score = 7
|
||||
hint = "You're slightly high at the 90."
|
||||
elseif (altitude < 380) then
|
||||
score = 5
|
||||
hint = "You're low at the 90."
|
||||
elseif (altitude < 420) then
|
||||
score = 7
|
||||
hint = "You're slightly low at the 90."
|
||||
else
|
||||
score = 10
|
||||
hint = "Good altitude at the 90!"
|
||||
end
|
||||
|
||||
self:_SendMessageToPlayer( hint, 8, playerData )
|
||||
self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData)
|
||||
|
||||
--local aoa = math.deg(mist.getAoA(playerData.mistUnit))
|
||||
local aoa = playerData.unit:GetAoA()
|
||||
local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData)
|
||||
|
||||
local onSpeedScore = self:_GetOnSpeedScore(aoa)
|
||||
|
||||
playerData:IncreaseScore(score + onSpeedScore)
|
||||
self:_PrintScore(score + onSpeedScore, playerData, true)
|
||||
|
||||
playerData:AddToSummary(hint .. " (" .. aoaFeedback .. ")")
|
||||
|
||||
playerData.longDownwindDone = true
|
||||
playerData.step = 7
|
||||
end
|
||||
end
|
||||
|
||||
--- Wake.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Wake(playerData)
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = carrier:GetVec3()
|
||||
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
local diffX = playerPosition.x - carrierPosition.x
|
||||
if(diffZ < -2000 or diffX < -4000 or diffX > 0) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
if diffZ > 0 then
|
||||
local idealAltitude = 370
|
||||
local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) )
|
||||
|
||||
local hint = ""
|
||||
local score = 0
|
||||
|
||||
if(altitude > 500) then
|
||||
score = 5
|
||||
hint = "You're high at the wake."
|
||||
elseif(altitude > 450) then
|
||||
score = 7
|
||||
hint = "You're slightly high at the wake."
|
||||
elseif (altitude < 300) then
|
||||
score = 5
|
||||
hint = "You're low at the wake."
|
||||
elseif (altitude < 340) then
|
||||
score = 7
|
||||
hint = "You're slightly low at the wake."
|
||||
else
|
||||
score = 10
|
||||
hint = "Good altitude at the wake!"
|
||||
end
|
||||
|
||||
self:_SendMessageToPlayer( hint, 8, playerData )
|
||||
self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData)
|
||||
|
||||
--local aoa = math.deg(mist.getAoA(playerData.mistUnit))
|
||||
local aoa = playerData.unit:GetAoA()
|
||||
local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData)
|
||||
|
||||
local onSpeedScore = self:_GetOnSpeedScore(aoa)
|
||||
|
||||
playerData:IncreaseScore(score + onSpeedScore)
|
||||
self:_PrintScore(score + onSpeedScore, playerData, true)
|
||||
|
||||
playerData:AddToSummary(hint .. " (" .. aoaFeedback .. ")")
|
||||
playerData.step = 8
|
||||
end
|
||||
end
|
||||
|
||||
--- Groove.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Groove(playerData)
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local diffX = playerPosition.x - (carrierPosition.x - 100)
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
|
||||
if(diffX > 0 or diffX < -4000) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
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)
|
||||
playerData:AddToSummary(hint)
|
||||
playerData.step = 9
|
||||
else
|
||||
local limitDeg = 8.0
|
||||
|
||||
local fraction = diffZ / (-diffX)
|
||||
local asinValue = math.asin(fraction)
|
||||
local angle = math.deg(asinValue)
|
||||
|
||||
if diffZ > -1300 and angle > limitDeg then
|
||||
local idealAltitude = 300
|
||||
local altitude = UTILS.Round( UTILS.MetersToFeet( playerPosition.y ) )
|
||||
|
||||
local hint = ""
|
||||
local score = 0
|
||||
|
||||
if (altitude > 450) then
|
||||
score = 5
|
||||
hint = "You're high in the groove."
|
||||
elseif (altitude > 350) then
|
||||
score = 7
|
||||
hint = "You're slightly high in the groove."
|
||||
elseif (altitude < 240) then
|
||||
score = 5
|
||||
hint = "You're low in the groove."
|
||||
elseif (altitude < 270) then
|
||||
score = 7
|
||||
hint = "You're slightly low in the groove."
|
||||
else
|
||||
score = 10
|
||||
hint = "Good altitude in the groove!"
|
||||
end
|
||||
|
||||
self:_SendMessageToPlayer(hint, 8, playerData)
|
||||
self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData)
|
||||
|
||||
--local aoa = math.deg(mist.getAoA(playerData.mistUnit))
|
||||
local aoa = playerData.unit:GetAoA()
|
||||
local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData)
|
||||
|
||||
local onSpeedScore = self:_GetOnSpeedScore(aoa)
|
||||
|
||||
playerData:IncreaseScore(score + onSpeedScore)
|
||||
self:_PrintScore(score + onSpeedScore, playerData, true)
|
||||
|
||||
local fullHint = hint .. " (" .. aoaFeedback .. ")"
|
||||
|
||||
playerData:AddToSummary(fullHint)
|
||||
|
||||
playerData.step = 9
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Trap.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data table.
|
||||
function CARRIERTRAINER:_Trap(playerData)
|
||||
local playerPosition = playerData.unit:GetVec3()
|
||||
local carrierPosition = self.carrier:GetVec3()
|
||||
|
||||
local playerVelocity = playerData.unit:GetVelocityKMH()
|
||||
local carrierVelocity = self.carrier:GetVelocityKMH()
|
||||
|
||||
local diffZ = playerPosition.z - carrierPosition.z
|
||||
local diffX = playerPosition.x - carrierPosition.x
|
||||
|
||||
if(diffZ < -2000 or diffZ > 2000 or diffX < -3000) then
|
||||
self:_AbortPattern(playerData)
|
||||
return
|
||||
end
|
||||
|
||||
if(diffX > playerData.highestCarrierXDiff) then
|
||||
playerData.highestCarrierXDiff = diffX
|
||||
end
|
||||
|
||||
if(playerPosition.y < playerData.lowestAltitude) then
|
||||
playerData.lowestAltitude = playerPosition.y
|
||||
end
|
||||
|
||||
if math.abs(playerVelocity - carrierVelocity) < 0.01 then
|
||||
playerData.secondsStandingStill = playerData.secondsStandingStill + 1
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
playerData:IncreaseScore(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."
|
||||
|
||||
playerData:AddToSummary(fullHint)
|
||||
|
||||
self:_PrintFinalScore(playerData, 60, wire)
|
||||
self:_HandleCollectedResult(playerData, wire)
|
||||
playerData.step = 0
|
||||
end
|
||||
|
||||
elseif (diffX > 150) then
|
||||
|
||||
local wire = 0
|
||||
local hint = ""
|
||||
local score = 0
|
||||
if(playerData.lowestAltitude < 23) then
|
||||
hint = "You boltered."
|
||||
else
|
||||
hint = "You were waved off."
|
||||
wire = -1
|
||||
score = -10
|
||||
end
|
||||
|
||||
self:_SendMessageToPlayer( hint, 8, playerData )
|
||||
self:_PrintScore(score, playerData, true)
|
||||
|
||||
playerData:AddToSummary(hint)
|
||||
|
||||
self:_PrintFinalScore(playerData, 60, wire)
|
||||
self:_HandleCollectedResult(playerData, wire)
|
||||
|
||||
playerData.step = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- MISC functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Send message about altitude feedback.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number altitude Current altitude of the player.
|
||||
-- @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 )
|
||||
end
|
||||
|
||||
--- Score for correct AoA.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number AoA Angle of attack.
|
||||
function CARRIERTRAINER:_GetOnSpeedScore(AoA)
|
||||
local score = 0
|
||||
if(AoA > 9.5) then --Slow
|
||||
score = 0
|
||||
elseif(AoA > 9) then --Slightly slow
|
||||
score = 5
|
||||
elseif(AoA > 7.25) then --On speed
|
||||
score = 10
|
||||
elseif(AoA > 6.7) then --Slightly fast
|
||||
score = 5
|
||||
else --Fast
|
||||
score = 0
|
||||
end
|
||||
|
||||
return score
|
||||
end
|
||||
|
||||
--- Print AoA feedback.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number AoA Angle of attack.
|
||||
-- @param #number idealAoA Ideal AoA.
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
-- @return #string Feedback hint.
|
||||
function CARRIERTRAINER:_PrintAoAFeedback(AoA, idealAoA, playerData)
|
||||
|
||||
local hint = ""
|
||||
if(AoA > 9.5) then
|
||||
hint = "You're slow."
|
||||
elseif(AoA > 9) then
|
||||
hint = "You're slightly slow."
|
||||
elseif(AoA > 7.25) then
|
||||
hint = "You're on speed!"
|
||||
elseif(AoA > 6.7) then
|
||||
hint = "You're slightly fast."
|
||||
else
|
||||
hint = "You're fast."
|
||||
end
|
||||
|
||||
local roundedAoA = UTILS.Round(AoA, 2)
|
||||
|
||||
self:_SendMessageToPlayer(hint .. " AOA: " .. roundedAoA .. " (Target: " .. idealAoA .. ")", 8, playerData)
|
||||
|
||||
return hint
|
||||
end
|
||||
|
||||
--- Send message to playe client.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #string message The message to send.
|
||||
-- @param #number duration Display message duration.
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
function CARRIERTRAINER:_SendMessageToPlayer(message, duration, playerData)
|
||||
if playerData.client then
|
||||
MESSAGE:New( message, duration, ""):ToClient(playerData.client)
|
||||
end
|
||||
end
|
||||
|
||||
--- Send message to playe client.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number score Score.
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
function CARRIERTRAINING:_PrintScore(score, playerData, alsoPrintTotalScore)
|
||||
if(alsoPrintTotalScore) then
|
||||
self:_SendMessageToPlayer( "Score: " .. score .. " (Total: " .. playerData.score .. ")", 8, playerData )
|
||||
else
|
||||
self:_SendMessageToPlayer( "Score: " .. score, 8, playerData )
|
||||
end
|
||||
end
|
||||
|
||||
--- Display final score.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
-- @param #number duration Duration for message display.
|
||||
function CARRIERTRAINER:_PrintFinalScore(playerData, duration, wire)
|
||||
local wireText = ""
|
||||
if(wire == -2) then
|
||||
wireText = "Aborted approach"
|
||||
elseif(wire == -1) then
|
||||
wireText = "Wave-off"
|
||||
elseif(wire == 0) then
|
||||
wireText = "Bolter"
|
||||
else
|
||||
wireText = wire .. "-wire"
|
||||
end
|
||||
|
||||
MessageToAll( playerData.callsign .. " - Final score: " .. playerData.score .. " / 140 (" .. wireText .. ")", duration, "FinalScore" )
|
||||
self:_SendMessageToPlayer( playerData.summary, duration, playerData )
|
||||
end
|
||||
|
||||
--- Collect result.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
-- @param #number wire Trapped wire.
|
||||
function CARRIERTRAINER:_HandleCollectedResult(playerData, wire)
|
||||
local newString = ""
|
||||
if(wire == -2) then
|
||||
newString = playerData.score .. " (Aborted)"
|
||||
elseif(wire == -1) then
|
||||
newString = playerData.score .. " (Wave-off)"
|
||||
elseif(wire == 0) then
|
||||
newString = playerData.score .. " (Bolter)"
|
||||
else
|
||||
newString = playerData.score .. " (" .. wire .."W)"
|
||||
end
|
||||
|
||||
playerData.totalScore = playerData.totalScore + playerData.score
|
||||
playerData.passes = playerData.passes + 1
|
||||
|
||||
if(playerData.collectedResultString == "") then
|
||||
playerData.collectedResultString = newString
|
||||
else
|
||||
playerData.collectedResultString = playerData.collectedResultString .. ", " .. newString
|
||||
MessageToAll( playerData.callsign .. "'s " .. playerData.passes .. " passes: " .. playerData.collectedResultString .. " (TOTAL: " .. playerData.totalScore .. ")" , 30, "CollectedResult" )
|
||||
end
|
||||
|
||||
self:_SendMessageToPlayer( "Return south 4 nm (over the trailing ship), towards WP 1, to restart the pattern.", 20, playerData )
|
||||
end
|
||||
|
||||
--- Pattern aborted.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #CARRIERTRAINER.PlayerData playerData Player data.
|
||||
function CARRIERTRAINER:_AbortPattern(playerData)
|
||||
self:_SendMessageToPlayer( "You're too far from where you should be. Abort approach!", 15, playerData )
|
||||
playerData:AddToSummary("Approach aborted.")
|
||||
self:_PrintFinalScore(playerData, 30, -2)
|
||||
self:_HandleCollectedResult(playerData, -2)
|
||||
playerData.step = 0
|
||||
end
|
||||
|
||||
--- Get the formatted score.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number score Score of player.
|
||||
-- @param #number maxScore Max score possible.
|
||||
-- @return #string Formatted score text.
|
||||
function CARRIERTRAINER:_GetFormattedScore(score, maxScore)
|
||||
if(score < maxScore) then
|
||||
return " (" .. score .. " points)."
|
||||
else
|
||||
return " (" .. score .. " points)!"
|
||||
end
|
||||
end
|
||||
|
||||
--- Get distance feedback.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #number distance Distance to boat.
|
||||
-- @param #number idealDistance Ideal distance.
|
||||
-- @return #string Feedback text.
|
||||
function CARRIERTRAINER:_GetDistanceFeedback(distance, idealDistance)
|
||||
return distance .. " nm (Target: " .. idealDistance .. " nm)"
|
||||
end
|
||||
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #CARRIERTRAINER self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function CARRIERTRAINER:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
local playername=DCSunit:getPlayerName()
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
if DCSunit and unit and playername then
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
|
||||
@ -657,5 +657,26 @@ function UTILS.Randomize(value, fac, lower, upper)
|
||||
return r
|
||||
end
|
||||
|
||||
--- Calculate the dot (scalar) product of two vectors.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x,y,z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x,y,z components.
|
||||
-- @return #number Scalar product of the two vectors a*b.
|
||||
function UTILS.VecDot(a, b)
|
||||
return a.x*b.x + a.y*b.y + a.z*b.z
|
||||
end
|
||||
|
||||
--- Calculate the eucledian norm (length) of a 3D vector.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x,y,z components.
|
||||
-- @return #number Norm of the vector.
|
||||
function UTILS.VecNorm(a)
|
||||
return math.sqrt(UTILS.DotProduct(a, a))
|
||||
end
|
||||
|
||||
--- Calculate the cross product of two 3D vectors.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x,y,z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x,y,z components.
|
||||
-- @return DCS#Vec3 Vector
|
||||
function UTILS.VecCross(a, b)
|
||||
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
|
||||
end
|
||||
|
||||
|
||||
@ -121,6 +121,25 @@ function POSITIONABLE:Destroy( GenerateEvent )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns a pos3 table of the objects current position and orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation.
|
||||
-- Coordinates are dependent on the position of the maps origin.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Position Table consisting of the point and orientation tables.
|
||||
function POSITIONABLE:GetPosition()
|
||||
self:F2( self.PositionableName )
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
local PositionablePosition = DCSPositionable:getPosition()
|
||||
self:T3( PositionablePosition )
|
||||
return PositionablePosition
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetPositionVec3", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
|
||||
@ -582,6 +601,46 @@ function POSITIONABLE:GetVelocityMPS()
|
||||
return 0
|
||||
end
|
||||
|
||||
--- Returns the Angle of Attack of a positionable.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Angle of attack 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:GetVelocity()
|
||||
|
||||
if unitvel and UTILS.VecNorm(unitvel)~=0 then
|
||||
|
||||
-- Unit velocity transformed into aircraft axes directions.
|
||||
local AxialVel = {}
|
||||
|
||||
-- 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)
|
||||
|
||||
-- AoA is angle between unitpos.x and the x and y velocities.
|
||||
local AoA = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/UTILS.VecNorm({x = AxialVel.x, y = AxialVel.y, z = 0}))
|
||||
|
||||
--Set correct direction:
|
||||
if AxialVel.y > 0 then
|
||||
AoA = -AoA
|
||||
end
|
||||
|
||||
-- Return AoA value in degrees.
|
||||
return math.deg(AoA)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns the message text with the callsign embedded (if there is one).
|
||||
-- @param #POSITIONABLE self
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user