From b923f66c564b03e36ebde74ee7cb8610abf9e0e2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Oct 2018 22:27:30 +0200 Subject: [PATCH 1/7] CT v0.0.6 --- .../Moose/Functional/CarrierTrainer.lua | 1646 +++++++++++++++++ 1 file changed, 1646 insertions(+) create mode 100644 Moose Development/Moose/Functional/CarrierTrainer.lua diff --git a/Moose Development/Moose/Functional/CarrierTrainer.lua b/Moose Development/Moose/Functional/CarrierTrainer.lua new file mode 100644 index 000000000..173f80fb3 --- /dev/null +++ b/Moose Development/Moose/Functional/CarrierTrainer.lua @@ -0,0 +1,1646 @@ +--- **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. +-- Other aircraft and carriers **might** be possible in future but would need a different set of parameters. +-- +-- === +-- +-- ### Author: **Bankler** (original idea and script) +-- +-- @module Functional.CarrierTrainer +-- @image MOOSE.JPG + +--- CARRIERTRAINER class. +-- @type CARRIERTRAINER +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @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 #table players Table of players. +-- @field #table menuadded Table of units where the F10 radio menu was added. +-- @field #CARRIERTRAINER.Checkpoint Upwind Upwind checkpoint. +-- @field #CARRIERTRAINER.Checkpoint BreakEarly Early break checkpoint. +-- @field #CARRIERTRAINER.Checkpoint BreakLate Late brak checkpoint. +-- @field #CARRIERTRAINER.Checkpoint Abeam Abeam checkpoint. +-- @field #CARRIERTRAINER.Checkpoint Ninety At the ninety checkpoint. +-- @field #CARRIERTRAINER.Checkpoint Wake Right behind the carrier. +-- @field #CARRIERTRAINER.Checkpoint Groove In the groove checkpoint. +-- @field #CARRIERTRAINER.Checkpoint Trap Landing checkpoint. +-- @extends Core.Fsm#FSM + +--- Practice Carrier Landings +-- +-- === +-- +-- ![Banner Image](..\Presentations\CARRIERTRAINER\CarrierTrainer_Main.png) +-- +-- # The Trainer Concept +-- +-- +-- bla bla +-- +-- @field #CARRIERTRAINER +CARRIERTRAINER = { + ClassName = "CARRIERTRAINER", + lid = nil, + Debug = true, + carrier = nil, + carriertype = nil, + alias = nil, + startZone = nil, + giantZone = nil, + players = {}, + menuadded = {}, + Upwind = {}, + Abeam = {}, + BreakEarly = {}, + BreakLate = {}, + Ninety = {}, + Wake = {}, + Groove = {}, + Trap = {}, +} + +--- Aircraft types. +-- @type CARRIERTRAINER.AircraftType +-- @field #string AV8B AV-8B Night Harrier. +-- @field #string HORNET F/A-18C Lot 20 Hornet. +CARRIERTRAINER.AircraftType={ + AV8B="AV8BNA", + HORNET="FA-18C_hornet", +} + +--- Carrier types. +-- @type CARRIERTRAINER.CarrierType +-- @field #string STENNIS USS John C. Stennis (CVN-74) +-- @field #string VINSON USS Carl Vinson (CVN-70) +-- @field #string TARAWA USS Tarawa (LHA-1) +-- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) +CARRIERTRAINER.CarrierType={ + STENNIS="Stennis", + VINSON="Vinson", + TARAWA="LHA_Tarawa", + KUZNETSOV="KUZNECOW" +} + +--- Difficulty level. +-- @type CARRIERTRAINER.Difficulty +-- @field #string EASY Easy difficulty: error margin 10 for high score and 20 for low score. No score for deviation >20. +-- @field #string NORMAL Normal difficulty: error margin 5 deviation from ideal for high score and 10 for low score. No score for deviation >10. +-- @field #string HARD Hard difficulty: error margin 2.5 deviation from ideal value for high score and 5 for low score. No score for deviation >5. +CARRIERTRAINER.Difficulty={ + EASY="Easy", + NORMAL="Normal", + HARD="Hard", +} + +--- Player data table holding all important parameters for each player. +-- @type CARRIERTRAINER.PlayerData +-- @field #number id Player ID. +-- @field #string callsign Callsign of player. +-- @field #number score Player score. +-- @field #number totalscore Score of all landing attempts. +-- @field #number passes Number of passes. +-- @field #string collectedResultString Results text of all passes. +-- @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. +-- @field #string summary Result summary text. +-- @field Wrapper.Client#CLIENT Client object of player. +-- @field #string difficulty Difficulty level. + +--- Checkpoint parameters triggering the next step in the pattern. +-- @type CARRIERTRAINER.Checkpoint +-- @field #number Xmin Minimum allowed longitual distance to carrier. +-- @field #number Xmax Maximum allowed longitual distance to carrier. +-- @field #number Zmin Minimum allowed latitudal distance to carrier. +-- @field #number Zmax Maximum allowed latitudal distance to carrier. +-- @field #number LimitXmin Latitudal threshold for triggering the next step if XXmax. +-- @field #number LimitZmin Latitudal threshold for triggering the next step if ZZmax. +-- @field #number Altitude Optimal altitude at this point. +-- @field #number AoA Optimal AoA at this point. +-- @field #number Distance Optimal distance at this point. +-- @field #number Speed Optimal speed at this point. +-- @field #table Checklist Table of checklist text items to display at this point. + +--- Main radio menu. +-- @field #table MenuF10 +CARRIERTRAINER.MenuF10={} + +--- Carrier trainer class version. +-- @field #string version +CARRIERTRAINER.version="0.0.6" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create new carrier trainer. +-- @param #CARRIERTRAINER self +-- @param carriername Name of the aircraft carrier unit as defined in the mission editor. +-- @param alias (Optional) Alias for the carrier. This will be used for radio messages and the F10 radius menu. Default is the carrier name as defined in the mission editor. +-- @return #CARRIERTRAINER self +function CARRIERTRAINER:New(carriername, alias) + + -- Inherit everthing from FSM class. + local self = BASE:Inherit(self, FSM:New()) -- #CARRIERTRAINER + + -- 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 + local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.", carriername) + MESSAGE:New(text, 120):ToAll() + self:E(self.lid..text) + return nil + end + + --CARRIERTRAINER.Difficulty.EASY + + -- Set some string id for output to DCS.log file. + self.lid=string.format("CARRIERTRAINER %s | ", carriername) + + -- Get carrier type. + self.carriertype=self.carrier:GetTypeName() + + -- Set alias. + self.alias=alias or carriername + + ----------------------- + --- FSM Transitions --- + ----------------------- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Running", "Status", "Running") + self:AddTransition("Running", "Stop", "Stopped") + + + --- Triggers the FSM event "Start" that starts the carrier trainer. Initializes parameters and starts event handlers. + -- @function [parent=#CARRIERTRAINER] Start + -- @param #CARRIERTRAINER self + + --- Triggers the FSM event "Start" after a delay that starts the carrier trainer. Initializes parameters and starts event handlers. + -- @function [parent=#CARRIERTRAINER] __Start + -- @param #CARRIERTRAINER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop" that stops the carrier trainer. Event handlers are stopped. + -- @function [parent=#CARRIERTRAINER] Stop + -- @param #CARRIERTRAINER self + + --- Triggers the FSM event "Stop" that stops the carrier trainer after a delay. Event handlers are stopped. + -- @function [parent=#CARRIERTRAINER] __Stop + -- @param #CARRIERTRAINER self + -- @param #number delay Delay in seconds. + + return self +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:I(self.lid..string.format("Starting Carrier Training %s for carrier unit %s of type %s.", CARRIERTRAINER.version, self.carrier:GetName(), self.carriertype)) + + -- Handle events. + self:HandleEvent(EVENTS.Birth) + + -- Init status check + self:__Status(5) +end + +--- On after Status event. Checks player status. +-- @param #CARRIERTRAINER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function CARRIERTRAINER:onafterStatus(From, Event, To) + + -- Check player status. + self:_CheckPlayerStatus() + + -- Call status again in one second. + self:__Status(-1) +end + +--- On after Stop event. Unhandle events and stop status updates. +-- @param #CARRIERTRAINER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function CARRIERTRAINER:onafterStop(From, Event, To) + self:UnHandleEvent(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(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) + self:T3(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) + self:T3(self.lid.."BIRTH: 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 entered unit %s (ID=%d) of group %s", _playername, _callsign, _unitName, _uid, _group:GetName()) + self:T(self.lid..text) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + + -- Add Menu commands. + self:_AddF10Commands(_unitName) + + -- Init player. + if self.players[_playername]==nil then + self.players[_playername]=self:_InitNewPlayer(_unitName) + else + self:_InitNewRound(self.players[_playername]) + end + + end +end + +--- Initialize player data. +-- @param #CARRIERTRAINER self +-- @param #string unitname Name of the player unit. +-- @return #CARRIERTRAINER.PlayerData Player data. +function CARRIERTRAINER:_InitNewPlayer(unitname) + + local playerData={} --#CARRIERTRAINER.PlayerData + + playerData.unit = UNIT:FindByName(unitname) + playerData.client = CLIENT:FindByName(playerData.unit.UnitName, nil, true) + playerData.callsign = playerData.unit:GetCallsign() + playerData.totalScore = 0 + playerData.passes = 0 + playerData.collectedResultString = "" + + playerData=self:_InitNewRound(playerData) + + return playerData +end + +--- Initialize new approach for player by resetting parmeters to initial values. +-- @param #CARRIERTRAINER self +-- @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.longDownwindDone = false + playerData.highestCarrierXDiff = -9999999 + playerData.secondsStandingStill = 0 + playerData.lowestAltitude = 999999 + return playerData +end + +--- Increase score for this approach. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data. +-- @param #number amount Amount by which the score is increased. +function CARRIERTRAINER:_IncreaseScore(playerData, amount) + playerData.score = playerData.score + amount +end + +--- Append text to summary text. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data. +-- @param #string item Text item appeded to the summary. +function CARRIERTRAINER:_AddToSummary(playerData, item) + playerData.summary = playerData.summary .. item .. "\n" +end + +--- Append text to result text. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data. +-- @param #string item Text item appeded to the result. +function CARRIERTRAINER:_AddToCollectedResult(playerData, item) + playerData.collectedResultString = playerData.collectedResultString .. item .. "\n" +end + +--- Get relative heading of player wrt carrier. +-- @param #CARRIERTRAINER self +-- @param Wrapper.Unit#UNIT unit Player unit. +-- @return #number Relative heading in degrees. +function CARRIERTRAINER:_GetRelativeHeading(unit) + --local a=self.carrier:GetVec3() + --local b=unit:GetVec3() + --local c={x=b.x-a.x, y=0, z=b.z-a.z} + --local headingCarrier=self.carrier:GetHeading() + --local headingPlayer=unit:GetHeading() + local vC=self.carrier:GetOrientationX() + local vP=unit:GetOrientationX() + + -- Get angle between the two orientation vectors in rad. + local relHead=math.acos(UTILS.VecDot(vC,vP)/UTILS.VecNorm(vC)/UTILS.VecNorm(vP)) + + -- Return heading in degrees. + return math.deg(relHead) +end + + +--- Carrier trainer event handler for event birth. +-- @param #CARRIERTRAINER self +function CARRIERTRAINER:_CheckPlayerStatus() + + -- Loop over all players. + for _playerName,_playerData in pairs(self.players) do + local playerData = _playerData --#CARRIERTRAINER.PlayerData + + if playerData then + + self:I("player "..playerData.callsign) + + -- Player unit. + local unit = playerData.unit + + if unit:IsAlive() then + + self:_DetailedPlayerStatus(playerData) + if unit:IsInZone(self.giantZone) then + --self:_DetailedPlayerStatus(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 + 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 + end + end + end + +end + + + +--- Check limits for reaching next step. +-- @param #CARRIERTRAINER self +-- @param #number X X position of player unit. +-- @param #number Z Z position of player unit. +-- @param #CARRIERTRAINER.Checkpoint check Checkpoint. +-- @return #boolean If true, checkpoint condition for next step was reached. +function CARRIERTRAINER:_CheckLimits(X, Z, check) + + local next=false + if check.LimitXmin and Xcheck.LimitXmax then + next=true + elseif check.LimitZmin and Zcheck.LimitZmax then + next=true + end + + return next +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="when entering pattern" + elseif step==2 then + name="on upwind leg" + elseif step==3 then + name="at the early break" + elseif step==4 then + name="at the late break" + 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==8 then + name="in the groove" + elseif step==9 then + name="trapped" + end + + return name +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. +function CARRIERTRAINER:_GetDistances(unit) + + -- Vector to carrier + local a=self.carrier:GetVec3() + + -- Vector to player + local b=unit:GetVec3() + + -- Vector from carrier to player. + local c={x=b.x-a.x, y=0, z=b.z-a.z} + + -- Orientation of carrier. + local x=self.carrier:GetOrientationX() + + -- Projection of player pos on x component. + local dx=UTILS.VecDot(x,c) + + -- Orientation of carrier. + local z=self.carrier:GetOrientationZ() + + -- Projection of player pos on z component. + local dz=UTILS.VecDot(z,c) + + return dx,dz +end + +--- Check if a player is within the right area. +-- @param #CARRIERTRAINER self +-- @param #number X X distance player to carrier. +-- @param #number Z Z distance player to carrier. +-- @param #table pos Position data limits. +-- @return #boolean If true, approach should be aborted. +function CARRIERTRAINER:_CheckAbort(X, Z, pos) + + local abort=false + if pos.Xmin and Xpos.Xmax then + abort=true + elseif pos.Zmin and Zpos.Zmax then + abort=true + end + + return abort +end + +--- Generate a text if a player is too far from where he should be. +-- @param #CARRIERTRAINER self +-- @param #number X X distance player to carrier. +-- @param #number Z Z distance player to carrier. +-- @param #table posData Position data limits. +function CARRIERTRAINER:_TooFarOutText(X, Z, posData) + + local text="You are too far" + + local xtext=nil + if posData.Xmin and XposData.Xmax then + xtext=" behind" + end + + local ztext=nil + if posData.Zmin and ZposData.Zmax then + ztext=" starboard (right)" + end + + if xtext and ztext then + text=text..xtext.." and"..ztext + elseif xtext then + text=text..xtext + elseif ztext then + text=text..ztext + end + + text=text.." of the carrier!" + + return text +end + +--- Pattern aborted. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data. +-- @param #number X X distance player to carrier. +-- @param #number Z Z distance player to carrier. +-- @param #table posData Position data. +function CARRIERTRAINER:_AbortPattern(playerData, X, Z, posData) + + local toofartext=self:_TooFarOutText(X, Z, posData) + + self:_SendMessageToPlayer(toofartext.." Abort approach!", 15, playerData ) + + MESSAGE:New(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)), 60):ToAllIf(self.Debug) + + self:_AddToSummary(playerData, "Approach aborted.") + + self:_PrintFinalScore(playerData, 30, -2) + + self:_HandleCollectedResult(playerData, -2) + + playerData.step = 0 +end + + +--- Provide info about player status on the fly. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data. +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 dx,dz=self:_GetDistances(unit) + + -- Player and carrier position vector. + local playerPosition = playerData.unit:GetVec3() + local carrierPosition = self.carrier:GetVec3() + + local diffZ = playerPosition.z - carrierPosition.z + local diffX = playerPosition.x - carrierPosition.x + + local heading=unit:GetCoordinate():HeadingTo(self.startZone:GetCoordinate()) + + local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3() + local velo=unit:GetVelocityVec3() + + local text=string.format("%s, current AoA=%.1f\n", playerData.callsign, aoa) + 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("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)) + --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 + +--- Init parameters for USS Stennis carrier. +-- @param #CARRIERTRAINER self +function CARRIERTRAINER:_InitStennis() + + -- Upwind leg + self.Upwind.Xmin=-4000 -- TODO Should be withing 4 km behind carrier. Why? + self.Upwind.Xmax=nil + self.Upwind.Zmin=0 + self.Upwind.Zmax=500 + self.Upwind.LimitXmin=nil + self.Upwind.LimitXmax=nil + self.Upwind.LimitZmin=nil + self.Upwind.LimitZmax=nil + self.upwind.Alitude=UTILS.FeetToMeters(800) + self.Upwind.AoA=8.1 + self.Upwind.Distance=nil + + -- Early break + self.BreakEarly.Xmin=-500 + self.BreakEarly.Xmax=nil + self.BreakEarly.Zmin=-3700 + self.BreakEarly.Zmax=1500 + self.BreakEarly.LimitXmin=nil + self.BreakEarly.LimitXmax=nil + self.BreakEarly.LimitZmin=-370 --0.2 NM + self.BreakEarly.LimitZmax=nil + self.BreakEarly.Alitude=UTILS.FeetToMeters(800) + self.BreakEarly.AoA=8.1 + self.BreakEarly.Distance=nil + + -- Late break + self.BreakLate.Xmin=-500 + self.BreakLate.Xmax=nil + self.BreakLate.Zmin=-3700 + self.BreakLate.Zmax=1500 + self.BreakLate.LimitXmin=nil + self.BreakLate.LimitXmax=nil + self.BreakLate.LimitZmin=-1470 --0.8 NM + self.BreakLate.LimitZmax=nil + self.BreakLate.Alitude=UTILS.FeetToMeters(800) + self.BreakLate.AoA=8.1 + self.BreakLate.Distance=nil + + -- Abeam position + self.Abeam.Xmin=nil + self.Abeam.Xmax=nil + self.Abeam.Zmin=-3700 + self.Abeam.Zmax=-1000 + self.Abeam.LimitXmin=-200 + self.Abeam.LimitXmax=nil + self.Abeam.LimitZmin=nil + self.Abeam.LimitZmax=nil + self.Abeam.Alitude=UTILS.FeetToMeters(600) + self.Abeam.AoA=8.1 + self.Abeam.Distance=nil + + -- At the ninety + self.Ninety.Xmin=-3700 + self.Ninety.Xmax=0 + self.Ninety.Zmin=-3700 + self.Ninety.Zmax=nil + self.Ninety.LimitXmin=nil + self.Ninety.LimitXmax=nil + self.Ninety.LimitZmin=nil + self.Ninety.LimitZmax=-1111 + self.Ninety.Altitude=UTILS.FeetToMeters(500) + self.Abeam.AoA=8.1 + self.Abeam.Distance=nil + + -- Wake position + self.Wake.Xmin=-4000 + self.Wake.Xmax=0 + self.Wake.Zmin=-2000 + self.Wake.Zmax=nil + self.Wake.LimitXmin=nil + self.Wake.LimitXmax=nil + self.Wake.LimitZmin=nil + self.Wake.LimitZmax=0 + self.Wake.Alitude=UTILS.FeetToMeters(370) + self.Wake.AoA=8.1 + self.Wake.Distance=nil + + -- In the groove + self.Groove.Xmin=-4000 + self.Groove.Xmax=100 + + -- Landing trap + self.Trap.Xmin=-3000 + self.Trap.Xmax=nil + self.Trap.Zmin=-2000 + self.Trap.Zmax=2000 + self.Trap.Limit=nil + self.Trap.Alitude=nil + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- CARRIER TRAINING functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Initialize player data. +-- @param #CARRIERTRAINER self +-- @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) + + self:_InitNewRound(playerData) + playerData.step = 1 +end + +--- Start landing pattern. +-- @param #CARRIERTRAINER self +-- @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 on the upwind.", playerData.callsign) + self:_SendMessageToPlayer(hint, 8, playerData) + playerData.score = 0 + playerData.step = 2 +end + +--- Evaluate player's altitude at checkpoint. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data table. +-- @param #CARRIERTRAINER.Checkpoint checkpoint Checkpoint. +-- @return #number Score. +-- @return #string Message text. +function CARRIERTRAINER:_AltitudeCheck(playerData, checkpoint) + + -- Player altitude. + local altitude=playerData.unit:GetAltitude() + + local lowscore + local badscore + if playerData.difficulty==CARRIERTRAINER.Difficulty.EASY then + lowscore=10 + badscore=20 + elseif playerData.difficulty==CARRIERTRAINER.Difficulty.NORMAL then + lowscore=5 + badscore=10 + elseif playerData.difficulty==CARRIERTRAINER.Difficulty.HARD then + lowscore=2.5 + badscore=5 + end + + -- Altitude error +-X% + local _error=(altitude-checkpoint.Altitude)/checkpoint.Altitude*100 + + local score + local hint + local steptext=self:_StepName(playerData.step) + if _error>badscore then + score = 5 + hint = string.format("You're high %s.", steptext) + elseif _error>lowscore then + score = 7 + hint = string.format("You're slightly high %s.", steptext) + elseif _errorbadscore then + score = 0 + hint = string.format("too far from the boat (%.1f NM)", dnm) + elseif _error>lowscore then + score = 5 + hint = string.format("slightly too far from the boat (%.1f NM)", dnm) + elseif _error 0 then + if self:_CheckLimits(diffX, diffZ, self.Upwind) then + + -- Get + local score, hint=self:_AltitudeCheck(playerData, self.Upwind) + + -- 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) + + -- Next step. + playerData.step = 3 + end +end + + +--- Break. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data table. +-- @param #string part Part of the break. +function CARRIERTRAINER:_Break(playerData, part) + + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) + local diffX, diffZ = self:_GetDistances(playerData.unit) + + --if (diffZ > 1500 or diffZ < -3700 or diffX < -500) then + if self:_CheckAbort(diffX, diffZ, self.Break) then + self:_AbortPattern(playerData, diffX, diffZ, self.Break) + return + end + + -- Break + -- z= -370 + -- y= 800 + -- x> -500 + + --local limit = -370 --0.2 NM + local limit = self.BreakEarly + if part == "late" then + --limit = -1470 -- 0.8 NM + limit = self.BreakLate + end + + -- Check if too far left + --if diffZ < limit then + if self:_CheckLimits(diffX, diffZ, limit) then + + local idealAltitude = 800 + local score, hint=self:_AltitudeCheck(playerData, self.Upwind) + + self:_IncreaseScore(playerData, score) + + self:_SendMessageToPlayer( hint, 8, playerData ) + self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) + self:_PrintScore(score, playerData, true) + + self:_AddToSummary(playerData, 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) + + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) + local 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 + if self:_CheckAbort(diffX, diffZ, self.Abeam) then + self:_AbortPattern(playerData, diffX, diffZ, self.Abeam) + return + end + + -- Abeam pos: + -- x= -200 + -- z=-2160 + -- y= 600 + + -- Abeam pos 200 meters behind ship + local limit = -200 + + --if diffX < limit then + if self:_CheckLimits(diffX, diffZ, self.Abeam) then + + -- Get AoA. + local aoa = playerData.unit:GetAoA() + local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData) + + local onSpeedScore = self:_GetOnSpeedScore(aoa) + + local idealAltitude = 600 + local score, hint=self:_AltitudeCheck(playerData, self.Abeam) + + local distanceHint = "" + local distanceScore + local diffEast = carrierPosition.z - playerPosition.z + + local idealDistance = UTILS.NMToMeters(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 ) + + self:_IncreaseScore(playerData, score + distanceScore + onSpeedScore) + self:_PrintScore(score + distanceScore + onSpeedScore, playerData, true) + + self:_AddToSummary(playerData, fullHint .. " (" .. aoaFeedback .. ")") + + -- Proceed to next step. + 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 + + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) + local diffX, diffZ = self:_GetDistances(playerData.unit) + + -- Check we are not too far out w.r.t back of the boat. + if diffX < limit then + + local headingPlayer = playerData.unit:GetHeading() + local headingCarrier = self.carrier:GetHeading() + + --TODO: Take carrier heading != 0 into account! + + if (headingPlayer > 170) then + + local hint = "Too long downwind. Turn final earlier next time." + self:_SendMessageToPlayer( hint, 8, playerData ) + local score = -40 + self:_IncreaseScore(playerData, score) + self:_PrintScore(score, playerData, true) + self:_AddToSummary(playerData, hint) + playerData.longDownwindDone = true + end + + end +end + +--- Ninety. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data table. +function CARRIERTRAINER:_Ninety(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 < -3700 or diffX < -3700 or diffX > 0) then + if self:_CheckAbort(diffX, diffZ, self.Ninety) then + self:_AbortPattern(playerData, diffX, diffZ, self.Ninety) + return + end + + local limitEast = -1111 --0.6nm + + --if diffZ > limitEast then + if self:_CheckLimits(diffX, diffZ, self.Ninety) then + + local idealAltitude = 500 + local score, hint=self:_AltitudeCheck(playerData, self.Ninety) + + 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) + + self:_IncreaseScore(playerData, score + onSpeedScore) + self:_PrintScore(score + onSpeedScore, playerData, true) + + self:_AddToSummary(playerData, 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) + + -- 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 + if self:_CheckAbort(diffX, diffZ, self.Wake) then + self:_AbortPattern(playerData, diffX, diffZ, self.Wake) + return + end + + --if diffZ > 0 then + if self:_CheckLimits(diffX, diffZ, self.Wake) then + + local idealAltitude = 370 + local score, hint=self:_AltitudeCheck(playerData, self.Wake) + + self:_SendMessageToPlayer( hint, 8, playerData ) + self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) + + local aoa = playerData.unit:GetAoA() + + local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData) + local onSpeedScore = self:_GetOnSpeedScore(aoa) + + self:_IncreaseScore(playerData, score + onSpeedScore) + self:_PrintScore(score + onSpeedScore, playerData, true) + self:_AddToSummary(playerData, 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 + + --TODO -100?! + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) + 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 + + --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 + + 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 score, hint=self:_AltitudeCheck(playerData, self.Groove) + + 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) + + self:_IncreaseScore(playerData, score + onSpeedScore) + self:_PrintScore(score + onSpeedScore, playerData, true) + + local fullHint = hint .. " (" .. aoaFeedback .. ")" + + self:_AddToSummary(playerData, 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 + + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) + diffZ, diffX = self:_GetDistances(playerData.unit) + + + --if(diffZ < -2000 or diffZ > 2000 or diffX < -3000) then + if self:_CheckAbort(diffX, diffZ, self.Trap) then + self:_AbortPattern(playerData, diffX, diffZ, self.Trap) + 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 + + 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 + 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) + + self:_AddToSummary(playerData, hint) + + self:_PrintFinalScore(playerData, 60, wire) + self:_HandleCollectedResult(playerData, wire) + + playerData.step = 0 + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Menu Functions +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add menu commands for player. +-- @param #CARRIERTRAINER self +-- @param #string _unitName Name of player unit. +function CARRIERTRAINER:_AddF10Commands(_unitName) + self:F(_unitName) + + -- Get player unit and name. + local _unit, playername = self:_GetPlayerUnitAndName(_unitName) + + -- Check for player unit. + if _unit and playername then + + -- Get group and ID. + local group=_unit:GetGroup() + local _gid=group:GetID() + + if group and _gid then + + if not self.menuadded[_gid] then + + -- Enable switch so we don't do this twice. + self.menuadded[_gid] = true + + -- Main F10 menu: F10/Carrier Trainer// + if CARRIERTRAINER.MenuF10[_gid] == nil then + CARRIERTRAINER.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "Carrier Trainer") + end + local _rangePath = missionCommands.addSubMenuForGroup(_gid, self.alias, CARRIERTRAINER.MenuF10[_gid]) + local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Results", _rangePath) + local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) + local _infoPath = missionCommands.addSubMenuForGroup(_gid, "Carrier Info", _rangePath) + + -- F10/On the Range//Stats/ + --missionCommands.addCommandForGroup(_gid, "All Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "My Results", _statsPath, self._DisplayBombingResults, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "Reset All Results", _statsPath, self._ResetRangeStats, self, _unitName) + -- F10/On the Range//My Settings/ + --missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) + -- F10/On the Range//Range Information + --missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) + missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayCarrierWeather, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) + --missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName) + end + else + self:T(self.lid.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) + end + else + self:T(self.lid.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName) + end + +end + +--- Report weather conditions at range. Temperature, QFE pressure and wind data. +-- @param #CARRIERTRAINER self +-- @param #string _unitname Name of the player unit. +function CARRIERTRAINER:_DisplayCarrierWeather(_unitname) + self:E(_unitname) + + -- Get player unit and player name. + local unit, playername = self:_GetPlayerUnitAndName(_unitname) + + -- Check if we have a player. + if unit and playername then + + -- Message text. + local text="" + + -- Current coordinates. + local coord=self.carrier:GetCoordinate() + + -- Get atmospheric data at range location. + local position=self.location --Core.Point#COORDINATE + local T=position:GetTemperature() + local P=position:GetPressure() + local Wd,Ws=position:GetWind() + + -- Get Beaufort wind scale. + local Bn,Bd=UTILS.BeaufortScale(Ws) + + local WD=string.format('%03d°', Wd) + local Ts=string.format("%d°C",T) + + local hPa2inHg=0.0295299830714 + local hPa2mmHg=0.7500615613030 + + local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS + local tT=string.format("%d°C",T) + local tW=string.format("%.1f m/s", Ws) + local tP=string.format("%.1f mmHg", P*hPa2mmHg) + if settings:IsImperial() then + tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) + tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) + tP=string.format("%.2f inHg", P*hPa2inHg) + end + + + -- Message text. + text=text..string.format("Weather Report at %s:\n", self.rangename) + 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("QFE %.1f hPa = %s", P, tP) + + + -- Send message to player group. + --self:_DisplayMessageToGroup(unit, text, nil, true) + self:_SendMessageToPlayer(text, 30, self.players[playername]) + + -- Debug output. + self:T2(self.lid..text) + else + self:T(self.lid..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname)) + 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) + local text=string.format("Alt: %d feet (Target: %d feet)", altitude, idealAltitude) + self:_SendMessageToPlayer(text, 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(string.format("%s, %s, ", self.alias, playerData.callsign)..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 CARRIERTRAINER:_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 + + local heading=playerData.unit:GetCoordinate():HeadingTo(self.startZone:GetCoordinate()) + local distance=playerData.unit:GetCoordinate():Get2DDistance(self.startZone:GetCoordinate()) + local text=string.format("%s, fly heading %d for %d nm to restart the pattern.", playerData.callsign, heading, UTILS.MetersToNM(distance)) + --"Return south 4 nm (over the trailing ship), towards WP 1, to restart the pattern." + self:_SendMessageToPlayer(text, 30, playerData) +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 + + From a8f42d61fa9595d7c0283eaac3910c57110ea521 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 15 Oct 2018 07:20:46 +0200 Subject: [PATCH 2/7] APC defense ... optimized the unloading of infantry when enemies are nearby. Now the infantry is reloading into the APCs when the enemies are eliminated. --- Moose Development/Moose/AI/AI_Cargo.lua | 41 ++++++++++++--------- Moose Development/Moose/AI/AI_Cargo_APC.lua | 8 ++-- Moose Development/Moose/Cargo/CargoUnit.lua | 20 ++++++---- Moose Development/Moose/Core/UserFlag.lua | 2 - 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index a1649d7a8..c585c0674 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -262,7 +262,6 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) Carrier:RouteStop() --Cargo:Ungroup() Cargo:__Board( -LoadDelay, CarrierUnit ) - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone ) -- So now this CarrierUnit has Cargo that is being loaded. @@ -279,6 +278,7 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) end end + LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval end @@ -359,7 +359,7 @@ function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone ) Carrier:RouteResume() local HasCargo = false - if Carrier and Carrier :IsAlive() then + if Carrier and Carrier:IsAlive() then for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do HasCargo = true break @@ -368,6 +368,7 @@ function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone ) self.Relocating = false if HasCargo then + self:F( "Transporting" ) self.Transporting = true end @@ -383,11 +384,11 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone ) - self:F( { Carrier, From, Event, To, DeployZone } ) +function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend ) + self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } ) - local UnboardInterval = 10 - local UnboardDelay = 10 + local UnboardInterval = 5 + local UnboardDelay = 5 if Carrier and Carrier:IsAlive() then for _, CarrierUnit in pairs( Carrier:GetUnits() ) do @@ -398,8 +399,10 @@ function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone ) if Cargo:IsLoaded() then Cargo:__UnBoard( UnboardDelay ) UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval - Cargo:SetDeployed( true ) - self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone ) + self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend ) + if not Defend == true then + Cargo:SetDeployed( true ) + end end end end @@ -415,17 +418,17 @@ end -- @param #string To To state. -- @param #string Cargo.Cargo#CARGO Cargo Cargo object. -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:F( { Carrier, From, Event, To, Cargo:GetName() } ) +function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) + self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) if Carrier and Carrier:IsAlive() then if not Cargo:IsUnLoaded() then - self:__Unboard( 10, Cargo, CarrierUnit, DeployZone ) + self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend ) return end end - self:Unloaded( Cargo, CarrierUnit, DeployZone ) + self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend ) end @@ -438,8 +441,8 @@ end -- @param #string Cargo.Cargo#CARGO Cargo Cargo object. -- @param #boolean Deployed Cargo is deployed. -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone } ) +function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) + self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) local AllUnloaded = true @@ -465,7 +468,7 @@ function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, end if AllUnloaded == true then - self:__Deployed( 5, DeployZone ) + self:__Deployed( 5, DeployZone, Defend ) end end @@ -477,10 +480,14 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone ) - self:F( { Carrier, From, Event, To, DeployZone = DeployZone } ) +function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend ) + self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) + if not Defend == true then self.Transporting = false + else + self:F( "Defending" ) + end end diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index ace90787d..b390f28d0 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -285,7 +285,7 @@ function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) else if self:Is( "Loaded" ) then -- There are enemies within combat radius. Unload the CargoCarrier. - self:__Unload( 1 ) + self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! else if self:Is( "Unloaded" ) then self:Follow() @@ -449,12 +449,12 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone ) - self:F( { APC, From, Event, To, DeployZone = DeployZone } ) +function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend ) + self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) self:__Guard( 0.1 ) - self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone ) + self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend ) end diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index ffd4742f9..bbdce8c57 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -231,8 +231,8 @@ do -- CARGO_UNIT local MaxSpeed = Desc.speedMaxOffRoad local TypeName = Desc.typeName - self:T( self.CargoInAir ) - + --self:F({Unit=self.CargoObject:GetName()}) + -- Only move the group to the carrier when the cargo is not in the air -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). if not self.CargoInAir then @@ -286,18 +286,19 @@ do -- CARGO_UNIT function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) self:F( { From, Event, To, CargoCarrier:GetName(), NearRadius = NearRadius } ) + --self:F({Unit=self.CargoObject:GetName()}) - if CargoCarrier and CargoCarrier:IsAlive() and self.CargoObject and self.CargoObject:IsAlive() then + if CargoCarrier and CargoCarrier:IsAlive() then -- and self.CargoObject and self.CargoObject:IsAlive() then if (CargoCarrier:IsAir() and not CargoCarrier:InAir()) or true then local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius( NearRadius ) + 5 if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then self:__Load( 1, CargoCarrier, ... ) else if self:IsNear( CargoCarrier:GetPointVec2(), 20 ) then - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) + self:__Boarding( 1, CargoCarrier, NearRadius, ... ) self.RunCount = self.RunCount + 1 else - self:__Boarding( -2, CargoCarrier, NearRadius, ... ) + self:__Boarding( 2, CargoCarrier, NearRadius, ... ) self.RunCount = self.RunCount + 2 end if self.RunCount >= 40 then @@ -306,6 +307,8 @@ do -- CARGO_UNIT local Angle = 180 local Distance = 0 + --self:F({Unit=self.CargoObject:GetName()}) + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) @@ -368,10 +371,11 @@ do -- CARGO_UNIT self.CargoCarrier = CargoCarrier - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + --self:F({Unit=self.CargoObject:GetName()}) + + -- Only destroy the CargoObject if there is a CargoObject (packages don't have CargoObjects). if self.CargoObject then - self:T("Destroying") - self.CargoObject:Destroy() + self.CargoObject:Destroy( false ) --self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 ) end end diff --git a/Moose Development/Moose/Core/UserFlag.lua b/Moose Development/Moose/Core/UserFlag.lua index f20a26303..88c1d0f60 100644 --- a/Moose Development/Moose/Core/UserFlag.lua +++ b/Moose Development/Moose/Core/UserFlag.lua @@ -57,8 +57,6 @@ do -- UserFlag -- function USERFLAG:Set( Number ) --R2.3 - self:F( { Number = Number } ) - trigger.action.setUserFlag( self.UserFlagName, Number ) return self From 40154788d66486edbeb07cc0c2ca73f81901aaa0 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 15 Oct 2018 16:19:43 +0200 Subject: [PATCH 3/7] WH, CT WIP --- .../Moose/Functional/CarrierTrainer.lua | 314 +++++++++--------- .../Moose/Functional/Warehouse.lua | 92 +++++ 2 files changed, 241 insertions(+), 165 deletions(-) diff --git a/Moose Development/Moose/Functional/CarrierTrainer.lua b/Moose Development/Moose/Functional/CarrierTrainer.lua index 173f80fb3..c24e7fbd5 100644 --- a/Moose Development/Moose/Functional/CarrierTrainer.lua +++ b/Moose Development/Moose/Functional/CarrierTrainer.lua @@ -770,124 +770,7 @@ function CARRIERTRAINER:_Start(playerData) playerData.step = 2 end ---- Evaluate player's altitude at checkpoint. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data table. --- @param #CARRIERTRAINER.Checkpoint checkpoint Checkpoint. --- @return #number Score. --- @return #string Message text. -function CARRIERTRAINER:_AltitudeCheck(playerData, checkpoint) - -- Player altitude. - local altitude=playerData.unit:GetAltitude() - - local lowscore - local badscore - if playerData.difficulty==CARRIERTRAINER.Difficulty.EASY then - lowscore=10 - badscore=20 - elseif playerData.difficulty==CARRIERTRAINER.Difficulty.NORMAL then - lowscore=5 - badscore=10 - elseif playerData.difficulty==CARRIERTRAINER.Difficulty.HARD then - lowscore=2.5 - badscore=5 - end - - -- Altitude error +-X% - local _error=(altitude-checkpoint.Altitude)/checkpoint.Altitude*100 - - local score - local hint - local steptext=self:_StepName(playerData.step) - if _error>badscore then - score = 5 - hint = string.format("You're high %s.", steptext) - elseif _error>lowscore then - score = 7 - hint = string.format("You're slightly high %s.", steptext) - elseif _errorbadscore then - score = 0 - hint = string.format("too far from the boat (%.1f NM)", dnm) - elseif _error>lowscore then - score = 5 - hint = string.format("slightly too far from the boat (%.1f NM)", dnm) - elseif _error 0 then + -- Check if we are in front of the boat (diffX > 0). if self:_CheckLimits(diffX, diffZ, self.Upwind) then -- Get local score, hint=self:_AltitudeCheck(playerData, self.Upwind) - - -- 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) -- Next step. @@ -935,21 +810,15 @@ function CARRIERTRAINER:_Break(playerData, part) -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) local diffX, diffZ = self:_GetDistances(playerData.unit) - --if (diffZ > 1500 or diffZ < -3700 or diffX < -500) then + -- Check abort conditions. if self:_CheckAbort(diffX, diffZ, self.Break) then self:_AbortPattern(playerData, diffX, diffZ, self.Break) return end - - -- Break - -- z= -370 - -- y= 800 - -- x> -500 - --local limit = -370 --0.2 NM + -- Early or late break. local limit = self.BreakEarly if part == "late" then - --limit = -1470 -- 0.8 NM limit = self.BreakLate end @@ -957,15 +826,11 @@ function CARRIERTRAINER:_Break(playerData, part) --if diffZ < limit then if self:_CheckLimits(diffX, diffZ, limit) then - local idealAltitude = 800 + -- Check altitude. local score, hint=self:_AltitudeCheck(playerData, self.Upwind) - self:_IncreaseScore(playerData, score) - - self:_SendMessageToPlayer( hint, 8, playerData ) - self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) - self:_PrintScore(score, playerData, true) + self:_SendMessageToPlayer(hint, 8, playerData) self:_AddToSummary(playerData, hint) if (part == "early") then @@ -984,24 +849,13 @@ 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) - -- 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 + -- Check abort conditions. if self:_CheckAbort(diffX, diffZ, self.Abeam) then self:_AbortPattern(playerData, diffX, diffZ, self.Abeam) return end - - -- Abeam pos: - -- x= -200 - -- z=-2160 - -- y= 600 - - -- Abeam pos 200 meters behind ship - local limit = -200 - - --if diffX < limit then + + -- Check nest step threshold. if self:_CheckLimits(diffX, diffZ, self.Abeam) then -- Get AoA. @@ -1451,6 +1305,115 @@ end -- MISC functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Evaluate player's altitude at checkpoint. +-- @param #CARRIERTRAINER self +-- @param #CARRIERTRAINER.PlayerData playerData Player data table. +-- @param #CARRIERTRAINER.Checkpoint checkpoint Checkpoint. +-- @return #number Score. +-- @return #string Message text. +function CARRIERTRAINER:_AltitudeCheck(playerData, checkpoint) + + -- Player altitude. + local altitude=playerData.unit:GetAltitude() + + local lowscore + local badscore + if playerData.difficulty==CARRIERTRAINER.Difficulty.EASY then + lowscore=10 + badscore=20 + elseif playerData.difficulty==CARRIERTRAINER.Difficulty.NORMAL then + lowscore=5 + badscore=10 + elseif playerData.difficulty==CARRIERTRAINER.Difficulty.HARD then + lowscore=2.5 + badscore=5 + end + + -- Altitude error +-X% + local _error=(altitude-checkpoint.Altitude)/checkpoint.Altitude*100 + + local score + local hint + local steptext=self:_StepName(playerData.step) + if _error>badscore then + score = 5 + hint = string.format("You're high %s.", steptext) + elseif _error>lowscore then + score = 7 + hint = string.format("You're slightly high %s.", steptext) + elseif _errorbadscore then + score = 0 + hint = string.format("too far from the boat (%.1f NM)", dnm) + elseif _error>lowscore then + score = 5 + hint = string.format("slightly too far from the boat (%.1f NM)", dnm) + elseif _error 9.5) then --Slow + if _error>badscore then --Slow score = 0 - elseif(AoA > 9) then --Slightly slow + elseif _error>lowscore then --Slightly slow score = 5 - elseif(AoA > 7.25) then --On speed + elseif _error 6.7) then --Slightly fast - score = 5 - else --Fast - score = 0 end return score @@ -1526,12 +1507,15 @@ end -- @param #CARRIERTRAINER self -- @param #number score Score. -- @param #CARRIERTRAINER.PlayerData playerData Player data. -function CARRIERTRAINER:_PrintScore(score, playerData, alsoPrintTotalScore) - if(alsoPrintTotalScore) then +-- @param #boolean printtotal Also print total score. +function CARRIERTRAINER:_PrintScore(score, playerData, printtotal) + + if printtotal then self:_SendMessageToPlayer( "Score: " .. score .. " (Total: " .. playerData.score .. ")", 8, playerData ) else self:_SendMessageToPlayer( "Score: " .. score, 8, playerData ) end + end --- Display final score. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 455723317..6081c031c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4612,6 +4612,98 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) end + +--- On after "LoadAssets" event. Warehouse assets are loaded from file on disk. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterLoadAssets(From, Event, To, filename) + +local function loadfile(filename) + local f = assert(io.open(filename, "rb")) + local data = f:read("*all") + f:close() + return data +end + +local function savefile(filename, data) + local f = assert(io.open(filename, "wb")) + f:write(data) + f:close() +end + +--local peter="hallo ich bin peter data" +local peter={"a", "b", "c"} +peter="Asset name='Meine Gruppe B';" +peter=peter.."Asset name='Meine Gruppe A';" +local filename="paul.dat" + +local assets={} +local asset1={templatename="Meine, Gruppe 1", attribute="Infantry", cargobay="100"} +local asset2={templatename="Meine Gruppe 2", attribute="Helicopter", cargobay="200"} + +table.insert(assets,asset1) +table.insert(assets,asset2) +--savefile(filename, peter) +--local data=loadfile(filename) + +--print(data) +print("Asset:") +--print(table.concat({1,2,2})) +--print(table.concat(asset1, ";")) +local warehouseassets="" +for _,asset in pairs(assets) do + local assetstring="" + for key,value in pairs(asset) do + --print(key,value) + --local name=string.format("%s=\"%s\";", key, value) + local name=string.format("%s=%s;", key, value) + --print(name) + assetstring=assetstring..name + end + --print(assetstring) + warehouseassets=warehouseassets..assetstring.."\n" +end +print(warehouseassets) + +savefile(filename, warehouseassets) +local data=loadfile(filename) + +--print(data) + +data2=UTILS.Split(data,"\n") +local newassets={} +for _,asset in pairs(data2) do + --print(asset) + local descriptors=UTILS.Split(asset,";") + local newasset={} + for _,descriptor in pairs(descriptors) do + local keyval=UTILS.Split(descriptor,"=") + if #keyval==2 then + local key=keyval[1] + local val=keyval[2] + --print(key, val) + newasset[key]=val + end + end + table.insert(newassets, newasset) +-- for k, v in string.gmatch(asset, "(%w+)=([%w%c, ]+)") do + --for k, v in string.gmatch(asset, "(%w+)=([^\"]+)") do +-- print(k,v) +-- end +end + +for _,myasset in pairs(newassets) do + local name=myasset.templatename + local attribute=myasset.attribute + local cargo=myasset.cargobay + print(string.format("name=%s attribute=%s cargobay=%s", name, attribute, cargo)) +end + + +end + --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Spawn functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From d87cb41b75176fa600f663af0637ad8818616b4e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 15 Oct 2018 23:50:40 +0200 Subject: [PATCH 4/7] Warehouse v0.6.2 added first version of save/load assets PseudoATC fixed bearing info other bugs reported! --- .../Moose/Functional/PseudoATC.lua | 8 +- .../Moose/Functional/Warehouse.lua | 264 ++++++++++++------ 2 files changed, 187 insertions(+), 85 deletions(-) diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 08422cb1f..bfb458078 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -98,7 +98,7 @@ PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version -PSEUDOATC.version="0.9.0" +PSEUDOATC.version="0.9.1" ----------------------------------------------------------------------------------------------------------------------------------------- @@ -743,9 +743,9 @@ function PSEUDOATC:ReportBR(id, position, location) local coord=unit:GetCoordinate() -- Direction vector from current position (coord) to target (position). - local pos=coord:Translate(30,90) - local vec3=coord:GetDirectionVec3(pos) - local angle=coord:GetAngleDegrees(vec3) + local angle=coord:HeadingTo(position) + + -- Range from current to local range=coord:Get2DDistance(position) -- Bearing string. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 6081c031c..16189527b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -50,7 +50,7 @@ -- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. -- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. --- @field #number uid Unit identifier of the warehouse. Derived from the associated airbase. +-- @field #number uid Unit identifier of the warehouse. Derived from id of warehouse static element. -- @field #number markerid ID of the warehouse marker at the airbase. -- @field #number dTstatus Time interval in seconds of updating the warehouse status and processing new events. Default 30 seconds. -- @field #number queueid Unit id of each request in the queue. Essentially a running number starting at one and incremented when a new request is added. @@ -1507,6 +1507,7 @@ WAREHOUSE = { -- @field #number loadradius Distance when cargo is loaded into the carrier. -- @field DCS#AI.Skill skill Skill of AI unit. -- @field #string livery Livery of the asset. +-- @field #string assignment Assignment of the asset. This could, e.g., be used in the @{#WAREHOUSE.OnAfterNewAsset) funktion. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -1655,7 +1656,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.6.1" +WAREHOUSE.version="0.6.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1771,9 +1772,10 @@ function WAREHOUSE:New(warehouse, alias) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("NotReadyYet", "Load", "Loaded") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("NotReadyYet", "Load", "Loaded") -- Load the warehouse state from scatch. + self:AddTransition("Stopped", "Load", "Loaded") -- Load the warehouse state stopped state. self:AddTransition("NotReadyYet", "Start", "Running") -- Start the warehouse from scratch. - self:AddTransition("Loaded", "Start", "Running") -- TODO Start the warehouse when loaded from disk. + self:AddTransition("Loaded", "Start", "Running") -- Start the warehouse when loaded from disk. self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. self:AddTransition("*", "NewAsset", "*") -- New asset was added to warehouse stock. @@ -2162,7 +2164,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE self --- Triggers the FSM event "Destroyed" with a delay when the warehouse was destroyed. Services are stopped. - -- @function [parent=#WAREHOUSE] Destroyed + -- @function [parent=#WAREHOUSE] __Destroyed -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. @@ -2173,6 +2175,53 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string Event Event. -- @param #string To To state. + + --- Triggers the FSM event "Save" when the warehouse assets are saved to file on disk. + -- @function [parent=#WAREHOUSE] Save + -- @param #WAREHOUSE self + -- @param #string path Path where the file is saved. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + --- Triggers the FSM event "Save" with a delay when the warehouse assets are saved to a file. + -- @function [parent=#WAREHOUSE] __Save + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param #string path Path where the file is saved. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + --- On after "Save" event user function. Called when the warehouse assets are saved to disk. + -- @function [parent=#WAREHOUSE] OnAfterSave + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path Path where the file is saved. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + + --- Triggers the FSM event "Load" when the warehouse is loaded from a file on disk. + -- @function [parent=#WAREHOUSE] Load + -- @param #WAREHOUSE self + -- @param #string path Path where the file is located. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + --- Triggers the FSM event "Load" with a delay when the warehouse assets are loaded from disk. + -- @function [parent=#WAREHOUSE] __Load + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param #string path Path where the file is located. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + --- On after "Load" event user function. Called when the warehouse assets are loaded from disk. + -- @function [parent=#WAREHOUSE] OnAfterLoad + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path Path where the file is located. Default is the DCS installation root directory. + -- @param #string filename (Optional) File name. Default is WAREHOUSE-_.txt. + + return self end @@ -2941,8 +2990,10 @@ function WAREHOUSE:onafterStop(From, Event, To) self.stock=nil self.stock={} + self:_UpdateWarehouseMarkText() + -- Clear all pending schedules. - self.CallScheduler:Clear() + --self.CallScheduler:Clear() end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -4613,93 +4664,144 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) end ---- On after "LoadAssets" event. Warehouse assets are loaded from file on disk. +--- On after "Save" event. Warehouse assets are saved to file on disk. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WAREHOUSE:onafterLoadAssets(From, Event, To, filename) +-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory. +-- @param #string filename (Optional) Name of the file containing the asset data. +function WAREHOUSE:onafterSave(From, Event, To, path, filename) -local function loadfile(filename) - local f = assert(io.open(filename, "rb")) - local data = f:read("*all") - f:close() - return data -end - -local function savefile(filename, data) - local f = assert(io.open(filename, "wb")) - f:write(data) - f:close() -end - ---local peter="hallo ich bin peter data" -local peter={"a", "b", "c"} -peter="Asset name='Meine Gruppe B';" -peter=peter.."Asset name='Meine Gruppe A';" -local filename="paul.dat" - -local assets={} -local asset1={templatename="Meine, Gruppe 1", attribute="Infantry", cargobay="100"} -local asset2={templatename="Meine Gruppe 2", attribute="Helicopter", cargobay="200"} - -table.insert(assets,asset1) -table.insert(assets,asset2) ---savefile(filename, peter) ---local data=loadfile(filename) - ---print(data) -print("Asset:") ---print(table.concat({1,2,2})) ---print(table.concat(asset1, ";")) -local warehouseassets="" -for _,asset in pairs(assets) do - local assetstring="" - for key,value in pairs(asset) do - --print(key,value) - --local name=string.format("%s=\"%s\";", key, value) - local name=string.format("%s=%s;", key, value) - --print(name) - assetstring=assetstring..name + local function _savefile(filename, data) + local f = assert(io.open(filename, "wb")) + f:write(data) + f:close() end - --print(assetstring) - warehouseassets=warehouseassets..assetstring.."\n" + + -- Set file name. + filename=filename or string.format("WAREHOUSE-%d_%s.txt", self.uid, self.alias) + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Info + local text=string.format("Saving warehouse assets to file %s", filename) + MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) + self:I(self.wid..text) + + -- Loop over all assets in stock. + local warehouseassets="" + for _,_asset in pairs(self.stock) do + local asset=_asset -- #WAREHOUSE.Assetitem + + -- Loop over asset parameters. + local assetstring="" + for key,value in pairs(asset) do + + -- Only save keys which are needed to restore the asset. + if key=="templatename" or key=="attribute" or key=="cargobay" or key=="weight" or key=="loadradius" or key=="livery" or key=="skill" or key=="assignment" then + local name + if type(value)=="table" then + name=string.format("%s=%s;", key, value[1]) + else + name=string.format("%s=%s;", key, value) + end + env.info(name) + assetstring=assetstring..name + end + end + + -- Add asset string. + warehouseassets=warehouseassets..assetstring.."\n" + end + --print(warehouseassets) + + -- Save file. + _savefile(filename, warehouseassets) + end -print(warehouseassets) -savefile(filename, warehouseassets) -local data=loadfile(filename) +--- On after "Load" event. Warehouse assets are loaded from file on disk. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path Path where the file is loaded from. +-- @param #string filename (Optional) Name of the file containing the asset data. +function WAREHOUSE:onafterLoad(From, Event, To, path, filename) ---print(data) + local function _loadfile(filename) + local f = assert(io.open(filename, "rb")) + local data = f:read("*all") + f:close() + return data + end -data2=UTILS.Split(data,"\n") -local newassets={} -for _,asset in pairs(data2) do - --print(asset) - local descriptors=UTILS.Split(asset,";") - local newasset={} - for _,descriptor in pairs(descriptors) do - local keyval=UTILS.Split(descriptor,"=") - if #keyval==2 then - local key=keyval[1] - local val=keyval[2] - --print(key, val) - newasset[key]=val + -- Set file name. + filename=filename or string.format("WAREHOUSE-%d_%s.txt", self.uid, self.alias) + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Info + local text=string.format("Loading warehouse assets from file %s", filename) + MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) + self:I(self.wid..text) + + -- Load asset data from file. + local data=_loadfile(filename) + + -- Split by line break. + local assetdata=UTILS.Split(data,"\n") + + -- Loop over asset lines. + local assets={} + for _,asset in pairs(assetdata) do + + -- Parameters are separated by semi-colons + local descriptors=UTILS.Split(asset,";") + + local asset={} + for _,descriptor in pairs(descriptors) do + local keyval=UTILS.Split(descriptor,"=") + if #keyval==2 then + local key=keyval[1] + local val=keyval[2] + + -- Livery or skill could be "nil". + if val=="nil" then + val=nil + end + + -- Convert string to number where necessary. + if key=="cargobay" or key=="weight" or key=="loadradius" then + asset[key]=tonumber(val) + else + asset[key]=val + end + + end + end + + -- Add to table. + table.insert(assets, asset) + end + + for _,_asset in pairs(assets) do + local asset=_asset --#WAREHOUSE.Assetitem + + local group=GROUP:FindByName(asset.templatename) + if group then + self:AddAsset(group, 1, asset.attribute, asset.cargobay, asset.weight, asset.loadradius, asset.skill, asset.livery, asset.assignment) + else + env.info("FF group doest not exit ".. tostring(asset.templatename)) end end - table.insert(newassets, newasset) --- for k, v in string.gmatch(asset, "(%w+)=([%w%c, ]+)") do - --for k, v in string.gmatch(asset, "(%w+)=([^\"]+)") do --- print(k,v) --- end -end - -for _,myasset in pairs(newassets) do - local name=myasset.templatename - local attribute=myasset.attribute - local cargo=myasset.cargobay - print(string.format("name=%s attribute=%s cargobay=%s", name, attribute, cargo)) -end end From e17b5356dd467a32c2cf59a2c8231d5f8568339c Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 16 Oct 2018 16:32:35 +0200 Subject: [PATCH 5/7] WH --- .../Moose/Functional/Warehouse.lua | 96 ++++++++++++++++--- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 16189527b..fc4ce4c9c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -12,6 +12,7 @@ -- * Strategic components such as capturing, defending and destroying warehouses and their associated infrastructure. -- * Intelligent spawning of aircraft on airports (only if enough parking spots are available). -- * Possibility to hook into events and customize actions. +-- * Persistance of assets. Warehouse assets can be saved and loaded from file. -- * Can be easily interfaced to other MOOSE classes. -- -- === @@ -675,6 +676,48 @@ -- Mission designers can capture the events with OnAfterEvent functions, e.g. @{#WAREHOUSE.OnAfterDelivered} or @{#WAREHOUSE.OnAfterAirbaseCaptured}. -- -- === +-- +-- # Persistance of Assets +-- +-- Assets in stock of a warehouse can be saved to a file on the hard drive and then loaded from the file at a later point. This enables to restart the mission +-- and restore the warehouse stock. +-- +-- ## Prerequisite +-- +-- **Important** By default, DCS does not allow for writing data to files. Therefore, one first has to comment out the line "blblba" in the the file "blabla" +-- +-- ## Save Assets +-- +-- Saving asset data to file is achieved by the @{WAREHOUSE.Save}(*path*, *filename*) function. The parameter *path* specifies the path on the file system where the +-- warehouse data is saved. If you do not specify a path, the file is saved your the DCS installation root directory. +-- The parameter *filename* is optional and defines the name of the saved file. By default this is automatically created from the warehouse id and name, for example +-- "Warehouse-1234_Batumi.txt". +-- +-- warehouseBatumi:Save("D:\\My Warehouse Data\\") +-- +-- This will save all asset data to as "D:\My Warehouse Data\Warehouse-1234_Batumi.txt". +-- +-- ## Load Assets +-- +-- Loading assets data from file is achieved by the @{WAREHOUSE.Load}(*path*, *filename*) function. The parameter *path* specifies the path on the file system where the +-- warehouse data is loaded from. If you do not specify a path, the file is loaded from your the DCS installation root directory. +-- The parameter *filename* is optional and defines the name of the file to load. By default this is automatically generated from the warehouse id and name, for example +-- "Warehouse-1234_Batumi.txt". +-- +-- Note that the warehouse **must not be started** and in the *Running* state in order to load the assets. In other words, loading should happen after the +-- @{#WAREHOUSE.New} command is specified in the code but before the @{#WAREHOUSE.Start} command is given. +-- +-- Loading the assets is done by +-- +-- warehouseBatumi:New(STATIC:FindByName("Warehouse Batumi")) +-- warehouseBatumi:Load("D:\\My Warehouse Data\\") +-- warehouseBatumi:Start() +-- +-- This sequence loads all assets from file. If a warehouse was captured in the last mission, it also respawns the static warehouse structure with the right coaliton. +-- However, it due to DCS limitations it is not possible to set the airbase coalition. This has to be done manually in the mission editor. Or alternatively, one could +-- spawn some ground units via a self request and let them capture the airbase. +-- +-- === -- -- # Examples -- @@ -4692,8 +4735,11 @@ function WAREHOUSE:onafterSave(From, Event, To, path, filename) MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) self:I(self.wid..text) - -- Loop over all assets in stock. local warehouseassets="" + warehouseassets=warehouseassets..string.format("coalition=%d\n", self:GetCoalition()) + warehouseassets=warehouseassets..string.format("country=$d\n", self:GetCountry()) + + -- Loop over all assets in stock. for _,_asset in pairs(self.stock) do local asset=_asset -- #WAREHOUSE.Assetitem @@ -4759,6 +4805,10 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) -- Split by line break. local assetdata=UTILS.Split(data,"\n") + -- Coalition and coutrny. + local Coalition + local Country + -- Loop over asset lines. local assets={} for _,asset in pairs(assetdata) do @@ -4768,21 +4818,33 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) local asset={} for _,descriptor in pairs(descriptors) do + local keyval=UTILS.Split(descriptor,"=") + if #keyval==2 then - local key=keyval[1] - local val=keyval[2] + + if keyval[1]=="coalition" then + -- Get coalition side. + Coalition=keyval[2] + elseif keyval[1]=="country" then + -- Get country id. + Country=keyval[2] + elseif #keyval==2 then - -- Livery or skill could be "nil". - if val=="nil" then - val=nil - end - - -- Convert string to number where necessary. - if key=="cargobay" or key=="weight" or key=="loadradius" then - asset[key]=tonumber(val) - else - asset[key]=val + local key=keyval[1] + local val=keyval[2] + + -- Livery or skill could be "nil". + if val=="nil" then + val=nil + end + + -- Convert string to number where necessary. + if key=="cargobay" or key=="weight" or key=="loadradius" then + asset[key]=tonumber(val) + else + asset[key]=val + end end end @@ -4792,6 +4854,11 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) table.insert(assets, asset) end + -- Respawn warehouse with prev coalition if necessary. + if Coalition~=self:GetCoalition() then + self:Captured(Coalition, Country) + end + for _,_asset in pairs(assets) do local asset=_asset --#WAREHOUSE.Assetitem @@ -4799,11 +4866,10 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) if group then self:AddAsset(group, 1, asset.attribute, asset.cargobay, asset.weight, asset.loadradius, asset.skill, asset.livery, asset.assignment) else - env.info("FF group doest not exit ".. tostring(asset.templatename)) + self:E(string.format("ERROR: Group %s doest not exit. Cannot be loaded as asset.", tostring(asset.templatename))) end end - end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From c6403325f590b4607ce04f605871c6d79062179a Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 17 Oct 2018 00:17:25 +0200 Subject: [PATCH 6/7] Warehouse v0.6.3 Persistance of assets. --- .../Moose/Functional/CarrierTrainer.lua | 1630 ----------------- .../Moose/Functional/Warehouse.lua | 250 ++- 2 files changed, 206 insertions(+), 1674 deletions(-) delete mode 100644 Moose Development/Moose/Functional/CarrierTrainer.lua diff --git a/Moose Development/Moose/Functional/CarrierTrainer.lua b/Moose Development/Moose/Functional/CarrierTrainer.lua deleted file mode 100644 index c24e7fbd5..000000000 --- a/Moose Development/Moose/Functional/CarrierTrainer.lua +++ /dev/null @@ -1,1630 +0,0 @@ ---- **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. --- Other aircraft and carriers **might** be possible in future but would need a different set of parameters. --- --- === --- --- ### Author: **Bankler** (original idea and script) --- --- @module Functional.CarrierTrainer --- @image MOOSE.JPG - ---- CARRIERTRAINER class. --- @type CARRIERTRAINER --- @field #string ClassName Name of the class. --- @field #string lid Class id string for output to DCS log file. --- @field #boolean Debug Debug mode. Messages to all about status. --- @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 #table players Table of players. --- @field #table menuadded Table of units where the F10 radio menu was added. --- @field #CARRIERTRAINER.Checkpoint Upwind Upwind checkpoint. --- @field #CARRIERTRAINER.Checkpoint BreakEarly Early break checkpoint. --- @field #CARRIERTRAINER.Checkpoint BreakLate Late brak checkpoint. --- @field #CARRIERTRAINER.Checkpoint Abeam Abeam checkpoint. --- @field #CARRIERTRAINER.Checkpoint Ninety At the ninety checkpoint. --- @field #CARRIERTRAINER.Checkpoint Wake Right behind the carrier. --- @field #CARRIERTRAINER.Checkpoint Groove In the groove checkpoint. --- @field #CARRIERTRAINER.Checkpoint Trap Landing checkpoint. --- @extends Core.Fsm#FSM - ---- Practice Carrier Landings --- --- === --- --- ![Banner Image](..\Presentations\CARRIERTRAINER\CarrierTrainer_Main.png) --- --- # The Trainer Concept --- --- --- bla bla --- --- @field #CARRIERTRAINER -CARRIERTRAINER = { - ClassName = "CARRIERTRAINER", - lid = nil, - Debug = true, - carrier = nil, - carriertype = nil, - alias = nil, - startZone = nil, - giantZone = nil, - players = {}, - menuadded = {}, - Upwind = {}, - Abeam = {}, - BreakEarly = {}, - BreakLate = {}, - Ninety = {}, - Wake = {}, - Groove = {}, - Trap = {}, -} - ---- Aircraft types. --- @type CARRIERTRAINER.AircraftType --- @field #string AV8B AV-8B Night Harrier. --- @field #string HORNET F/A-18C Lot 20 Hornet. -CARRIERTRAINER.AircraftType={ - AV8B="AV8BNA", - HORNET="FA-18C_hornet", -} - ---- Carrier types. --- @type CARRIERTRAINER.CarrierType --- @field #string STENNIS USS John C. Stennis (CVN-74) --- @field #string VINSON USS Carl Vinson (CVN-70) --- @field #string TARAWA USS Tarawa (LHA-1) --- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) -CARRIERTRAINER.CarrierType={ - STENNIS="Stennis", - VINSON="Vinson", - TARAWA="LHA_Tarawa", - KUZNETSOV="KUZNECOW" -} - ---- Difficulty level. --- @type CARRIERTRAINER.Difficulty --- @field #string EASY Easy difficulty: error margin 10 for high score and 20 for low score. No score for deviation >20. --- @field #string NORMAL Normal difficulty: error margin 5 deviation from ideal for high score and 10 for low score. No score for deviation >10. --- @field #string HARD Hard difficulty: error margin 2.5 deviation from ideal value for high score and 5 for low score. No score for deviation >5. -CARRIERTRAINER.Difficulty={ - EASY="Easy", - NORMAL="Normal", - HARD="Hard", -} - ---- Player data table holding all important parameters for each player. --- @type CARRIERTRAINER.PlayerData --- @field #number id Player ID. --- @field #string callsign Callsign of player. --- @field #number score Player score. --- @field #number totalscore Score of all landing attempts. --- @field #number passes Number of passes. --- @field #string collectedResultString Results text of all passes. --- @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. --- @field #string summary Result summary text. --- @field Wrapper.Client#CLIENT Client object of player. --- @field #string difficulty Difficulty level. - ---- Checkpoint parameters triggering the next step in the pattern. --- @type CARRIERTRAINER.Checkpoint --- @field #number Xmin Minimum allowed longitual distance to carrier. --- @field #number Xmax Maximum allowed longitual distance to carrier. --- @field #number Zmin Minimum allowed latitudal distance to carrier. --- @field #number Zmax Maximum allowed latitudal distance to carrier. --- @field #number LimitXmin Latitudal threshold for triggering the next step if XXmax. --- @field #number LimitZmin Latitudal threshold for triggering the next step if ZZmax. --- @field #number Altitude Optimal altitude at this point. --- @field #number AoA Optimal AoA at this point. --- @field #number Distance Optimal distance at this point. --- @field #number Speed Optimal speed at this point. --- @field #table Checklist Table of checklist text items to display at this point. - ---- Main radio menu. --- @field #table MenuF10 -CARRIERTRAINER.MenuF10={} - ---- Carrier trainer class version. --- @field #string version -CARRIERTRAINER.version="0.0.6" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create new carrier trainer. --- @param #CARRIERTRAINER self --- @param carriername Name of the aircraft carrier unit as defined in the mission editor. --- @param alias (Optional) Alias for the carrier. This will be used for radio messages and the F10 radius menu. Default is the carrier name as defined in the mission editor. --- @return #CARRIERTRAINER self -function CARRIERTRAINER:New(carriername, alias) - - -- Inherit everthing from FSM class. - local self = BASE:Inherit(self, FSM:New()) -- #CARRIERTRAINER - - -- 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 - local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.", carriername) - MESSAGE:New(text, 120):ToAll() - self:E(self.lid..text) - return nil - end - - --CARRIERTRAINER.Difficulty.EASY - - -- Set some string id for output to DCS.log file. - self.lid=string.format("CARRIERTRAINER %s | ", carriername) - - -- Get carrier type. - self.carriertype=self.carrier:GetTypeName() - - -- Set alias. - self.alias=alias or carriername - - ----------------------- - --- FSM Transitions --- - ----------------------- - - -- Start State. - self:SetStartState("Stopped") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") - self:AddTransition("Running", "Status", "Running") - self:AddTransition("Running", "Stop", "Stopped") - - - --- Triggers the FSM event "Start" that starts the carrier trainer. Initializes parameters and starts event handlers. - -- @function [parent=#CARRIERTRAINER] Start - -- @param #CARRIERTRAINER self - - --- Triggers the FSM event "Start" after a delay that starts the carrier trainer. Initializes parameters and starts event handlers. - -- @function [parent=#CARRIERTRAINER] __Start - -- @param #CARRIERTRAINER self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop" that stops the carrier trainer. Event handlers are stopped. - -- @function [parent=#CARRIERTRAINER] Stop - -- @param #CARRIERTRAINER self - - --- Triggers the FSM event "Stop" that stops the carrier trainer after a delay. Event handlers are stopped. - -- @function [parent=#CARRIERTRAINER] __Stop - -- @param #CARRIERTRAINER self - -- @param #number delay Delay in seconds. - - return self -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:I(self.lid..string.format("Starting Carrier Training %s for carrier unit %s of type %s.", CARRIERTRAINER.version, self.carrier:GetName(), self.carriertype)) - - -- Handle events. - self:HandleEvent(EVENTS.Birth) - - -- Init status check - self:__Status(5) -end - ---- On after Status event. Checks player status. --- @param #CARRIERTRAINER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function CARRIERTRAINER:onafterStatus(From, Event, To) - - -- Check player status. - self:_CheckPlayerStatus() - - -- Call status again in one second. - self:__Status(-1) -end - ---- On after Stop event. Unhandle events and stop status updates. --- @param #CARRIERTRAINER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function CARRIERTRAINER:onafterStop(From, Event, To) - self:UnHandleEvent(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(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) - self:T3(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) - self:T3(self.lid.."BIRTH: 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 entered unit %s (ID=%d) of group %s", _playername, _callsign, _unitName, _uid, _group:GetName()) - self:T(self.lid..text) - MESSAGE:New(text, 5):ToAllIf(self.Debug) - - -- Add Menu commands. - self:_AddF10Commands(_unitName) - - -- Init player. - if self.players[_playername]==nil then - self.players[_playername]=self:_InitNewPlayer(_unitName) - else - self:_InitNewRound(self.players[_playername]) - end - - end -end - ---- Initialize player data. --- @param #CARRIERTRAINER self --- @param #string unitname Name of the player unit. --- @return #CARRIERTRAINER.PlayerData Player data. -function CARRIERTRAINER:_InitNewPlayer(unitname) - - local playerData={} --#CARRIERTRAINER.PlayerData - - playerData.unit = UNIT:FindByName(unitname) - playerData.client = CLIENT:FindByName(playerData.unit.UnitName, nil, true) - playerData.callsign = playerData.unit:GetCallsign() - playerData.totalScore = 0 - playerData.passes = 0 - playerData.collectedResultString = "" - - playerData=self:_InitNewRound(playerData) - - return playerData -end - ---- Initialize new approach for player by resetting parmeters to initial values. --- @param #CARRIERTRAINER self --- @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.longDownwindDone = false - playerData.highestCarrierXDiff = -9999999 - playerData.secondsStandingStill = 0 - playerData.lowestAltitude = 999999 - return playerData -end - ---- Increase score for this approach. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data. --- @param #number amount Amount by which the score is increased. -function CARRIERTRAINER:_IncreaseScore(playerData, amount) - playerData.score = playerData.score + amount -end - ---- Append text to summary text. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data. --- @param #string item Text item appeded to the summary. -function CARRIERTRAINER:_AddToSummary(playerData, item) - playerData.summary = playerData.summary .. item .. "\n" -end - ---- Append text to result text. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data. --- @param #string item Text item appeded to the result. -function CARRIERTRAINER:_AddToCollectedResult(playerData, item) - playerData.collectedResultString = playerData.collectedResultString .. item .. "\n" -end - ---- Get relative heading of player wrt carrier. --- @param #CARRIERTRAINER self --- @param Wrapper.Unit#UNIT unit Player unit. --- @return #number Relative heading in degrees. -function CARRIERTRAINER:_GetRelativeHeading(unit) - --local a=self.carrier:GetVec3() - --local b=unit:GetVec3() - --local c={x=b.x-a.x, y=0, z=b.z-a.z} - --local headingCarrier=self.carrier:GetHeading() - --local headingPlayer=unit:GetHeading() - local vC=self.carrier:GetOrientationX() - local vP=unit:GetOrientationX() - - -- Get angle between the two orientation vectors in rad. - local relHead=math.acos(UTILS.VecDot(vC,vP)/UTILS.VecNorm(vC)/UTILS.VecNorm(vP)) - - -- Return heading in degrees. - return math.deg(relHead) -end - - ---- Carrier trainer event handler for event birth. --- @param #CARRIERTRAINER self -function CARRIERTRAINER:_CheckPlayerStatus() - - -- Loop over all players. - for _playerName,_playerData in pairs(self.players) do - local playerData = _playerData --#CARRIERTRAINER.PlayerData - - if playerData then - - self:I("player "..playerData.callsign) - - -- Player unit. - local unit = playerData.unit - - if unit:IsAlive() then - - self:_DetailedPlayerStatus(playerData) - if unit:IsInZone(self.giantZone) then - --self:_DetailedPlayerStatus(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 - 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 - end - end - end - -end - - - ---- Check limits for reaching next step. --- @param #CARRIERTRAINER self --- @param #number X X position of player unit. --- @param #number Z Z position of player unit. --- @param #CARRIERTRAINER.Checkpoint check Checkpoint. --- @return #boolean If true, checkpoint condition for next step was reached. -function CARRIERTRAINER:_CheckLimits(X, Z, check) - - local next=false - if check.LimitXmin and Xcheck.LimitXmax then - next=true - elseif check.LimitZmin and Zcheck.LimitZmax then - next=true - end - - return next -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="when entering pattern" - elseif step==2 then - name="on upwind leg" - elseif step==3 then - name="at the early break" - elseif step==4 then - name="at the late break" - 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==8 then - name="in the groove" - elseif step==9 then - name="trapped" - end - - return name -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. -function CARRIERTRAINER:_GetDistances(unit) - - -- Vector to carrier - local a=self.carrier:GetVec3() - - -- Vector to player - local b=unit:GetVec3() - - -- Vector from carrier to player. - local c={x=b.x-a.x, y=0, z=b.z-a.z} - - -- Orientation of carrier. - local x=self.carrier:GetOrientationX() - - -- Projection of player pos on x component. - local dx=UTILS.VecDot(x,c) - - -- Orientation of carrier. - local z=self.carrier:GetOrientationZ() - - -- Projection of player pos on z component. - local dz=UTILS.VecDot(z,c) - - return dx,dz -end - ---- Check if a player is within the right area. --- @param #CARRIERTRAINER self --- @param #number X X distance player to carrier. --- @param #number Z Z distance player to carrier. --- @param #table pos Position data limits. --- @return #boolean If true, approach should be aborted. -function CARRIERTRAINER:_CheckAbort(X, Z, pos) - - local abort=false - if pos.Xmin and Xpos.Xmax then - abort=true - elseif pos.Zmin and Zpos.Zmax then - abort=true - end - - return abort -end - ---- Generate a text if a player is too far from where he should be. --- @param #CARRIERTRAINER self --- @param #number X X distance player to carrier. --- @param #number Z Z distance player to carrier. --- @param #table posData Position data limits. -function CARRIERTRAINER:_TooFarOutText(X, Z, posData) - - local text="You are too far" - - local xtext=nil - if posData.Xmin and XposData.Xmax then - xtext=" behind" - end - - local ztext=nil - if posData.Zmin and ZposData.Zmax then - ztext=" starboard (right)" - end - - if xtext and ztext then - text=text..xtext.." and"..ztext - elseif xtext then - text=text..xtext - elseif ztext then - text=text..ztext - end - - text=text.." of the carrier!" - - return text -end - ---- Pattern aborted. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data. --- @param #number X X distance player to carrier. --- @param #number Z Z distance player to carrier. --- @param #table posData Position data. -function CARRIERTRAINER:_AbortPattern(playerData, X, Z, posData) - - local toofartext=self:_TooFarOutText(X, Z, posData) - - self:_SendMessageToPlayer(toofartext.." Abort approach!", 15, playerData ) - - MESSAGE:New(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)), 60):ToAllIf(self.Debug) - - self:_AddToSummary(playerData, "Approach aborted.") - - self:_PrintFinalScore(playerData, 30, -2) - - self:_HandleCollectedResult(playerData, -2) - - playerData.step = 0 -end - - ---- Provide info about player status on the fly. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data. -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 dx,dz=self:_GetDistances(unit) - - -- Player and carrier position vector. - local playerPosition = playerData.unit:GetVec3() - local carrierPosition = self.carrier:GetVec3() - - local diffZ = playerPosition.z - carrierPosition.z - local diffX = playerPosition.x - carrierPosition.x - - local heading=unit:GetCoordinate():HeadingTo(self.startZone:GetCoordinate()) - - local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3() - local velo=unit:GetVelocityVec3() - - local text=string.format("%s, current AoA=%.1f\n", playerData.callsign, aoa) - 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("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)) - --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 - ---- Init parameters for USS Stennis carrier. --- @param #CARRIERTRAINER self -function CARRIERTRAINER:_InitStennis() - - -- Upwind leg - self.Upwind.Xmin=-4000 -- TODO Should be withing 4 km behind carrier. Why? - self.Upwind.Xmax=nil - self.Upwind.Zmin=0 - self.Upwind.Zmax=500 - self.Upwind.LimitXmin=nil - self.Upwind.LimitXmax=nil - self.Upwind.LimitZmin=nil - self.Upwind.LimitZmax=nil - self.upwind.Alitude=UTILS.FeetToMeters(800) - self.Upwind.AoA=8.1 - self.Upwind.Distance=nil - - -- Early break - self.BreakEarly.Xmin=-500 - self.BreakEarly.Xmax=nil - self.BreakEarly.Zmin=-3700 - self.BreakEarly.Zmax=1500 - self.BreakEarly.LimitXmin=nil - self.BreakEarly.LimitXmax=nil - self.BreakEarly.LimitZmin=-370 --0.2 NM - self.BreakEarly.LimitZmax=nil - self.BreakEarly.Alitude=UTILS.FeetToMeters(800) - self.BreakEarly.AoA=8.1 - self.BreakEarly.Distance=nil - - -- Late break - self.BreakLate.Xmin=-500 - self.BreakLate.Xmax=nil - self.BreakLate.Zmin=-3700 - self.BreakLate.Zmax=1500 - self.BreakLate.LimitXmin=nil - self.BreakLate.LimitXmax=nil - self.BreakLate.LimitZmin=-1470 --0.8 NM - self.BreakLate.LimitZmax=nil - self.BreakLate.Alitude=UTILS.FeetToMeters(800) - self.BreakLate.AoA=8.1 - self.BreakLate.Distance=nil - - -- Abeam position - self.Abeam.Xmin=nil - self.Abeam.Xmax=nil - self.Abeam.Zmin=-3700 - self.Abeam.Zmax=-1000 - self.Abeam.LimitXmin=-200 - self.Abeam.LimitXmax=nil - self.Abeam.LimitZmin=nil - self.Abeam.LimitZmax=nil - self.Abeam.Alitude=UTILS.FeetToMeters(600) - self.Abeam.AoA=8.1 - self.Abeam.Distance=nil - - -- At the ninety - self.Ninety.Xmin=-3700 - self.Ninety.Xmax=0 - self.Ninety.Zmin=-3700 - self.Ninety.Zmax=nil - self.Ninety.LimitXmin=nil - self.Ninety.LimitXmax=nil - self.Ninety.LimitZmin=nil - self.Ninety.LimitZmax=-1111 - self.Ninety.Altitude=UTILS.FeetToMeters(500) - self.Abeam.AoA=8.1 - self.Abeam.Distance=nil - - -- Wake position - self.Wake.Xmin=-4000 - self.Wake.Xmax=0 - self.Wake.Zmin=-2000 - self.Wake.Zmax=nil - self.Wake.LimitXmin=nil - self.Wake.LimitXmax=nil - self.Wake.LimitZmin=nil - self.Wake.LimitZmax=0 - self.Wake.Alitude=UTILS.FeetToMeters(370) - self.Wake.AoA=8.1 - self.Wake.Distance=nil - - -- In the groove - self.Groove.Xmin=-4000 - self.Groove.Xmax=100 - - -- Landing trap - self.Trap.Xmin=-3000 - self.Trap.Xmax=nil - self.Trap.Zmin=-2000 - self.Trap.Zmax=2000 - self.Trap.Limit=nil - self.Trap.Alitude=nil - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- CARRIER TRAINING functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Initialize player data. --- @param #CARRIERTRAINER self --- @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) - - self:_InitNewRound(playerData) - playerData.step = 1 -end - ---- Start landing pattern. --- @param #CARRIERTRAINER self --- @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 on the upwind.", playerData.callsign) - 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) - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) - - -- Abort condition check. - if self:_CheckAbort(diffX,diffZ, self.Upwind) then - self:_AbortPattern(playerData, diffX, diffZ, self.Upwind) - return - end - - -- Check if we are in front of the boat (diffX > 0). - if self:_CheckLimits(diffX, diffZ, self.Upwind) then - - -- Get - local score, hint=self:_AltitudeCheck(playerData, self.Upwind) - - self:_SendMessageToPlayer(hint, 8, playerData) - self:_AddToSummary(playerData, hint) - - -- Next step. - playerData.step = 3 - end -end - - ---- Break. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data table. --- @param #string part Part of the break. -function CARRIERTRAINER:_Break(playerData, part) - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) - - -- Check abort conditions. - if self:_CheckAbort(diffX, diffZ, self.Break) then - self:_AbortPattern(playerData, diffX, diffZ, self.Break) - return - end - - -- Early or late break. - local limit = self.BreakEarly - if part == "late" then - limit = self.BreakLate - end - - -- Check if too far left - --if diffZ < limit then - if self:_CheckLimits(diffX, diffZ, limit) then - - -- Check altitude. - local score, hint=self:_AltitudeCheck(playerData, self.Upwind) - - - self:_SendMessageToPlayer(hint, 8, playerData) - self:_AddToSummary(playerData, 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) - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) - - -- Check abort conditions. - if self:_CheckAbort(diffX, diffZ, self.Abeam) then - self:_AbortPattern(playerData, diffX, diffZ, self.Abeam) - return - end - - -- Check nest step threshold. - if self:_CheckLimits(diffX, diffZ, self.Abeam) then - - -- Get AoA. - local aoa = playerData.unit:GetAoA() - local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData) - - local onSpeedScore = self:_GetOnSpeedScore(aoa) - - local idealAltitude = 600 - local score, hint=self:_AltitudeCheck(playerData, self.Abeam) - - local distanceHint = "" - local distanceScore - local diffEast = carrierPosition.z - playerPosition.z - - local idealDistance = UTILS.NMToMeters(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 ) - - self:_IncreaseScore(playerData, score + distanceScore + onSpeedScore) - self:_PrintScore(score + distanceScore + onSpeedScore, playerData, true) - - self:_AddToSummary(playerData, fullHint .. " (" .. aoaFeedback .. ")") - - -- Proceed to next step. - 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 - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - local diffX, diffZ = self:_GetDistances(playerData.unit) - - -- Check we are not too far out w.r.t back of the boat. - if diffX < limit then - - local headingPlayer = playerData.unit:GetHeading() - local headingCarrier = self.carrier:GetHeading() - - --TODO: Take carrier heading != 0 into account! - - if (headingPlayer > 170) then - - local hint = "Too long downwind. Turn final earlier next time." - self:_SendMessageToPlayer( hint, 8, playerData ) - local score = -40 - self:_IncreaseScore(playerData, score) - self:_PrintScore(score, playerData, true) - self:_AddToSummary(playerData, hint) - playerData.longDownwindDone = true - end - - end -end - ---- Ninety. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data table. -function CARRIERTRAINER:_Ninety(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 < -3700 or diffX < -3700 or diffX > 0) then - if self:_CheckAbort(diffX, diffZ, self.Ninety) then - self:_AbortPattern(playerData, diffX, diffZ, self.Ninety) - return - end - - local limitEast = -1111 --0.6nm - - --if diffZ > limitEast then - if self:_CheckLimits(diffX, diffZ, self.Ninety) then - - local idealAltitude = 500 - local score, hint=self:_AltitudeCheck(playerData, self.Ninety) - - 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) - - self:_IncreaseScore(playerData, score + onSpeedScore) - self:_PrintScore(score + onSpeedScore, playerData, true) - - self:_AddToSummary(playerData, 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) - - -- 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 - if self:_CheckAbort(diffX, diffZ, self.Wake) then - self:_AbortPattern(playerData, diffX, diffZ, self.Wake) - return - end - - --if diffZ > 0 then - if self:_CheckLimits(diffX, diffZ, self.Wake) then - - local idealAltitude = 370 - local score, hint=self:_AltitudeCheck(playerData, self.Wake) - - self:_SendMessageToPlayer( hint, 8, playerData ) - self:_PrintAltitudeFeedback(altitude, idealAltitude, playerData) - - local aoa = playerData.unit:GetAoA() - - local aoaFeedback = self:_PrintAoAFeedback(aoa, 8.1, playerData) - local onSpeedScore = self:_GetOnSpeedScore(aoa) - - self:_IncreaseScore(playerData, score + onSpeedScore) - self:_PrintScore(score + onSpeedScore, playerData, true) - self:_AddToSummary(playerData, 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 - - --TODO -100?! - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - 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 - - --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 - - 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 score, hint=self:_AltitudeCheck(playerData, self.Groove) - - 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) - - self:_IncreaseScore(playerData, score + onSpeedScore) - self:_PrintScore(score + onSpeedScore, playerData, true) - - local fullHint = hint .. " (" .. aoaFeedback .. ")" - - self:_AddToSummary(playerData, 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 - - -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) - diffZ, diffX = self:_GetDistances(playerData.unit) - - - --if(diffZ < -2000 or diffZ > 2000 or diffX < -3000) then - if self:_CheckAbort(diffX, diffZ, self.Trap) then - self:_AbortPattern(playerData, diffX, diffZ, self.Trap) - 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 - - 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 - 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) - - self:_AddToSummary(playerData, hint) - - self:_PrintFinalScore(playerData, 60, wire) - self:_HandleCollectedResult(playerData, wire) - - playerData.step = 0 - end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --- Menu Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ---- Add menu commands for player. --- @param #CARRIERTRAINER self --- @param #string _unitName Name of player unit. -function CARRIERTRAINER:_AddF10Commands(_unitName) - self:F(_unitName) - - -- Get player unit and name. - local _unit, playername = self:_GetPlayerUnitAndName(_unitName) - - -- Check for player unit. - if _unit and playername then - - -- Get group and ID. - local group=_unit:GetGroup() - local _gid=group:GetID() - - if group and _gid then - - if not self.menuadded[_gid] then - - -- Enable switch so we don't do this twice. - self.menuadded[_gid] = true - - -- Main F10 menu: F10/Carrier Trainer// - if CARRIERTRAINER.MenuF10[_gid] == nil then - CARRIERTRAINER.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "Carrier Trainer") - end - local _rangePath = missionCommands.addSubMenuForGroup(_gid, self.alias, CARRIERTRAINER.MenuF10[_gid]) - local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Results", _rangePath) - local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) - local _infoPath = missionCommands.addSubMenuForGroup(_gid, "Carrier Info", _rangePath) - - -- F10/On the Range//Stats/ - --missionCommands.addCommandForGroup(_gid, "All Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "My Results", _statsPath, self._DisplayBombingResults, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "Reset All Results", _statsPath, self._ResetRangeStats, self, _unitName) - -- F10/On the Range//My Settings/ - --missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) - -- F10/On the Range//Range Information - --missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayCarrierWeather, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) - --missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName) - end - else - self:T(self.lid.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) - end - else - self:T(self.lid.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName) - end - -end - ---- Report weather conditions at range. Temperature, QFE pressure and wind data. --- @param #CARRIERTRAINER self --- @param #string _unitname Name of the player unit. -function CARRIERTRAINER:_DisplayCarrierWeather(_unitname) - self:E(_unitname) - - -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName(_unitname) - - -- Check if we have a player. - if unit and playername then - - -- Message text. - local text="" - - -- Current coordinates. - local coord=self.carrier:GetCoordinate() - - -- Get atmospheric data at range location. - local position=self.location --Core.Point#COORDINATE - local T=position:GetTemperature() - local P=position:GetPressure() - local Wd,Ws=position:GetWind() - - -- Get Beaufort wind scale. - local Bn,Bd=UTILS.BeaufortScale(Ws) - - local WD=string.format('%03d°', Wd) - local Ts=string.format("%d°C",T) - - local hPa2inHg=0.0295299830714 - local hPa2mmHg=0.7500615613030 - - local settings=_DATABASE:GetPlayerSettings(playername) or _SETTINGS --Core.Settings#SETTINGS - local tT=string.format("%d°C",T) - local tW=string.format("%.1f m/s", Ws) - local tP=string.format("%.1f mmHg", P*hPa2mmHg) - if settings:IsImperial() then - tT=string.format("%d°F", UTILS.CelciusToFarenheit(T)) - tW=string.format("%.1f knots", UTILS.MpsToKnots(Ws)) - tP=string.format("%.2f inHg", P*hPa2inHg) - end - - - -- Message text. - text=text..string.format("Weather Report at %s:\n", self.rangename) - 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("QFE %.1f hPa = %s", P, tP) - - - -- Send message to player group. - --self:_DisplayMessageToGroup(unit, text, nil, true) - self:_SendMessageToPlayer(text, 30, self.players[playername]) - - -- Debug output. - self:T2(self.lid..text) - else - self:T(self.lid..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname)) - end -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- MISC functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Evaluate player's altitude at checkpoint. --- @param #CARRIERTRAINER self --- @param #CARRIERTRAINER.PlayerData playerData Player data table. --- @param #CARRIERTRAINER.Checkpoint checkpoint Checkpoint. --- @return #number Score. --- @return #string Message text. -function CARRIERTRAINER:_AltitudeCheck(playerData, checkpoint) - - -- Player altitude. - local altitude=playerData.unit:GetAltitude() - - local lowscore - local badscore - if playerData.difficulty==CARRIERTRAINER.Difficulty.EASY then - lowscore=10 - badscore=20 - elseif playerData.difficulty==CARRIERTRAINER.Difficulty.NORMAL then - lowscore=5 - badscore=10 - elseif playerData.difficulty==CARRIERTRAINER.Difficulty.HARD then - lowscore=2.5 - badscore=5 - end - - -- Altitude error +-X% - local _error=(altitude-checkpoint.Altitude)/checkpoint.Altitude*100 - - local score - local hint - local steptext=self:_StepName(playerData.step) - if _error>badscore then - score = 5 - hint = string.format("You're high %s.", steptext) - elseif _error>lowscore then - score = 7 - hint = string.format("You're slightly high %s.", steptext) - elseif _errorbadscore then - score = 0 - hint = string.format("too far from the boat (%.1f NM)", dnm) - elseif _error>lowscore then - score = 5 - hint = string.format("slightly too far from the boat (%.1f NM)", dnm) - elseif _errorbadscore then --Slow - score = 0 - elseif _error>lowscore then --Slightly slow - score = 5 - elseif _error 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(string.format("%s, %s, ", self.alias, playerData.callsign)..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. --- @param #boolean printtotal Also print total score. -function CARRIERTRAINER:_PrintScore(score, playerData, printtotal) - - if printtotal 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 - - local heading=playerData.unit:GetCoordinate():HeadingTo(self.startZone:GetCoordinate()) - local distance=playerData.unit:GetCoordinate():Get2DDistance(self.startZone:GetCoordinate()) - local text=string.format("%s, fly heading %d for %d nm to restart the pattern.", playerData.callsign, heading, UTILS.MetersToNM(distance)) - --"Return south 4 nm (over the trailing ship), towards WP 1, to restart the pattern." - self:_SendMessageToPlayer(text, 30, playerData) -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 - - diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index fc4ce4c9c..421eb5a48 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -679,12 +679,27 @@ -- -- # Persistance of Assets -- --- Assets in stock of a warehouse can be saved to a file on the hard drive and then loaded from the file at a later point. This enables to restart the mission +-- Assets in stock of a warehouse can be saved to a file on your hard drive and then loaded from that file at a later point. This enables to restart the mission -- and restore the warehouse stock. -- --- ## Prerequisite +-- ## Prerequisites -- --- **Important** By default, DCS does not allow for writing data to files. Therefore, one first has to comment out the line "blblba" in the the file "blabla" +-- **Important** By default, DCS does not allow for writing data to files. Therefore, one first has to comment out the line "sanitizeModule('io')", i.e. +-- +-- do +-- sanitizeModule('os') +-- --sanitizeModule('io') +-- sanitizeModule('lfs') +-- require = nil +-- loadlib = nil +-- end +-- +-- in the file "MissionScripting.lua", which is located in the subdirectory "Scripts" of your DCS installation root directory. +-- +-- ### Don'ts +-- +-- Do not use **semi-colons** or **equal signs** in the group names of your assets as these are used as separators in the saved and loaded files texts. +-- If you do, it will cause problems and give you a headache! -- -- ## Save Assets -- @@ -695,7 +710,7 @@ -- -- warehouseBatumi:Save("D:\\My Warehouse Data\\") -- --- This will save all asset data to as "D:\My Warehouse Data\Warehouse-1234_Batumi.txt". +-- This will save all asset data to in "D:\\My Warehouse Data\\Warehouse-1234_Batumi.txt". -- -- ## Load Assets -- @@ -1699,7 +1714,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.6.2" +WAREHOUSE.version="0.6.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1833,9 +1848,12 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("Running", "Pause", "Paused") -- Pause the processing of new requests. Still possible to add assets and requests. self:AddTransition("Paused", "Unpause", "Running") -- Unpause the warehouse. Queued requests are processed again. self:AddTransition("*", "Stop", "Stopped") -- Stop the warehouse. + self:AddTransition("Stopped", "Restart", "Running") -- Restart the warehouse when it was stopped before. + self:AddTransition("Loaded", "Restart", "Running") -- Restart the warehouse when assets were loaded from file before. self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition. self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated! + self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned! self:AddTransition("Attacked", "Captured", "Running") -- Warehouse was captured by another coalition. It must have been attacked first. self:AddTransition("*", "AirbaseCaptured", "*") -- Airbase was captured by other coalition. self:AddTransition("*", "AirbaseRecaptured", "*") -- Airbase was re-captured from other coalition. @@ -1855,15 +1873,24 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the warehouse and all its event handlers. + --- Triggers the FSM event "Stop". Stops the warehouse and all its event handlers. All waiting and pending queue items are deleted as well and all assets are removed from stock. -- @function [parent=#WAREHOUSE] Stop -- @param #WAREHOUSE self - --- Triggers the FSM event "Stop" after a delay. Stops the warehouse and all its event handlers. + --- Triggers the FSM event "Stop" after a delay. Stops the warehouse and all its event handlers. All waiting and pending queue items are deleted as well and all assets are removed from stock. -- @function [parent=#WAREHOUSE] __Stop -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Restart". Restarts the warehouse from stopped state by reactivating the event handlers *only*. + -- @function [parent=#WAREHOUSE] Restart + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Restart" after a delay. Restarts the warehouse from stopped state by reactivating the event handlers *only*. + -- @function [parent=#WAREHOUSE] __Restart + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Pause". Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. -- @function [parent=#WAREHOUSE] Pause -- @param #WAREHOUSE self @@ -2116,6 +2143,26 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string To To state. + --- Triggers the FSM event "ChangeCountry" so the warehouse is respawned with the new country. + -- @function [parent=#WAREHOUSE] ChangeCountry + -- @param #WAREHOUSE self + -- @param DCS#country.id Country New country id of the warehouse. + + --- Triggers the FSM event "ChangeCountry" after a delay so the warehouse is respawned with the new country. + -- @function [parent=#WAREHOUSE] __ChangeCountry + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param DCS#country.id Country Country id which has captured the warehouse. + + --- On after "ChangeCountry" event user function. Called when the warehouse has changed its country. + -- @function [parent=#WAREHOUSE] OnAfterChangeCountry + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#country.id Country New country id of the warehouse, i.e. a number @{DCS#country.id} enumerator. + + --- Triggers the FSM event "Captured" when a warehouse has been captured by another coalition. -- @function [parent=#WAREHOUSE] Captured -- @param #WAREHOUSE self @@ -3006,6 +3053,36 @@ function WAREHOUSE:onafterStart(From, Event, To) self:__Status(-1) end +--- On after "Restart" event. Restarts the warehouse when it was in stopped state by reactivating the event handlers *only*. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterRestart(From, Event, To) + + self:I(self.wid..string.format("Restarting Warehouse %s.", self.alias)) + + -- Handle events: + self:HandleEvent(EVENTS.Birth, self._OnEventBirth) + self:HandleEvent(EVENTS.EngineStartup, self._OnEventEngineStartup) + self:HandleEvent(EVENTS.Takeoff, self._OnEventTakeOff) + self:HandleEvent(EVENTS.Land, self._OnEventLanding) + self:HandleEvent(EVENTS.EngineShutdown, self._OnEventEngineShutdown) + self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead) + self:HandleEvent(EVENTS.Dead, self._OnEventCrashOrDead) + self:HandleEvent(EVENTS.BaseCaptured, self._OnEventBaseCaptured) + + -- This event triggers the arrived event for air assets. + -- TODO Might need to make this landing or optional! + -- In fact, it would be better if the type could be defined for only for the warehouse which receives stuff, + -- since there will be warehouses with small airbases and little space or other problems! + self:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) + + -- Start the status monitoring. + self:__Status(-1) + +end + --- On after "Stop" event. Stops the warehouse, unhandles all events. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4570,6 +4647,73 @@ function WAREHOUSE:onafterDefeated(From, Event, To) end end + +--- On before "ChangeCountry" event. Checks whether a change of country is necessary by comparing the actual country to the the requested one. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param DCS#country.id Country which has captured the warehouse. +function WAREHOUSE:onbeforeChangeCountry(From, Event, To, Country) + + local currentCountry=self:GetCountry() + + -- Message. + local text=string.format("Warehouse %s: request to change country %d-->%d", self.alias, currentCountry, Country) + self:_DebugMessage(text, 10) + + -- Check if current or requested coalition or country match. + if currentCountry~=Country then + return true + end + + return false +end + + +--- On after "ChangeCountry" event. Warehouse is respawned with the specified country. All queued requests are deleted and the owned airbase is reset if the coalition is changed by changing the +-- country. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param DCS#country.id Country which has captured the warehouse. +function WAREHOUSE:onafterChangeCountry(From, Event, To, Country) + + local CoalitionOld=self:GetCoalition() + + -- Respawn warehouse with new coalition/country. + self.warehouse:ReSpawn(Country) + + local CoalitionNew=self:GetCoalition() + + -- Delete all waiting requests because they are not valid any more. + self.queue=nil + self.queue={} + + -- Airbase could have been captured before and already belongs to the new coalition. + local airbase=AIRBASE:FindByName(self.airbasename) + local airbasecoaltion=airbase:GetCoalition() + + if CoalitionNew==airbasecoaltion then + -- Airbase already owned by the coalition that captured the warehouse. Airbase can be used by this warehouse. + self.airbase=airbase + else + -- Airbase is owned by other coalition. So this warehouse does not have an airbase unil it is captured. + self.airbase=nil + end + + -- Debug smoke. + if self.Debug then + if CoalitionNew==coalition.side.RED then + self:GetCoordinate():SmokeRed() + elseif CoalitionNew==coalition.side.BLUE then + self:GetCoordinate():SmokeBlue() + end + end + +end + --- On after "Captured" event. Warehouse has been captured by another coalition. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4580,39 +4724,15 @@ end function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) -- Message. - local text=string.format("Warehouse %s: We were captured by enemy coalition (ID=%d)!", self.alias, Coalition) + local text=string.format("Warehouse %s: We were captured by enemy coalition (side=%d)!", self.alias, Coalition) self:_InfoMessage(text) - - -- Respawn warehouse with new coalition/country. - self.warehouse:ReSpawn(Country) - - -- Delete all waiting requests because they are not valid any more - self.queue=nil - self.queue={} - - -- Airbase could have been captured before and already belongs to the new coalition. - local airbase=AIRBASE:FindByName(self.airbasename) - local airbasecoaltion=airbase:GetCoalition() - - if Coalition==airbasecoaltion then - -- Airbase already owned by the coalition that captured the warehouse. Airbase can be used by this warehouse. - self.airbase=airbase - else - -- Airbase is owned by other coalition. So this warehouse does not have an airbase unil it is captured. - self.airbase=nil - end - - -- Debug smoke. - if self.Debug then - if Coalition==coalition.side.RED then - self:GetCoordinate():SmokeRed() - elseif Coalition==coalition.side.BLUE then - self:GetCoordinate():SmokeBlue() - end - end - + + -- Change coalition and country of warehouse static. + self:ChangeCoaliton(Coalition, Country) + end + --- On after "AirbaseCaptured" event. Airbase of warehouse has been captured by another coalition. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4737,7 +4857,7 @@ function WAREHOUSE:onafterSave(From, Event, To, path, filename) local warehouseassets="" warehouseassets=warehouseassets..string.format("coalition=%d\n", self:GetCoalition()) - warehouseassets=warehouseassets..string.format("country=$d\n", self:GetCountry()) + warehouseassets=warehouseassets..string.format("country=%d\n", self:GetCountry()) -- Loop over all assets in stock. for _,_asset in pairs(self.stock) do @@ -4755,21 +4875,61 @@ function WAREHOUSE:onafterSave(From, Event, To, path, filename) else name=string.format("%s=%s;", key, value) end - env.info(name) assetstring=assetstring..name end + self:I(string.format("Loaded asset: %s", assetstring)) end -- Add asset string. warehouseassets=warehouseassets..assetstring.."\n" end - --print(warehouseassets) -- Save file. _savefile(filename, warehouseassets) end + +--- On before "Load" event. Checks if the file the warehouse data should be loaded from exists. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path Path where the file is loaded from. +-- @param #string filename (Optional) Name of the file containing the asset data. +function WAREHOUSE:onbeforeLoad(From, Event, To, path, filename) + + + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) + return true + else + return false + end + end + + -- Set file name. + filename=filename or string.format("WAREHOUSE-%d_%s.txt", self.uid, self.alias) + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local exists=_fileexists(filename) + + if exists then + return true + else + self:_ErrorMessage(string.format("ERROR: file %s does not exist! Cannot load assets.", filename), 60) + end + +end + + --- On after "Load" event. Warehouse assets are loaded from file on disk. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4825,10 +4985,10 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) if keyval[1]=="coalition" then -- Get coalition side. - Coalition=keyval[2] + Coalition=tonumber(keyval[2]) elseif keyval[1]=="country" then -- Get country id. - Country=keyval[2] + Country=tonumber(keyval[2]) elseif #keyval==2 then local key=keyval[1] @@ -4855,8 +5015,10 @@ function WAREHOUSE:onafterLoad(From, Event, To, path, filename) end -- Respawn warehouse with prev coalition if necessary. - if Coalition~=self:GetCoalition() then - self:Captured(Coalition, Country) + self:E(string.format("Changing country %d-->%d (before)", self:GetCountry(), Country)) + if Country~=self:GetCountry() then + self:E(string.format("Changing country %d-->%d (after)", self:GetCountry(), Country)) + self:ChangeCountry(Country) end for _,_asset in pairs(assets) do From 8a78308dbcb9a4cd2c523b2f51402df893377f55 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Wed, 17 Oct 2018 19:21:35 +0200 Subject: [PATCH 7/7] Improvements on the pickup. Avoid a template as part of a CarrierSet to be considered as a valid carrier for pickup. And improved the timing of the loading. --- Moose Development/Moose/AI/AI_Cargo.lua | 12 +- .../Moose/AI/AI_Cargo_Dispatcher.lua | 523 +++++++++--------- 2 files changed, 270 insertions(+), 265 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index c585c0674..c0c9fa8a7 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -213,8 +213,8 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) local Boarding = false - local LoadInterval = 5 - local LoadDelay = 0 + local LoadInterval = 2 + local LoadDelay = 1 local Carrier_List = {} local Carrier_Weight = {} @@ -263,7 +263,9 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) --Cargo:Ungroup() Cargo:__Board( -LoadDelay, CarrierUnit ) self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone ) - + + LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval + -- So now this CarrierUnit has Cargo that is being loaded. -- This will be used further in the logic to follow and to check cargo status. self.Carrier_Cargo[Cargo] = CarrierUnit @@ -278,9 +280,7 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) end end - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval - - + end if not Loaded == true then diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 533a83e15..fd4a03d52 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -894,275 +894,280 @@ end -- @param #AI_CARGO_DISPATCHER self function AI_CARGO_DISPATCHER:onafterMonitor() + self:F("Carriers") + self.SetCarrier:Flush() + for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do local Carrier = Carrier -- Wrapper.Group#GROUP - local AI_Cargo = self.AI_Cargo[Carrier] - if not AI_Cargo then - - -- ok, so this Carrier does not have yet an AI_CARGO handling object... - -- let's create one and also declare the Loaded and UnLoaded handlers. - self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius ) - AI_Cargo = self.AI_Cargo[Carrier] + if Carrier:IsAlive() == true then + local AI_Cargo = self.AI_Cargo[Carrier] + if not AI_Cargo then - --- Pickup event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickup - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - function AI_Cargo.OnAfterPickup( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, PickupZone ) - self:Pickup( CarrierGroup, Coordinate, Speed, Height, PickupZone ) + -- ok, so this Carrier does not have yet an AI_CARGO handling object... + -- let's create one and also declare the Loaded and UnLoaded handlers. + self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius ) + AI_Cargo = self.AI_Cargo[Carrier] + + --- Pickup event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickup + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. + -- @param #number Height Height in meters to move to the pickup coordinate. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + function AI_Cargo.OnAfterPickup( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, PickupZone ) + self:Pickup( CarrierGroup, Coordinate, Speed, Height, PickupZone ) + end + + --- Load event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoad + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterLoad( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) + self:Load( CarrierGroup, PickupZone ) + end + + --- Loading event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoading + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterBoard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) + self:Loading( CarrierGroup, Cargo, CarrierUnit, PickupZone ) + end + + --- Loaded event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. + -- A CarrierUnit can be part of the larger CarrierGroup. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterLoaded( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) + self:Loaded( CarrierGroup, Cargo, CarrierUnit, PickupZone ) + end + + --- PickedUp event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickedUp + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterPickedUp( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) + self:PickedUp( CarrierGroup, PickupZone ) + self:Transport( CarrierGroup ) + end + + + --- Deploy event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeploy + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. + -- @param #number Height Height in meters to move to the deploy coordinate. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterDeploy( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, DeployZone ) + self:Deploy( CarrierGroup, Coordinate, Speed, Height, DeployZone ) + end + + + --- Unload event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnload + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloading( Carrier, Cargo, CarrierUnit, DeployZone ) + end + + --- UnLoading event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloading + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterUnboard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloading( CarrierGroup, Cargo, CarrierUnit, DeployZone ) + end + + + --- Unloaded event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. + -- A CarrierUnit can be part of the larger CarrierGroup. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloaded( Carrier, Cargo, CarrierUnit, DeployZone ) + end + + --- Deployed event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeployed + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterDeployed( AI_Cargo, Carrier, From, Event, To, DeployZone ) + self:Deployed( Carrier, DeployZone ) + end + + --- Home event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterHome + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. + -- @param #number Height Height in meters to move to the home coordinate. + -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterHome( AI_Cargo, Carrier, From, Event, To, Coordinate, Speed, Height, HomeZone ) + self:Home( Carrier, Coordinate, Speed, Height, HomeZone ) + end end - - --- Load event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoad - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoad( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:Load( CarrierGroup, PickupZone ) - end - - --- Loading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterBoard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loading( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- Loaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoaded( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loaded( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- PickedUp event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickedUp - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterPickedUp( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:PickedUp( CarrierGroup, PickupZone ) - self:Transport( CarrierGroup ) - end - - - --- Deploy event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeploy - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. - -- @param #number Height Height in meters to move to the deploy coordinate. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeploy( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, DeployZone ) - self:Deploy( CarrierGroup, Coordinate, Speed, Height, DeployZone ) - end - - - --- Unload event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnload - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- UnLoading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnboard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( CarrierGroup, Cargo, CarrierUnit, DeployZone ) - end - - - --- Unloaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloaded( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- Deployed event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeployed - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeployed( AI_Cargo, Carrier, From, Event, To, DeployZone ) - self:Deployed( Carrier, DeployZone ) - end - - --- Home event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterHome - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterHome( AI_Cargo, Carrier, From, Event, To, Coordinate, Speed, Height, HomeZone ) - self:Home( Carrier, Coordinate, Speed, Height, HomeZone ) - end - end - - -- The Pickup sequence ... - -- Check if this Carrier need to go and Pickup something... - -- So, if the cargo bay is not full yet with cargo to be loaded ... - self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) - if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then - -- ok, so there is a free Carrier - -- now find the first cargo that is Unloaded - - local PickupCargo = nil - local PickupZone = nil - - --self.SetCargo:Flush() - for CargoName, Cargo in UTILS.spairs( self.SetCargo:GetSet(), function( t, a, b ) return t[a]:GetWeight() < t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then - local CargoCoordinate = Cargo:GetCoordinate() - local CoordinateFree = true - --self.PickupZoneSet:Flush() - --PickupZone = self.PickupZoneSet:GetRandomZone() - PickupZone = self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone( CargoCoordinate ) - if not self.PickupZoneSet or PickupZone then - for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do - if CarrierPickup:IsAlive() == true then - if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then - self:F( { "Coordinate not free for ", Cargo = Cargo:GetName(), Carrier:GetName(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - CoordinateFree = false + + -- The Pickup sequence ... + -- Check if this Carrier need to go and Pickup something... + -- So, if the cargo bay is not full yet with cargo to be loaded ... + self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) + if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then + -- ok, so there is a free Carrier + -- now find the first cargo that is Unloaded + + local PickupCargo = nil + local PickupZone = nil + + self.SetCargo:Flush() + for CargoName, Cargo in UTILS.spairs( self.SetCargo:GetSet(), function( t, a, b ) return t[a]:GetWeight() < t[b]:GetWeight() end ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) + if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then + local CargoCoordinate = Cargo:GetCoordinate() + local CoordinateFree = true + --self.PickupZoneSet:Flush() + --PickupZone = self.PickupZoneSet:GetRandomZone() + PickupZone = self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone( CargoCoordinate ) + if not self.PickupZoneSet or PickupZone then + for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do + if CarrierPickup:IsAlive() == true then + if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then + self:F( { "Coordinate not free for ", Cargo = Cargo:GetName(), Carrier:GetName(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) + CoordinateFree = false + break + end + else + self.PickupCargo[CarrierPickup] = nil + end + end + if CoordinateFree == true then + -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. + local LargestLoadCapacity = 0 + for _, Carrier in pairs( Carrier:GetUnits() ) do + local LoadCapacity = Carrier:GetCargoBayFreeWeight() + if LargestLoadCapacity < LoadCapacity then + LargestLoadCapacity = LoadCapacity + end + end + -- So if there is aa carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. + -- Otherwise break and go to the next carrier. + -- This will skip cargo which is too large to be able to be loaded by carriers + -- and will secure an efficient dispatching scheme. + if LargestLoadCapacity >= Cargo:GetWeight() then + self.PickupCargo[Carrier] = CargoCoordinate + PickupCargo = Cargo break end - else - self.PickupCargo[CarrierPickup] = nil - end - end - if CoordinateFree == true then - -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. - local LargestLoadCapacity = 0 - for _, Carrier in pairs( Carrier:GetUnits() ) do - local LoadCapacity = Carrier:GetCargoBayFreeWeight() - if LargestLoadCapacity < LoadCapacity then - LargestLoadCapacity = LoadCapacity - end - end - -- So if there is aa carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. - -- Otherwise break and go to the next carrier. - -- This will skip cargo which is too large to be able to be loaded by carriers - -- and will secure an efficient dispatching scheme. - if LargestLoadCapacity >= Cargo:GetWeight() then - self.PickupCargo[Carrier] = CargoCoordinate - PickupCargo = Cargo - break end end end end - end - - if PickupCargo then - self.CarrierHome[Carrier] = nil - local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) - AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), PickupZone ) - break - else - if self.HomeZone then - if not self.CarrierHome[Carrier] then - self.CarrierHome[Carrier] = true - AI_Cargo:Home( self.HomeZone:GetRandomPointVec2(), math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), self.HomeZone ) + + if PickupCargo then + self.CarrierHome[Carrier] = nil + local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) + AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), PickupZone ) + break + else + if self.HomeZone then + if not self.CarrierHome[Carrier] then + self.CarrierHome[Carrier] = true + AI_Cargo:Home( self.HomeZone:GetRandomPointVec2(), math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), self.HomeZone ) + end end end end