--- **Functional** - (R2.5) - Carrier recovery tanker. -- -- Tanker aircraft flying a racetrack pattern overhead an aircraft carrier. -- -- Features: -- -- * Regular pattern update with respect to carrier positon. -- * Automatic respawning when tanker runs out of fuel for 24/7 operations. -- * Tanker can be spawned cold or hot on the carrier or at any other airbase or directly in air. -- -- Please not that his class is work in progress and in an **alpha** stage. -- -- === -- -- ### Author: **funkyfranky** -- -- @module Ops.RecoveryTanker -- @image MOOSE.JPG --- RECOVERYTANKER class. -- @type RECOVERYTANKER -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. -- @field Wrapper.Unit#UNIT carrier The carrier the helo is attached to. -- @field #string carriertype Carrier type. -- @field #string tankergroupname Name of the late activated tanker template group. -- @field Wrapper.Group#GROUP tanker Tanker group. -- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier. -- @field Core.Radio#BEACON beacon Tanker TACAN beacon. -- @field #number TACANchannel TACAN channel. Default 1. -- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". -- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR". -- @field #boolean TACANon If true, TACAN is automatically activated. If false, TACAN is disabled. -- @field #number speed Tanker speed when flying pattern. -- @field #number altitude Tanker orbit pattern altitude. -- @field #number distStern Race-track distance astern. -- @field #number distBow Race-track distance bow. -- @field #number dTupdate Time interval for updating pattern position wrt new tanker position. -- @field #number Tupdate Last time the pattern was updated. -- @field #number takeoff Takeoff type (cold, hot, air). -- @field #number lowfuel Low fuel threshold in percent. -- @field #boolean respawn If true, tanker be respawned (default). If false, no respawning will happen. -- @field #boolean respawninair If true, tanker will always be respawned in air. This has no impact on the initial spawn setting. -- @field #boolean uncontrolledac If true, use and uncontrolled tanker group already present in the mission. -- @field DCS#Vec3 orientation Orientation of the carrier. Used to monitor changes and update the pattern if heading changes significantly. -- @field Core.Point#COORDINATE position Positon of carrier. Used to monitor if carrier significantly changed its position and then update the tanker pattern. -- @extends Core.Fsm#FSM --- Recovery Tanker. -- -- === -- -- ![Banner Image](..\Presentations\RECOVERYTANKER\RecoveryTanker_Main.jpg) -- -- # Recovery Tanker -- -- A recovery tanker acts as refueling unit flying overhead an aircraft carrier in order to supply incoming flights with gas if necessary. -- -- # Simple Script -- -- In the mission editor you have to set up a carrier unit, which will act as "mother". In the following, this unit will be named "USS Stennis". -- -- Secondly, you need to define a recovery tanker group in the mission editor and set it to "LATE ACTIVATED". The name of the group we'll use is "Texaco". -- -- The basic script is very simple and consists of only two lines. -- -- TexacoStennis=RECOVERYTANKER:New(UNIT:FindByName("USS Stennis"), "Texaco") -- TexacoStennis:Start() -- -- The first line will create a new RECOVERYTANKER object and the second line starts the process. -- -- With this setup, the tanker will be spawned on the USS Stennis with running engines. After it takes off, it will fly a position astern of the boat and from there start its -- pattern. This is a counter clockwise racetrack pattern at angels 6. -- -- ![Banner Image](..\Presentations\RECOVERYTANKER\RecoveryTanker_Pattern.jpg) -- -- The "downwind" leg of the pattern is normally used for refueling. -- -- Once the tanker runs out of fuel itself, it will return to the carrier and be respawned. -- -- # Fine Tuning -- -- Several parameters can be customized by the mission designer. -- -- ## Adjusting the Takeoff Type -- -- By default, the tanker is spawned with running engies on the carrier. The mission designer has set option to set the take off type via the @{#RECOVERYTANKER.SetTakeoff} function. -- Or via shortcuts -- -- * @{#RECOVERYTANKER.SetTakeoffHot}(): Will set the takeoff to hot, which is also the default. -- * @{#RECOVERYTANKER.SetTakeoffCold}(): Will set the takeoff type to cold, i.e. with engines off. -- * @{#RECOVERYTANKER.SetTakeoffAir}(): Will set the takeoff type to air, i.e. the tanker will be spawned in air relatively far behind the carrier. -- -- For example, -- TexacoStennis=RECOVERYTANKER:New(UNIT:FindByName("USS Stennis"), "Texaco") -- TexacoStennis:SetTakeoffAir() -- TexacoStennis:Start() -- will spawn the tanker several nautical miles astern the carrier. From there it will start its pattern. -- -- Spawning in air is not as realsitic but can be useful do avoid DCS bugs and shortcomings like aircraft crashing into each other on the flight deck. -- -- **Note** that when spawning in air is set, the tanker will also not return to the boat, once it is out of fuel. Instead it will be respawned directly in air. -- -- If only the first spawning should happen on the carrier, one use the @{#RECOVERYTANKER.SetRespawnInAir}() function to command that all subsequent spawning -- will happen in air. -- -- If the helo should no be respawned at all, one can set @{#RECOVERYTANKER.SetRespawnOff}(). -- -- ## Adjusting the Pattern -- -- The racetrack pattern parameters can be fine tuned via the following functions: -- -- * @{#RECOVERYTANKER.SetAltitude}(*altitude*), where *altitude* is the pattern altitude in feet. Default 6000 ft. -- * @{#RECOVERYTANKER.SetSpeed}(*speed*), where *speed* is the pattern speed in knots. Default is 272 knots. -- * @{#RECOVERYTANKER.SetRacetrackDistances}(*distbow*, *diststern*), where *distbow* and *diststern* are the distances ahead and astern the boat, respectively. -- -- @field #RECOVERYTANKER RECOVERYTANKER = { ClassName = "RECOVERYTANKER", Debug = true, carrier = nil, carriertype = nil, tankergroupname = nil, tanker = nil, airbase = nil, beacon = nil, TACANchannel = nil, TACANmode = nil, TACANmorse = nil, TACANon = nil, altitude = nil, speed = nil, distStern = nil, distBow = nil, dTupdate = nil, Tupdate = nil, takeoff = nil, lowfuel = nil, respawn = nil, respawninair = nil, uncontrolledac = nil, orientation = nil, position = nil, } --- Class version. -- @field #string version RECOVERYTANKER.version="0.9.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Is alive check for tanker. -- TODO: Trace functions self:T instead of self:I for less output. -- TODO: Make pattern update parameters (distance, orientation) input parameters. -- TODO: Write documenation. -- DONE: Add FSM event for pattern update. -- DONE: Smarter pattern update function. E.g. (small) zone around carrier. Only update position when carrier leaves zone or changes heading? -- DONE: Set AA TACAN. -- DONE: Add refueling event/state. -- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create new RECOVERYTANKER object. -- @param #RECOVERYTANKER self -- @param Wrapper.Unit#UNIT carrierunit Carrier unit. -- @param #string tankergroupname Name of the late activated tanker aircraft template group. -- @return #RECOVERYTANKER RECOVERYTANKER object. function RECOVERYTANKER:New(carrierunit, tankergroupname) -- Inherit everthing from FSM class. local self = BASE:Inherit(self, FSM:New()) -- #RECOVERYTANKER if type(carrierunit)=="string" then self.carrier=UNIT:FindByName(carrierunit) else self.carrier=carrierunit end -- Carrier type. self.carriertype=self.carrier:GetTypeName() -- Tanker group name. self.tankergroupname=tankergroupname -- Save self in static object. Easier to retrieve later. self.carrier:SetState(self.carrier, "RECOVERYTANKER", self) -- Init default parameters. self:SetPatternUpdateInterval() self:SetAltitude() self:SetSpeed() self:SetRacetrackDistances(6, 8) self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) self:SetTakeoffAir() self:SetLowFuelThreshold() self:SetRespawnOnOff() self:SetTACAN() ----------------------- --- FSM Transitions --- ----------------------- -- Start State. self:SetStartState("Stopped") -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start the FSM. self:AddTransition("*", "Refuel", "Refueling") -- Tanker starts to refuel. self:AddTransition("*", "Run", "Running") -- Tanker starts normal operation again. self:AddTransition("Running", "RTB", "Returning") -- Tanker is returning to base (for fuel). self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("Running", "PatternUpdate", "*") -- Update pattern wrt to carrier. self:AddTransition("*", "Stop", "Stopped") -- Stop the FSM. --- Triggers the FSM event "Start" that starts the recovery tanker. Initializes parameters and starts event handlers. -- @function [parent=#RECOVERYTANKER] Start -- @param #RECOVERYTANKER self --- Triggers the FSM event "Start" that starts the recovery tanker after a delay. Initializes parameters and starts event handlers. -- @function [parent=#RECOVERYTANKER] __Start -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Refuel" when the tanker is refueling another aircraft. -- @function [parent=#RECOVERYTANKER] Refuel -- @param #RECOVERYTANKER self -- @param Wrapper.Unit#UNIT receiver Unit receiving fuel from the tanker. --- Triggers delayed the FSM event "Refuel" when the tanker is refueling another aircraft. -- @function [parent=#RECOVERYTANKER] __Refuel -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. -- @param Wrapper.Unit#UNIT receiver Unit receiving fuel from the tanker. --- Triggers the FSM event "Run". Simply puts the group into "Running" state, e.g. after refueling ended. -- @function [parent=#RECOVERYTANKER] Run -- @param #RECOVERYTANKER self --- Triggers delayed the FSM event "Run". Simply puts the group into "Running" state, e.g. after refueling ended. -- @function [parent=#RECOVERYTANKER] __Run -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. --- Triggers the FSM event "RTB" that sends the tanker home. -- @function [parent=#RECOVERYTANKER] RTB -- @param #RECOVERYTANKER self -- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to. --- Triggers the FSM event "RTB" that sends the tanker home after a delay. -- @function [parent=#RECOVERYTANKER] __RTB -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. -- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to. --- Triggers the FSM event "Status" that updates the tanker status. -- @function [parent=#RECOVERYTANKER] Status -- @param #RECOVERYTANKER self --- Triggers the delayed FSM event "Status" that updates the tanker status. -- @function [parent=#RECOVERYTANKER] __Status -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. --- Triggers the FSM event "PatternUpdate" that updates the pattern of the tanker wrt to the carrier position. -- @function [parent=#RECOVERYTANKER] PatternUpdate -- @param #RECOVERYTANKER self --- Triggers the delayed FSM event "PatternUpdate" that updates the pattern of the tanker wrt to the carrier position. -- @function [parent=#RECOVERYTANKER] __PatternUpdate -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Stop" that stops the recovery tanker. Event handlers are stopped. -- @function [parent=#RECOVERYTANKER] Stop -- @param #RECOVERYTANKER self --- Triggers the FSM event "Stop" that stops the recovery tanker after a delay. Event handlers are stopped. -- @function [parent=#RECOVERYTANKER] __Stop -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Set the speed the tanker flys in its orbit pattern. -- @param #RECOVERYTANKER self -- @param #number speed Tanker speed in knots. Default 272 knots. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetSpeed(speed) self.speed=UTILS.KnotsToMps(speed or 272) return self end --- Set orbit pattern altitude of the tanker. -- @param #RECOVERYTANKER self -- @param #number altitude Tanker altitude in feet. Default 6000 ft. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetAltitude(altitude) self.altitude=UTILS.FeetToMeters(altitude or 6000) return self end --- Set race-track distances. -- @param #RECOVERYTANKER self -- @param #number distbow Distance [NM] in front of the carrier. Default 6 NM. -- @param #number diststern Distance [NM] behind the carrier. Default 8 NM. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetRacetrackDistances(distbow, diststern) self.distBow=UTILS.NMToMeters(distbow or 6) self.distStern=-UTILS.NMToMeters(diststern or 8) return self end --- Set pattern update interval. Note that this update causes a slight disruption in the race track pattern. -- Therefore, the interval should be as long as possible but short enough to keep the tanker overhead the carrier. -- @param #RECOVERYTANKER self -- @param #number interval Interval in minutes. Default is every 30 minutes. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetPatternUpdateInterval(interval) self.dTupdate=(interval or 30)*60 return self end --- Set low fuel state of tanker. When fuel is below this threshold, the tanker will RTB or be respawned if takeoff type is in air. -- @param #RECOVERYTANKER self -- @param #number threshold Low fuel threshold in percent. Default 10. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetLowFuelThreshold(threshold) self.lowfuel=threshold or 10 return self end --- Set home airbase of the tanker. Default is the carrier. -- @param #RECOVERYTANKER self -- @param Wrapper.Airbase#AIRBASE airbase -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetHomeBase(airbase) self.airbase=airbase return self end --- Set takeoff type. -- @param #RECOVERYTANKER self -- @param #number takeofftype Takeoff type. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTakeoff(takeofftype) self.takeoff=takeofftype return self end --- Set takeoff with engines running (hot). -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTakeoffHot() self:SetTakeoff(SPAWN.Takeoff.Hot) return self end --- Set takeoff with engines off (cold). -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTakeoffCold() self:SetTakeoff(SPAWN.Takeoff.Cold) return self end --- Set takeoff in air at pattern altitude 30 NM behind the carrier. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTakeoffAir() self:SetTakeoff(SPAWN.Takeoff.Air) return self end --- Enable respawning of tanker. Note that this is the default behaviour. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetRespawnOn() self.respawn=true return self end --- Disable respawning of tanker. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetRespawnOff() self.respawn=false return self end --- Set whether tanker shall be respawned or not. -- @param #RECOVERYTANKER self -- @param #boolean switch If true (or nil), tanker will be respawned. If false, tanker will not be respawned. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetRespawnOnOff(switch) if switch==nil or switch==true then self.respawn=true else self.respawn=false end return self end --- Tanker will be respawned in air, even it was initially spawned on the carrier. -- So only the first spawn will be on the carrier while all subsequent spawns will happen in air. -- This allows for undisrupted operations and less problems on the carrier deck. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetRespawnInAir() self.respawninair=true return self end --- Use an uncontrolled aircraft already present in the mission rather than spawning a new tanker as initial recovery thanker. -- This can be useful when interfaced with, e.g., a warehouse. -- The group name is the one specified in the @{#RECOVERYTANKER.New} function. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetUseUncontrolledAircraft() self.uncontrolledac=true return self end --- Disable automatic TACAN activation. -- @param #RECOVERYTANKER self -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTACANoff() self.TACANon=false return self end --- Set TACAN channel of tanker. -- @param #RECOVERYTANKER self -- @param #number channel TACAN channel. Default 1. -- @param #string mode TACAN mode, i.e. "X" or "Y". Default "Y". -- @param #string morse TACAN morse code identifier. Three letters. Default "TKR". -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetTACAN(channel, mode, morse) self.TACANchannel=channel or 1 self.TACANmode=mode or "Y" self.TACANmorse=morse or "TKR" self.TACANon=true return self end --- Check if tanker is currently returning to base. -- @param #RECOVERYTANKER self -- @return #boolean If true, tanker is returning to base. function RECOVERYTANKER:IsReturning() return self:is("Returning") end --- Check if tanker is currently operating. -- @param #RECOVERYTANKER self -- @return #boolean If true, tanker is operating. function RECOVERYTANKER:IsRunning() return self:is("Running") end --- Check if tanker is currently refueling another aircraft. -- @param #RECOVERYTANKER self -- @return #boolean If true, tanker is refueling. function RECOVERYTANKER:IsRefueling() return self:is("Refueling") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue. -- @param #RECOVERYTANKER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function RECOVERYTANKER:onafterStart(From, Event, To) -- Info on start. self:I(string.format("Starting Recovery Tanker v%s for carrier unit %s of type %s for tanker group %s.", RECOVERYTANKER.version, self.carrier:GetName(), self.carriertype, self.tankergroupname)) -- Handle events. self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explcit functions sice OnEventRefueling and OnEventRefuelingStop did not hook. self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop) --self:HandleEvent(EVENTS.Crash) -- Spawn tanker. local Spawn=SPAWN:New(self.tankergroupname):InitUnControlled(false) -- Spawn on carrier. if self.takeoff==SPAWN.Takeoff.Air then -- Carrier heading local hdg=self.carrier:GetHeading() -- Spawn distance behind the carrier. local dist=UTILS.NMToMeters(20) -- Coordinate behind the carrier local Carrier=self.carrier:GetCoordinate():SetAltitude(self.altitude):Translate(-dist, hdg) -- Orientation of spawned group. Spawn:InitHeading(hdg) -- Spawn at coordinate. self.tanker=Spawn:SpawnFromCoordinate(Carrier) -- Initial route. self:_InitRoute(15, 1) else -- Check if an uncontrolled tanker group was requested. if self.useuncontrolled then -- Use an uncontrolled aircraft group. self.tanker=GROUP:FindByName(self.tankergroupname) if self.tanker:IsAlive() then -- Start uncontrolled group. self.tanker:StartUncontrolled() else -- No group by that name! self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!", self.tankergroupname)) return end else -- Spawn tanker at airbase. self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) end -- Initialize route. self:_InitRoute(15, 1) end -- Create tanker beacon. if self.TACANon then self:_ActivateTACAN(2) end -- Get initial orientation and position of carrier. self.orientation=self.carrier:GetOrientationX() self.position=self.carrier:GetCoordinate() -- Init status check. self:__Status(10) end --- On after Status event. Checks player status. -- @param #RECOVERYTANKER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function RECOVERYTANKER:onafterStatus(From, Event, To) -- Get current time. local time=timer.getTime() -- Get fuel of tanker. local fuel=self.tanker:GetFuel()*100 local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) self:I(text) -- Check if tanker is running and not RTBing or refueling. if self:IsRunning() then -- Check fuel. if fuel 100 meters, this should be another tanker. if dist>100 then return end -- Info message. self:I(string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), unit:GetName())) -- FMS state "Refueling". self:Refuel(unit) end end --- Event handler for refueling stopped. -- @param #RECOVERYTANKER self -- @param Core.Event#EVENTDATA EventData Event data. function RECOVERYTANKER:_RefuelingStop(EventData) if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() then -- Unit receiving fuel. local unit=EventData.IniUnit -- Get distance to tanker to check that unit is receiving fuel from this tanker. local dist=unit:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) -- If distance > 100 meters, this should be another tanker. if dist>100 then return end -- Info message. self:I(string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), unit:GetName())) -- FSM state "Running". self:Run() end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- MISC functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Task function to -- @param #RECOVERYTANKER self function RECOVERYTANKER:_InitPatternTaskFunction() -- Name of the warehouse (static) object. local carriername=self.carrier:GetName() -- Task script. local DCSScript = {} DCSScript[#DCSScript+1] = string.format('local mycarrier = UNIT:FindByName(\"%s\") ', carriername) -- The carrier unit that holds the self object. DCSScript[#DCSScript+1] = string.format('local mytanker = mycarrier:GetState(mycarrier, \"RECOVERYTANKER\") ') -- Get the RECOVERYTANKER self object. DCSScript[#DCSScript+1] = string.format('mytanker:PatternUpdate()') -- Call the function, e.g. mytanker.(self) -- Create task. local DCSTask = CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) return DCSTask end --- Init waypoint after spawn. -- @param #RECOVERYTANKER self -- @param #number dist Distance [NM] of initial waypoint astern carrier. Default 15 NM. -- @param #number delay Delay before routing in seconds. Default 1 second. function RECOVERYTANKER:_InitRoute(dist, delay) -- Defaults. dist=UTILS.NMToMeters(dist or 15) delay=delay or 1 -- Debug message. self:I(string.format("Initializing route for recovery tanker %s.", self.tanker:GetName())) -- Carrier position. local Carrier=self.carrier:GetCoordinate() -- Carrier heading. local hdg=self.carrier:GetHeading() -- First waypoint is ~15 NM behind the boat. local p=Carrier:Translate(-dist, hdg):SetAltitude(self.altitude) -- Debug mark. if self.Debug then p:MarkToAll(string.format("Init WP: alt=%d ft, speed=%d kts", UTILS.MetersToFeet(self.altitude), UTILS.MpsToKnots(self.speed))) end -- Task to update pattern when wp 2 is reached. local task=self:_InitPatternTaskFunction() -- Waypoints. local wp={} if self.takeoff==SPAWN.Takeoff.Air then wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil, self.speed, {}, "Spawn Position") else wp[#wp+1]=Carrier:WaypointAirTakeOffParking() end wp[#wp+1]=p:WaypointAirTurningPoint(nil, self.speed, {task}, "Begin Pattern") -- Set route. self.tanker:Route(wp, delay) -- No update yet, wait until the function is called (avoids checks if pattern update is needed). self.Tupdate=nil end --- Check if heading or position have changed significantly. -- @param #RECOVERYTANKER self -- @param #number dt Time since last update in seconds. -- @return #boolean If true, heading and/or position have changed more than 10 degrees or 10 km, respectively. function RECOVERYTANKER:_CheckPatternUpdate(dt) -- Assume no update necessary. local update=false -- Get current position and orientation of carrier. local pos=self.carrier:GetCoordinate() local vC=self.carrier:GetOrientationX() -- Check if tanker is running and last updated is more than 10 minutes ago. if self:IsRunning() and dt>10*60 then -- Last saved orientation of carrier. local vP=self.orientation -- We only need the X-Z plane. vC.y=0 ; vP.y=0 -- Get angle between the two orientation vectors in rad. local rhdg=math.deg(math.acos(UTILS.VecDot(vC,vP)/UTILS.VecNorm(vC)/UTILS.VecNorm(vP))) -- Check if orientation changed. -- TODO: make 5 deg input variable. if math.abs(rhdg)>5 then self:I(string.format("Carrier heading changed by %d degrees. Updating recovery tanker pattern.", rhdg)) update=true end -- Get distance to saved position. local dist=pos:Get2DDistance(self.position) -- Check if carrier moved more than 10 km. -- TODO: make 10 km input variable. if dist/1000>10 then self:I(string.format("Carrier position changed by %.1f km. Updating recovery tanker pattern.", dist/1000)) update=true end end -- If pattern is updated then update orientation AND positon. -- But only if last update is less then 10 minutes ago. if update then self.orientation=vC self.position=pos end return update end --- Activate TACAN of tanker. -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. function RECOVERYTANKER:_ActivateTACAN(delay) if delay and delay>0 then -- Schedule TACAN activation. SCHEDULER:New(nil,self._ActivateTACAN, {self}, delay) else -- Get tanker unit. local unit=self.tanker:GetUnit(1) -- Check if unit is alive. if unit:IsAlive() then -- Debug message. self:I(string.format("Activating recovery tanker TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse)) -- Create a new beacon and activate TACAN. self.beacon=BEACON:New(unit) self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, self.TACANmorse, true) else self:E("ERROR: Recovery tanker is not alive!") end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------