AIRBOSS 0.5.4w

This commit is contained in:
funkyfranky 2018-12-14 16:44:00 +01:00
parent 5201c73d35
commit 7cf10e90f8
3 changed files with 417 additions and 167 deletions

View File

@ -2,7 +2,7 @@
-- --
-- The AIRBOSS class manages recoveries of human pilots and AI aircraft on aircraft carriers. -- The AIRBOSS class manages recoveries of human pilots and AI aircraft on aircraft carriers.
-- --
-- Main features: -- **Main Features:**
-- --
-- * CASE I, II and III recoveries. -- * CASE I, II and III recoveries.
-- * Supports human pilots as well as AI flight groups. -- * Supports human pilots as well as AI flight groups.
@ -12,33 +12,35 @@
-- * Automatic TACAN and ICLS channel setting of carrier. -- * Automatic TACAN and ICLS channel setting of carrier.
-- * Separate radio channels for LSO and Marshal transmissions. -- * Separate radio channels for LSO and Marshal transmissions.
-- * Voice over support for LSO and Marshal radio transmissions. -- * Voice over support for LSO and Marshal radio transmissions.
-- * F10 radio menu including carrier info (weather, radio frequencies, TACAN/ICLS channels), player LSO grades, help function (player aircraft attitude, marking of pattern zones etc). -- * F10 radio menu including carrier info (weather, radio frequencies, TACAN/ICLS channels), player LSO grades,
-- help function (player aircraft attitude, marking of pattern zones etc).
-- * Recovery tanker and refueling option via integration of @{#Ops.RecoveryTanker} class. -- * Recovery tanker and refueling option via integration of @{#Ops.RecoveryTanker} class.
-- * Rescue helo option via @{#Ops.RescueHelo} class. -- * Rescue helo option via @{#Ops.RescueHelo} class.
-- * Highly customizable by user API functions. -- * Many parameters customizable by convenient user API functions.
-- * Multiple carrier support due to object oriented approach. -- * Multiple carrier support due to object oriented approach.
-- * Finite State Machine (FSM) implementation. -- * Finite State Machine (FSM) implementat
--
-- **PLEASE NOTE** that his class is work in progress and in an **alpha** stage and very much **work in progress**.
-- Your constructive feedback is both necessary and highly appreciated.
-- --
-- Supported Carriers: -- Supported Carriers:
-- --
-- * USS John C. Stennis: -- * USS John C. Stennis
-- --
-- Supported Player and AI Aircraft: -- Supported Player and AI Aircraft:
-- --
-- * F/A-18C Hornet Lot 20 (player+AI) -- * F/A-18C Hornet Lot 20 (player+AI)
-- * A-4E-C community mod (player+AI) -- * A-4E-C Skyhawk Community Mod (player+AI)
-- * F/A-18C (AI) -- * F/A-18C Hornet (AI)
-- * F-14A (AI) -- * F-14A Tomcat (AI)
-- * E-2D (AI) -- * E-2D Hawkeye (AI)
-- * S-3B (AI) -- * S-3B Viking & tanker version (AI)
-- --
-- At the moment, parameters are optimized for F/A-18C Hornet as aircraft and USS John C. Stennis as carrier. -- At the moment, parameters are optimized for F/A-18C Hornet as aircraft and USS John C. Stennis as carrier.
-- The community A-4E mod is also supported in priciple but maybe needs further tweaking of parameters such as on speed AoA values. -- The community A-4E mod is also supported in priciple but maybe needs further tweaking of parameters such as on speed AoA values.
-- --
-- Other aircraft and carriers **might** be possible in future but would need a different set of optimized parameters. -- Other aircraft and carriers *might* be possible in future but would need a different set of optimized individual parameters.
-- *Winter is coming!*
--
-- **PLEASE NOTE** that his class is work in progress and in an **alpha** stage and very much **work in progress**.
-- Your constructive feedback is both necessary and highly appreciated.
-- --
-- === -- ===
-- --
@ -126,32 +128,66 @@
-- --
-- The AIRBOSS class supports all three commonly used recovery cases, i.e. -- The AIRBOSS class supports all three commonly used recovery cases, i.e.
-- --
-- * CASE I: For daytime and good weather, -- * **CASE I** during daytime and good weather,
-- * CASE II: For daytime but poor visibility conditions, -- * **CASE II** during daytime with poor visibility conditions,
-- * CASE III: For nighttime recoveries. -- * **CASE III** during nighttime recoveries.
-- --
-- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate. -- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate.
-- --
-- This is a lot of responsability. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! -- This is a lot of responsability. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong!
-- --
-- Recovery windows can be set up via the @{#AIRBOSS.AddRecoveryWindow} function as explained below. With this it is possible to seamlessly switch recovery cases even in the same mission.
--
-- ## CASE I -- ## CASE I
-- --
-- As mentioned before, Case I recovery is the standard procedure during daytime and good visibility conditions.
--
-- ### Holding Pattern -- ### Holding Pattern
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Holding.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Holding.png)
-- --
-- The graphic depicts a the standard holding pattern during a Case I recovery. Incoming aircraft enter the holding pattern, which is a counter clockwise turn with a
-- diameter of 5 NM, at their assigned altiude. The holding altitude of the first stack is 2000 ft. The inverval between stacks is 1000 ft.
--
-- Once a recovery window opens, the aircraft of the lowest stack commence their landing approach and the rest of the Marshal stack collapses, i.e. aircraft switch from
-- their current stack to the next lower stack.
--
-- The flight that transitions form the holding pattern to the landing approach, it should leave the Marshal stack at the 3 position and make a left hand turn to the *Initial*
-- position, which is 3 NM astern of the boat.
--
-- ### Landing Pattern -- ### Landing Pattern
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png)
-- --
-- Once the aircraft reaches the Inital, the landing pattern begins. The important steps of the pattern are shown in the image above.
--
-- ## CASE III -- ## CASE III
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png)
-- --
-- A Case III recovery is conducted during nighttime. The holding positon and the landing pattern are very different from a Case I recovery as can be seen in the image above.
--
-- The first holding zone starts 21 NM astern the carrier at angels 6. The interval between the stacks is 1000 ft just like in Case I. However, the distance to the boat also
-- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at one.
--
-- Once the aircraft of the lowest stack is allowed to commence to the landing pattern, it starts a descent at 4000 ft/min until it reaches the "*Platform*" at 5000 ft and
-- ~19 NM DME. From there a shallower descent at 2000 ft/min should be performed. At an altitude of 1200 ft the aircraft should level out and "*Dirty Up*" (gear & hook down).
--
-- At 3 NM distance to the carrier, the aircraft should intercept the 3.5 degrees glide slope at the "*Bullseye*". From there the pilot should "follow the needes" of the ICLS.
--
-- ## CASE II -- ## CASE II
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case2.png) -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case2.png)
-- --
-- Case II is the common recovery procedure at daytime if visibilty conditions are poor. It can be viewed as hybrid between Case I and III.
-- The holding pattern is very similar to that of the Case III recovery with the difference the the radial is the inverse of the BRC instead of the FB.
-- From the holding zone aircraft are follow the Case III path until they reach the Initial position 3 NM astern the boat. From there a standard Case I recovery procdure is
-- in place.
--
-- Note that the image depicts the case, where the holding zone has an angle offset off 30 degrees with respect to the BRC. This is optional. Commonly used offset angles
-- are 0 (no offset), +-15 degrees or +-30 degrees. The AIRBOSS class supports all these scenarios which are used during Case II and III recoveries.
--
--
-- # Scripting -- # Scripting
-- --
-- Writing a basic script is easy and can be done in two lines. -- Writing a basic script is easy and can be done in two lines.
@ -324,17 +360,69 @@
-- * **IM** In the Middle -- * **IM** In the Middle
-- * **IC** In Close -- * **IC** In Close
-- * **AR** At the Ramp -- * **AR** At the Ramp
-- * **IW** In the Wires -- * **IW** In the Wiress
-- --
-- Grading at each step includes the above calls, i.e. -- Grading at each step includes the above calls, i.e.
-- --
-- * Linup: (LUL), LUL, _LUL_, (RUL), RUL, _RUL_ -- * Linup: (LUL), LUL, _LUL_, (RUL), RUL, \_RUL\_
-- * Alitude: (H), H, _H_, (L), L, _L_ -- * Alitude: (H), H, _H_, (L), L, \_L\_
-- * Speed: (F), F, _F_, (S), S, _S_ -- * Speed: (F), F, _F_, (SLO), SLO, \_SLO\_
-- --
-- The position at the landing even is analyses and the corresponding trapped wire calculated. If no wire was caught, the LSO will give the bolter call. -- The position at the landing event is analyzed and the corresponding trapped wire calculated. If no wire was caught, the LSO will give the bolter call.
-- --
-- If a player is sigifiantly off from the ideal parameters in close or at the ramp, the LSO will wave off the player. -- If a player is sigifiantly off from the ideal parameters in close or at the ramp, the LSO will wave the player off.
--
-- ## Pattern Wave Off
--
-- The player's aircraft position is evaluated at certain critical locations in the landing pattern. If the player is far off from the ideal approach, the LSO will
-- issue a pattern wave off. Currently, this is only implemented for Case I recoveries and the Case I part in the Case II recovery, i.e.
--
-- * Break Entry
-- * Early Break
-- * Late Break
-- * Abeam
-- * Ninety
-- * Wake
-- * Groove
--
-- At these points it is also checked if a player comes too close to another aircraft ahead of him in the pattern.
--
-- # AI Handling
--
-- The implementation allows to handle incoming AI units and integrate them into the marshal and landing pattern.
--
-- By default, incoming carrier capable aircraft which are detecting inside the CCZ and approach the carrier by more than 5 NM are automatically guided to the holding zone.
-- Each AI group gets its own marshal stack in the holding pattern. Once a recovery window opens, the AI group of the lowest stack is transitioning to the landing pattern
-- and the Marshal stack collapses.
--
-- If no AI handling is desired, this can be turned off via the @{#AIRBOSS.SetHandleAIOFF} function.
--
-- ## Known Issues
--
-- The holding position of the AI is updated regularly when the carrier has changed its position by more then 2.5 NM or changed its course significantly.
-- The patterns are realized by orbit or racetrack patterns of the DCS scripting API.
-- However, when the position is updated or the marshal stack collapses, it comes to disruptions of the regular orbit becase a new waypoint with a new
-- orbit task needs to be created.
--
-- # Debugging
--
-- In case you have problems, it is always a good idea to have a look at your DCS log file. You find it in your "Saved Games" folder, so for example in
-- C:\Users\<yourname>\Saved Games\DCS\Logs\dcs.log
-- All output concerning the @{#AIRBOSS} class should have the string "AIRBOSS" in the corresponding line.
-- Searching for lines that contain the string "error" or "nil" can also give you a hint what's wrong.
--
-- The verbosity of the output can be increased by adding the following lines to your script:
--
-- BASE:TraceOnOff(true)
-- BASE:TraceLevel(1)
-- BASE:TraceClass("AIRBOSS")
--
-- To get even more output you can increase the trace level to 2 or even 3, c.f. @{Core.Base#BASE} for more details.
--
-- ## Debug Mode
--
-- You have the option to enable the debug mode for this class via the @{#AIRBOSS.SetDebugModeON} function.
-- If enabled, status and debug text messages will be displayed on the screen. Also informative marks on the F10 map are created.
-- --
-- @field #AIRBOSS -- @field #AIRBOSS
AIRBOSS = { AIRBOSS = {
@ -418,7 +506,7 @@ AIRBOSS.AircraftPlayer={
-- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string S3BTANKER Lockheed S-3B Viking tanker.
-- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string E2D Grumman E-2D Hawkeye AWACS.
-- @field #string FA18C F/A-18C Hornet (AI). -- @field #string FA18C F/A-18C Hornet (AI).
-- @field #string F14A F-14A (AI). -- @field #string F14A F-14A Tomcat (AI).
AIRBOSS.AircraftCarrier={ AIRBOSS.AircraftCarrier={
--AV8B="AV8BNA", --AV8B="AV8BNA",
HORNET="FA-18C_hornet", HORNET="FA-18C_hornet",
@ -929,7 +1017,7 @@ AIRBOSS.MenuF10={}
--- Airboss class version. --- Airboss class version.
-- @field #string version -- @field #string version
AIRBOSS.version="0.5.4" AIRBOSS.version="0.5.4w"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -1456,6 +1544,22 @@ function AIRBOSS:SetWarehouse(warehouse)
return self return self
end end
--- Activate debug mode. Display debug messages on screen.
-- @param #AIRBOSS self
-- @return #AIRBOSS self
function AIRBOSS:SetDebugModeON()
self.Debug=true
return self
end
--- Deactivate debug mode. This is also the default setting.
-- @param #AIRBOSS self
-- @return #AIRBOSS self
function AIRBOSS:SetDebugModeOFF()
self.Debug=false
return self
end
--- Check if carrier is recovering aircraft. --- Check if carrier is recovering aircraft.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @return #boolean If true, time slot for recovery is open. -- @return #boolean If true, time slot for recovery is open.
@ -1630,11 +1734,10 @@ function AIRBOSS:_CheckAIStatus()
self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.CALLTHEBALL, false, 0) self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.CALLTHEBALL, false, 0)
-- Pilot: "405, Hornet Ball, 3.2" -- Pilot: "405, Hornet Ball, 3.2"
-- TODO: Message to players only.
-- TODO: Voice over. -- TODO: Voice over.
local text=string.format("%s, %s Ball, %.1f.", element.onboard, self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) local text=string.format("%s Ball, %.1f.", self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000)
MESSAGE:New(text, 15):ToCoalition(self:GetCoalition()) self:MessageToPattern(text, element.onboard, "", 3, false, 0, true)
--self:MessageToPlayer(playerData, text, playerData.onboard, "", 3, false, 3) MESSAGE:New(text, 15):ToAll()
-- Paddles: Roger ball after 3 seconds. -- Paddles: Roger ball after 3 seconds.
self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.ROGERBALL, false, 3) self:RadioTransmission(self.LSORadio, AIRBOSS.LSOCall.ROGERBALL, false, 3)
@ -1677,7 +1780,10 @@ function AIRBOSS:_CheckPlayerPatternDistance(player)
return false return false
end end
-- TODO: return false when unit2 is not in air? Could be on the carrier. -- Return false when unit2 is not in air? Could be on the carrier.
if not unit2:InAir() then
return false
end
-- Positions of units. -- Positions of units.
local c1=unit1:GetCoordinate() local c1=unit1:GetCoordinate()
@ -1696,11 +1802,12 @@ function AIRBOSS:_CheckPlayerPatternDistance(player)
-- Get angle between the two orientation vectors. Does the player aircraft nose point into the direction of the other aircraft? (Could be behind him!) -- Get angle between the two orientation vectors. Does the player aircraft nose point into the direction of the other aircraft? (Could be behind him!)
local rhdg=math.deg(math.acos(UTILS.VecDot(vec12,vec1)/UTILS.VecNorm(vec12)/UTILS.VecNorm(vec1))) local rhdg=math.deg(math.acos(UTILS.VecDot(vec12,vec1)/UTILS.VecNorm(vec12)/UTILS.VecNorm(vec1)))
-- TODO: Check altitude difference? -- Check altitude difference?
local dalt=math.abs(c2.y-c1.y)
-- Direction in 30 degrees cone and distance < 200 meters. -- Direction in 30 degrees cone and distance < 200 meters and altitude difference <50
-- TODO: Test parameter values. -- TODO: Test parameter values.
if math.abs(rhdg)<30 and dist<200 then if math.abs(rhdg)<30 and dist<200 and dalt<50 then
return true return true
else else
return false return false
@ -1939,17 +2046,26 @@ function AIRBOSS._PassingWaypoint(group, airboss, i, final)
-- Debug message. -- Debug message.
local text=string.format("Group %s passing waypoint %d of %d.", group:GetName(), i, final) local text=string.format("Group %s passing waypoint %d of %d.", group:GetName(), i, final)
-- Debug smoke and marker.
if airboss.Debug then if airboss.Debug then
local pos=group:GetCoordinate() local pos=group:GetCoordinate()
pos:SmokeRed() pos:SmokeRed()
local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d", group:GetName(), i)) local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d", group:GetName(), i))
end end
-- Debug message.
MESSAGE:New(text,10):ToAllIf(airboss.Debug) MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text) airboss:T(airboss.lid..text)
-- Set current waypoint. -- Set current waypoint.
airboss.currentwp=i airboss.currentwp=i
-- If final waypoint reached, do route all over again.
if i==final then
-- TODO: set task to call this routine again when carrier reaches final waypoint if user chooses to.
-- SetPatrolAdInfinitum user function
airboss:_PatrolRoute()
end
end end
--- Function called when a group has reached the holding zone. --- Function called when a group has reached the holding zone.
@ -1993,7 +2109,6 @@ function AIRBOSS:_PatrolRoute()
-- NOTE: This is only necessary, if the first waypoint would already be far way, i.e. when the script is started with a large delay. -- NOTE: This is only necessary, if the first waypoint would already be far way, i.e. when the script is started with a large delay.
-- Calculate the new Route. -- Calculate the new Route.
--local wp0=CarrierGroup:GetCoordinate():WaypointGround(5.5*3.6) --local wp0=CarrierGroup:GetCoordinate():WaypointGround(5.5*3.6)
-- Insert current coordinate as first waypoint -- Insert current coordinate as first waypoint
--table.insert(Waypoints, 1, wp0) --table.insert(Waypoints, 1, wp0)
@ -2006,9 +2121,6 @@ function AIRBOSS:_PatrolRoute()
CarrierGroup:SetTaskWaypoint(Waypoints[n], TaskPassingWP) CarrierGroup:SetTaskWaypoint(Waypoints[n], TaskPassingWP)
end end
-- TODO: set task to call this routine again when carrier reaches final waypoint if user chooses to.
-- SetPatrolAdInfinitum user function
-- Set waypoint table. -- Set waypoint table.
local i=1 local i=1
for _,point in ipairs(Waypoints) do for _,point in ipairs(Waypoints) do
@ -7129,7 +7241,6 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration
else else
-- Send onboard number so that player is alerted about the text message. -- Send onboard number so that player is alerted about the text message.
-- DONE: This will fail with message to all since for each player the message will be played!
if receiver==playerData.onboard and not soundoff then if receiver==playerData.onboard and not soundoff then
if sender then if sender then
if sender=="LSO" then if sender=="LSO" then
@ -7163,36 +7274,88 @@ end
-- @param #boolean soundoff If true, do not play boad number message. -- @param #boolean soundoff If true, do not play boad number message.
function AIRBOSS:MessageToAll(message, sender, receiver, duration, clear, delay, soundoff) function AIRBOSS:MessageToAll(message, sender, receiver, duration, clear, delay, soundoff)
local playit=true -- In case two have the same flight number. -- Make sure the onboard number sound is played only once.
local soundoff=false
for _,_player in pairs(self.players) do for _,_player in pairs(self.players) do
local playerData=_player --#AIRBOSS.PlayerData local playerData=_player --#AIRBOSS.PlayerData
-- Message to all players in CCA. -- Message to all players in CCA.
-- TODO: could make something to all in pattern or all in marshal queue depending on sender.
if playerData.unit:IsInZone(self.zoneCCA) then if playerData.unit:IsInZone(self.zoneCCA) then
-- Play receiver board number. Best we can do if no voice over for the whole message is there. -- Message to player.
if receiver==playerData.onboard and sender and playit and not soundoff then self:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay, soundoff)
-- Check who is the sender.
if sender=="LSO" then -- Disable sound play of onboard number.
-- Sender is LSO or AIRBOSS ==> Broadcast on LSO radio. soundoff=true
self:_Number2Sound(self.LSORadio, receiver, delay)
elseif sender=="MARSHAL" then
-- Sender is MARSHAL ==> Broadcast on MARSHAL radio.
self:_Number2Sound(self.MarshalRadio, receiver, delay)
end end
playit=false -- Play only once, in case two have the same flight number.
end end
end
--- Send text message to all players in the pattern queue.
-- Message format will be "SENDER: RECCEIVER, MESSAGE".
-- @param #AIRBOSS self
-- @param #string message The message to send.
-- @param #string sender The person who sends the message or nil.
-- @param #string receiver The person who receives the message. Default player's onboard number. Set to "" for no receiver.
-- @param #number duration Display message duration. Default 10 seconds.
-- @param #boolean clear If true, clear screen from previous messages.
-- @param #number delay Delay in seconds, before the message is displayed.
-- @param #boolean soundoff If true, do not play boad number message.
function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, delay, soundoff)
-- Make sure the onboard number sound is played only once.
local soundoff=false
-- Loop over all flights in the pattern queue.
for _,_player in pairs(self.Qpattern) do
local playerData=_player --#AIRBOSS.PlayerData
-- Message only to human pilots.
if not playerData.ai then
-- Message to player. -- Message to player.
self:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay, true) self:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay, soundoff)
-- Disable sound play of onboard number.
soundoff=true
end end
end end
end end
--- Send text message to all players in the marshal queue.
-- Message format will be "SENDER: RECCEIVER, MESSAGE".
-- @param #AIRBOSS self
-- @param #string message The message to send.
-- @param #string sender The person who sends the message or nil.
-- @param #string receiver The person who receives the message. Default player's onboard number. Set to "" for no receiver.
-- @param #number duration Display message duration. Default 10 seconds.
-- @param #boolean clear If true, clear screen from previous messages.
-- @param #number delay Delay in seconds, before the message is displayed.
-- @param #boolean soundoff If true, do not play boad number message.
function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, delay, soundoff)
-- Make sure the onboard number sound is played only once.
local soundoff=false
-- Loop over all flights in the marshal queue.
for _,_player in pairs(self.Qmarshal) do
local playerData=_player --#AIRBOSS.PlayerData
-- Message only to human pilots.
if not playerData.ai then
-- Message to player.
self:MessageToPlayer(playerData, message, sender, receiver, duration, clear, delay, soundoff)
-- Disable sound play of onboard number.
soundoff=true
end
end
end
--- Convert a number (as string) into a radio message. --- Convert a number (as string) into a radio message.
-- E.g. for board number or headings. -- E.g. for board number or headings.
-- @param #AIRBOSS self -- @param #AIRBOSS self
@ -7280,7 +7443,10 @@ function AIRBOSS:_AddF10Commands(_unitName)
local group=_unit:GetGroup() local group=_unit:GetGroup()
local gid=group:GetID() local gid=group:GetID()
if group and gid then -- Player Data.
local playerData=self.players[playername]
if group and gid and playerData then
if not self.menuadded[gid] then if not self.menuadded[gid] then
@ -7292,9 +7458,6 @@ function AIRBOSS:_AddF10Commands(_unitName)
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss") AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss")
end end
-- Player Data.
local playerData=self.players[playername]
-- F10/Airboss/<Carrier> -- F10/Airboss/<Carrier>
local _rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10[gid]) local _rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10[gid])
@ -7311,19 +7474,19 @@ function AIRBOSS:_AddF10Commands(_unitName)
-- F10/Airboss/<Carrier>/F1 Help/F2 Mark Zones -- F10/Airboss/<Carrier>/F1 Help/F2 Mark Zones
local _markPath=missionCommands.addSubMenuForGroup(gid, "Mark Zones", _helpPath) local _markPath=missionCommands.addSubMenuForGroup(gid, "Mark Zones", _helpPath)
-- F10/Airboss/<Carrier>/F1 Help/F3 Mark Zones/ -- F10/Airboss/<Carrier>/F1 Help/F3 Mark Zones/
missionCommands.addCommandForGroup(gid, "Smoke My Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, false) -- F1 missionCommands.addCommandForGroup(gid, "Smoke Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, false) -- F1
missionCommands.addCommandForGroup(gid, "Flare My Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, true) -- F2 missionCommands.addCommandForGroup(gid, "Flare Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, true) -- F2
missionCommands.addCommandForGroup(gid, "Smoke Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, false) -- F3 missionCommands.addCommandForGroup(gid, "Smoke My Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, false) -- F3
missionCommands.addCommandForGroup(gid, "Flare Pattern Zones", _markPath, self._MarkCaseZones, self, _unitName, true) -- F4 missionCommands.addCommandForGroup(gid, "Flare My Marshal Zone", _markPath, self._MarkMarshalZone, self, _unitName, true) -- F4
-- F10/Airboss/<Carrier>/F1 Help/ -- F10/Airboss/<Carrier>/F1 Help/
missionCommands.addCommandForGroup(gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName) -- F4 missionCommands.addCommandForGroup(gid, "My Status", _helpPath, self._DisplayPlayerStatus, self, _unitName) -- F4
missionCommands.addCommandForGroup(gid, "Attitude Monitor ON/OFF", _helpPath, self._AttitudeMonitor, self, playername) -- F5 missionCommands.addCommandForGroup(gid, "Attitude Monitor", _helpPath, self._AttitudeMonitor, self, playername) -- F5
missionCommands.addCommandForGroup(gid, "Radio Check LSO", _helpPath, self._LSORadioCheck, self, _unitName) -- F6 missionCommands.addCommandForGroup(gid, "Radio Check LSO", _helpPath, self._LSORadioCheck, self, _unitName) -- F6
missionCommands.addCommandForGroup(gid, "Radio Check Marshal", _helpPath, self._MarshalRadioCheck, self, _unitName) -- F7 missionCommands.addCommandForGroup(gid, "Radio Check Marshal", _helpPath, self._MarshalRadioCheck, self, _unitName) -- F7
missionCommands.addCommandForGroup(gid, "[Reset My Status]", _helpPath, self._ResetPlayerStatus, self, _unitName) -- F8 missionCommands.addCommandForGroup(gid, "[Reset My Status]", _helpPath, self._ResetPlayerStatus, self, _unitName) -- F8
------------------------------------- -------------------------------------
-- F10/Airboss/<Carrier>/F2 Kneeboard -- -- F10/Airboss/<Carrier>/F2 Kneeboard
------------------------------------- -------------------------------------
local _kneeboardPath=missionCommands.addSubMenuForGroup(gid, "Kneeboard", _rootPath) local _kneeboardPath=missionCommands.addSubMenuForGroup(gid, "Kneeboard", _rootPath)
-- F10/Airboss/<Carrier>/F2 Kneeboard/F1 Results -- F10/Airboss/<Carrier>/F2 Kneeboard/F1 Results
@ -7337,9 +7500,9 @@ function AIRBOSS:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(gid, "Weather Report", _kneeboardPath, self._DisplayCarrierWeather, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "Weather Report", _kneeboardPath, self._DisplayCarrierWeather, self, _unitName) -- F3
missionCommands.addCommandForGroup(gid, "Set Section", _kneeboardPath, self._SetSection, self, _unitName) -- F4 missionCommands.addCommandForGroup(gid, "Set Section", _kneeboardPath, self._SetSection, self, _unitName) -- F4
---------------------------- -------------------------
-- F10/Airboss/<Carrier>/ -- -- F10/Airboss/<Carrier>/
---------------------------- -------------------------
missionCommands.addCommandForGroup(gid, "Request Marshal", _rootPath, self._RequestMarshal, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "Request Marshal", _rootPath, self._RequestMarshal, self, _unitName) -- F3
missionCommands.addCommandForGroup(gid, "Request Commence", _rootPath, self._RequestCommence, self, _unitName) -- F4 missionCommands.addCommandForGroup(gid, "Request Commence", _rootPath, self._RequestCommence, self, _unitName) -- F4
missionCommands.addCommandForGroup(gid, "Request Refueling", _rootPath, self._RequestRefueling, self, _unitName) -- F5 missionCommands.addCommandForGroup(gid, "Request Refueling", _rootPath, self._RequestRefueling, self, _unitName) -- F5

View File

@ -2,15 +2,15 @@
-- --
-- Tanker aircraft flying a racetrack pattern overhead an aircraft carrier. -- Tanker aircraft flying a racetrack pattern overhead an aircraft carrier.
-- --
-- Features: -- **Main Features:**
-- --
-- * Regular pattern update with respect to carrier positon. -- * Regular pattern update with respect to carrier positon.
-- * Automatic respawning when tanker runs out of fuel for 24/7 operations. -- * 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. -- * Tanker can be spawned cold or hot on the carrier or at any other airbase or directly in air.
-- * Automatic AA TACAN beacon setting. -- * Automatic AA TACAN beacon setting.
-- * Multiple tanker at different carriers due to object oriented approach.
-- * Finite State Machine (FSM) implementation, which allows the mission designer to hook into certain events. -- * Finite State Machine (FSM) implementation, which allows the mission designer to hook into certain events.
-- --
--
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
@ -23,7 +23,8 @@
-- @type RECOVERYTANKER -- @type RECOVERYTANKER
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. -- @field #boolean Debug Debug mode.
-- @field Wrapper.Unit#UNIT carrier The carrier the helo is attached to. -- @field #string lid Log debug id text.
-- @field Wrapper.Unit#UNIT carrier The carrier the tanker is attached to.
-- @field #string carriertype Carrier type. -- @field #string carriertype Carrier type.
-- @field #string tankergroupname Name of the late activated tanker template group. -- @field #string tankergroupname Name of the late activated tanker template group.
-- @field Wrapper.Group#GROUP tanker Tanker group. -- @field Wrapper.Group#GROUP tanker Tanker group.
@ -60,7 +61,7 @@
-- --
-- # Recovery Tanker -- # Recovery Tanker
-- --
-- A recovery tanker acts as refueling unit flying overhead an aircraft carrier in order to supply incoming flights with gas if necessary. -- A recovery tanker acts as refueling unit flying overhead an aircraft carrier in order to supply incoming flights with gas if they go "Bingo on the Ball".
-- --
-- # Simple Script -- # Simple Script
-- --
@ -110,7 +111,7 @@
-- If only the first spawning should happen on the carrier, one use the @{#RECOVERYTANKER.SetRespawnInAir}() function to command that all subsequent spawning -- 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. -- will happen in air.
-- --
-- If the helo should not be respawned at all, one can set @{#RECOVERYTANKER.SetRespawnOff}(). -- If the tanker should not be respawned at all, one can set @{#RECOVERYTANKER.SetRespawnOff}().
-- --
-- ## Pattern Parameters -- ## Pattern Parameters
-- --
@ -136,7 +137,6 @@
-- --
-- In order to completely disable the TACAN beacon, you can use the @{#RECOVERYTANKER.SetTACANoff}() function in your script. -- In order to completely disable the TACAN beacon, you can use the @{#RECOVERYTANKER.SetTACANoff}() function in your script.
-- --
--
-- ## Pattern Update -- ## Pattern Update
-- --
-- The pattern of the tanker is updated if at least one of the two following conditions apply: -- The pattern of the tanker is updated if at least one of the two following conditions apply:
@ -150,9 +150,9 @@
-- The maximum update frequency is set to 10 minutes. You can adjust this by @{#RECOVERYTANKER.SetPatternUpdateInterval}. -- The maximum update frequency is set to 10 minutes. You can adjust this by @{#RECOVERYTANKER.SetPatternUpdateInterval}.
-- Also the pattern will not be updated while the carrier is turning or the tanker is currently refuelling another unit. -- Also the pattern will not be updated while the carrier is turning or the tanker is currently refuelling another unit.
-- --
-- # Finite State Model -- # Finite State Machine
-- --
-- The implementation uses a Finite State Model (FSM). This allows the mission designer to hook in to certain events. -- The implementation uses a Finite State Machine (FSM). This allows the mission designer to hook in to certain events.
-- --
-- * @{#RECOVERYTANKER.Start}: This event starts the FMS process and initialized parameters and spawns the tanker. DCS event handling is started. -- * @{#RECOVERYTANKER.Start}: This event starts the FMS process and initialized parameters and spawns the tanker. DCS event handling is started.
-- * @{#RECOVERYTANKER.Status}: This event is called in regular intervals (~60 seconds) and checks the status of the tanker and carrier. It triggers other events if necessary. -- * @{#RECOVERYTANKER.Status}: This event is called in regular intervals (~60 seconds) and checks the status of the tanker and carrier. It triggers other events if necessary.
@ -180,10 +180,16 @@
-- --
-- To get even more output you can increase the trace level to 2 or even 3, c.f. @{Core.Base#BASE} for more details. -- To get even more output you can increase the trace level to 2 or even 3, c.f. @{Core.Base#BASE} for more details.
-- --
-- ## Debug Mode
--
-- You have the option to enable the debug mode for this class via the @{#RECOVERYTANKER.SetDebugModeON} function.
-- If enabled, text messages about the tanker status will be displayed on screen and marks of the pattern created on the F10 map.
--
-- @field #RECOVERYTANKER -- @field #RECOVERYTANKER
RECOVERYTANKER = { RECOVERYTANKER = {
ClassName = "RECOVERYTANKER", ClassName = "RECOVERYTANKER",
Debug = false, Debug = false,
lid = nil,
carrier = nil, carrier = nil,
carriertype = nil, carriertype = nil,
tankergroupname = nil, tankergroupname = nil,
@ -263,6 +269,9 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
-- Save self in static object. Easier to retrieve later. -- Save self in static object. Easier to retrieve later.
self.carrier:SetState(self.carrier, "RECOVERYTANKER", self) self.carrier:SetState(self.carrier, "RECOVERYTANKER", self)
-- Debug log id.
self.lid=string.format("RECOVERYTANKER %s", self.carrier:GetName())
-- Init default parameters. -- Init default parameters.
self:SetAltitude() self:SetAltitude()
self:SetSpeed() self:SetSpeed()
@ -359,7 +368,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to. -- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to.
--- On after "RTB" event user function. Called when a the the tanker returns to its home base. --- On after "RTB" event user function. Called when a the the tanker returns to its home base.
-- @function [parent=#RECOVERYTANKER] OnAfterPatternUpdate -- @function [parent=#RECOVERYTANKER] OnAfterRTB
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
@ -731,7 +740,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
-- Get fuel of tanker. -- Get fuel of tanker.
local fuel=self.tanker:GetFuel()*100 local fuel=self.tanker:GetFuel()*100
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel)
self:T(text) self:T(self.lid..text)
-- Check if tanker flies through pattern update zone. -- Check if tanker flies through pattern update zone.
-- TODO: Check if this can be used to update the pattern without too much disruption. -- TODO: Check if this can be used to update the pattern without too much disruption.
@ -760,7 +769,8 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
-- Debug message. -- Debug message.
local text=string.format("Respawning recovery tanker %s in air.", self.tanker:GetName()) local text=string.format("Respawning recovery tanker %s in air.", self.tanker:GetName())
self:T(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Respawn tanker. -- Respawn tanker.
self.tanker:InitHeading(self.tanker:GetHeading()) self.tanker:InitHeading(self.tanker:GetHeading())
@ -816,7 +826,9 @@ end
function RECOVERYTANKER:onafterPatternUpdate(From, Event, To) function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
-- Debug message. -- Debug message.
self:T(string.format("Updating recovery tanker %s racetrack pattern.", self.tanker:GetName())) local text=string.format("Updating recovery tanker %s racetrack pattern.", self.tanker:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Carrier heading. -- Carrier heading.
local hdg=self.carrier:GetHeading() local hdg=self.carrier:GetHeading()
@ -865,12 +877,11 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
end end
--- Self made race track pattern. --- Self made race track pattern. (not used)
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @return #table Table of pattern waypoints. -- @return #table Table of pattern waypoints.
function RECOVERYTANKER:_Pattern() function RECOVERYTANKER:_Pattern()
-- Carrier heading. -- Carrier heading.
local hdg=self.carrier:GetHeading() local hdg=self.carrier:GetHeading()
@ -919,7 +930,8 @@ function RECOVERYTANKER:onafterRTB(From, Event, To, airbase)
-- Debug message. -- Debug message.
local text=string.format("Recoery tanker %s returning to airbase %s.", self.tanker:GetName(), airbase:GetName()) local text=string.format("Recoery tanker %s returning to airbase %s.", self.tanker:GetName(), airbase:GetName())
self:T(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Waypoint array. -- Waypoint array.
local wp={} local wp={}
@ -968,7 +980,10 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
if groupname:match(self.tankergroupname) then if groupname:match(self.tankergroupname) then
-- Debug info. -- Debug info.
self:T(string.format("Respawning recovery tanker group %s.", group:GetName())) local text=string.format("Respawning recovery tanker group %s.", group:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Respawn tanker. -- Respawn tanker.
self.tanker=group:RespawnAtCurrentAirbase() self.tanker=group:RespawnAtCurrentAirbase()
@ -1004,7 +1019,9 @@ function RECOVERYTANKER:_RefuelingStart(EventData)
end end
-- Info message. -- Info message.
self:T(string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), receiver:GetName())) local text=string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), receiver:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- FMS state "Refueling". -- FMS state "Refueling".
self:RefuelStart(receiver) self:RefuelStart(receiver)
@ -1032,7 +1049,9 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
end end
-- Info message. -- Info message.
self:T(string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), receiver:GetName())) local text=string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), receiver:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- FSM state "Running". -- FSM state "Running".
self:RefuelStop(receiver) self:RefuelStop(receiver)
@ -1076,7 +1095,7 @@ function RECOVERYTANKER:_InitRoute(dist, delay)
delay=delay or 1 delay=delay or 1
-- Debug message. -- Debug message.
self:T(string.format("Initializing route for recovery tanker %s.", self.tanker:GetName())) self:T(self.lid..string.format("Initializing route of recovery tanker %s.", self.tanker:GetName()))
-- Carrier position. -- Carrier position.
local Carrier=self.carrier:GetCoordinate() local Carrier=self.carrier:GetCoordinate()
@ -1149,13 +1168,13 @@ function RECOVERYTANKER:_CheckPatternUpdate(dt)
-- Debug output if turning -- Debug output if turning
if turning then if turning then
self:T2(string.format("Carrier is turning. Delta Heading = %.1f", deltaLast)) self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f", deltaLast))
end end
-- Check if orientation changed. -- Check if orientation changed.
local Hchange=false local Hchange=false
if math.abs(deltaHeading)>=self.Hupdate then if math.abs(deltaHeading)>=self.Hupdate then
self:T(string.format("Carrier heading changed by %d degrees. Turning=%s.", deltaHeading, tostring(turning))) self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.", deltaHeading, tostring(turning)))
Hchange=true Hchange=true
end end
@ -1165,7 +1184,7 @@ function RECOVERYTANKER:_CheckPatternUpdate(dt)
-- Check if carrier moved more than ~10 km. -- Check if carrier moved more than ~10 km.
local Dchange=false local Dchange=false
if dist>self.Dupdate then if dist>self.Dupdate then
self:T(string.format("Carrier position changed by %.1f NM. Turning=%s.", UTILS.MetersToNM(dist), tostring(turning))) self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.", UTILS.MetersToNM(dist), tostring(turning)))
Dchange=true Dchange=true
end end
@ -1177,6 +1196,12 @@ function RECOVERYTANKER:_CheckPatternUpdate(dt)
-- Update if heading or distance changed. -- Update if heading or distance changed.
if Hchange or Dchange then if Hchange or Dchange then
-- Debug message.
local text=string.format("Updating tanker %s pattern due to carrier change.", self.tanker:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Update pos and orientation.
self.orientation=vNew self.orientation=vNew
self.position=pos self.position=pos
update=true update=true
@ -1206,7 +1231,9 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if unit:IsAlive() then if unit:IsAlive() then
-- Debug message. -- Debug message.
self:T(string.format("Activating recovery tanker TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse)) local text=string.format("Activating recovery tanker TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Create a new beacon and activate TACAN. -- Create a new beacon and activate TACAN.
self.beacon=BEACON:New(unit) self.beacon=BEACON:New(unit)
@ -1220,52 +1247,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
end end
--- Calculate distances between carrier and tanker.
-- @param #RECOVERYTANKER self
-- @return #number Distance [m] in the direction of the orientation of the carrier.
-- @return #number Distance [m] perpendicular to the orientation of the carrier.
-- @return #number Distance [m] to the carrier.
-- @return #number Angle [Deg] from carrier to plane. Phi=0 if the plane is directly behind the carrier, phi=90 if the plane is starboard, phi=180 if the plane is in front of the carrier.
function RECOVERYTANKER:_GetDistances()
-- Vector to carrier
local a=self.carrier:GetVec3()
-- Vector to player
local b=self.tanker: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)
-- Polar coordinates
local rho=math.sqrt(dx*dx+dz*dz)
local phi=math.deg(math.atan2(dz,dx))
if phi<0 then
phi=phi+360
end
-- phi=0 if the plane is directly behind the carrier, phi=180 if the plane is in front of the carrier
phi=phi-180
if phi<0 then
phi=phi+360
end
return dx,dz,rho,phi
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -2,12 +2,14 @@
-- --
-- Recue helicopter for carrier operations. -- Recue helicopter for carrier operations.
-- --
-- Features: -- **Main Features:**
-- --
-- * Close formation with carrier. -- * Close formation with carrier.
-- * Carrier can have any number of waypoints. -- * Carrier can have any number of waypoints.
-- * Automatic respawning on empty fuel for 24/7 operations. -- * Automatic respawning on empty fuel for 24/7 operations.
-- * Automatic rescuing of crashed or ejected units in the vicinity. -- * Automatic rescuing of crashed or ejected units in the vicinity.
-- * Multiple helos at different carriers due to object oriented approach.
-- * Finite State Machine (FSM) implementation.
-- --
-- === -- ===
-- --
@ -20,6 +22,7 @@
-- @type RESCUEHELO -- @type RESCUEHELO
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode on/off. -- @field #boolean Debug Debug mode on/off.
-- @field #string lid Log debug id text.
-- @field Wrapper.Unit#UNIT carrier The carrier the helo is attached to. -- @field Wrapper.Unit#UNIT carrier The carrier the helo is attached to.
-- @field #string carriertype Carrier type. -- @field #string carriertype Carrier type.
-- @field #string helogroupname Name of the late activated helo template group. -- @field #string helogroupname Name of the late activated helo template group.
@ -53,7 +56,7 @@
-- # Recue Helo -- # Recue Helo
-- --
-- The rescue helo will fly in close formation with another unit, which is typically an aircraft carrier. -- The rescue helo will fly in close formation with another unit, which is typically an aircraft carrier.
-- It's mission is to rescue crashed units or ejected pilots. Well, and to look cool... -- It's mission is to rescue crashed or ejected pilots. Well, and to look cool...
-- --
-- # Simple Script -- # Simple Script
-- --
@ -120,7 +123,6 @@
-- --
-- Once the helo runs out of fuel, it will return to the USS Normandy and not the Stennis for respawning. -- Once the helo runs out of fuel, it will return to the USS Normandy and not the Stennis for respawning.
-- --
--
-- ## Formation Positon -- ## Formation Positon
-- --
-- The position of the helo relative to the mother ship can be tuned via the functions -- The position of the helo relative to the mother ship can be tuned via the functions
@ -129,11 +131,44 @@
-- * @{#RESCUEHELO.SetOffsetX}(*distance*)}, where *distance is the distance in the direction of movement of the carrier. Default is 200 meters. -- * @{#RESCUEHELO.SetOffsetX}(*distance*)}, where *distance is the distance in the direction of movement of the carrier. Default is 200 meters.
-- * @{#RESCUEHELO.SetOffsetZ}(*distance*)}, where *distance is the distance on the starboard side. Default is 200 meters. -- * @{#RESCUEHELO.SetOffsetZ}(*distance*)}, where *distance is the distance on the starboard side. Default is 200 meters.
-- --
-- # Finite State Machine
--
-- The implementation uses a Finite State Machine (FSM). This allows the mission designer to hook in to certain events.
--
-- * @{#RESCUEHELO.Start}: This eventfunction starts the FMS process and initialized parameters and spawns the helo. DCS event handling is started.
-- * @{#RESCUEHELO.Status}: This eventfunction is called in regular intervals (~60 seconds) and checks the status of the helo and carrier. It triggers other events if necessary.
-- * @{#RESCUEHELO.Rescue}: This eventfunction commands the helo to go on a rescue operation at a certain coordinate.
-- * @{#RESCUEHELO.RTB}: This eventsfunction sends the helo to its home base (usually the carrier). This is called once the helo runs low on gas.
-- * @{#RESCUEHELO.Run}: This eventfunction is called when the helo resumes normal operations and goes back on station.
-- * @{#RESCUEHELO.Stop}: This eventfunction stops the FSM by unhandling DCS events.
--
-- The mission designer can capture these events by RESCUEHELO.OnAfter*Eventname* functions, e.g. @{#RESCUEHELO.OnAfterRescue}.
--
-- # Debugging
--
-- In case you have problems, it is always a good idea to have a look at your DCS log file. You find it in your "Saved Games" folder, so for example in
-- C:\Users\<yourname>\Saved Games\DCS\Logs\dcs.log
-- All output concerning the @{#RESCUEHELO} class should have the string "RESCUEHELO" in the corresponding line.
-- Searching for lines that contain the string "error" or "nil" can also give you a hint what's wrong.
--
-- The verbosity of the output can be increased by adding the following lines to your script:
--
-- BASE:TraceOnOff(true)
-- BASE:TraceLevel(1)
-- BASE:TraceClass("RESCUEHELO")
--
-- To get even more output you can increase the trace level to 2 or even 3, c.f. @{Core.Base#BASE} for more details.
--
-- ## Debug Mode
--
-- You have the option to enable the debug mode for this class via the @{#RESCUEHELO.SetDebugModeON} function.
-- If enabled, text messages about the helo status will be displayed on screen and marks of the pattern created on the F10 map.
-- --
-- @field #RESCUEHELO -- @field #RESCUEHELO
RESCUEHELO = { RESCUEHELO = {
ClassName = "RESCUEHELO", ClassName = "RESCUEHELO",
Debug = false, Debug = false,
lid = nil,
carrier = nil, carrier = nil,
carriertype = nil, carriertype = nil,
helogroupname = nil, helogroupname = nil,
@ -160,14 +195,14 @@ RESCUEHELO = {
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RESCUEHELO.version="0.9.5" RESCUEHELO.version="0.9.6"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Write documenation.
-- TODO: Add option to stop carrier while rescue operation is in progress? Done but NOT working! -- TODO: Add option to stop carrier while rescue operation is in progress? Done but NOT working!
-- DONE: Write documenation.
-- DONE: Add option to deactivate the rescueing. -- DONE: Add option to deactivate the rescueing.
-- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse. -- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse.
-- DONE: Add rescue event when aircraft crashes. -- DONE: Add rescue event when aircraft crashes.
@ -200,6 +235,9 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- Helo group name. -- Helo group name.
self.helogroupname=helogroupname self.helogroupname=helogroupname
-- Log ID.
self.lid=string.format("RESCUEHELO %s |", self.carrier:GetName())
-- Init defaults. -- Init defaults.
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
self:SetTakeoffHot() self:SetTakeoffHot()
@ -239,6 +277,7 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "Rescue" that sends the helo on a rescue mission to a specifc coordinate. --- Triggers the FSM event "Rescue" that sends the helo on a rescue mission to a specifc coordinate.
-- @function [parent=#RESCUEHELO] Rescue -- @function [parent=#RESCUEHELO] Rescue
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@ -250,6 +289,15 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
-- @param Core.Point#COORDINATE RescueCoord Coordinate where the resue mission takes place. -- @param Core.Point#COORDINATE RescueCoord Coordinate where the resue mission takes place.
--- On after "Rescue" event user function. Called when a the the helo goes on a rescue mission.
-- @function [parent=#RESCUEHELO] OnAfterRescue
-- @param #RESCUEHELO self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE RescueCoord Crash site where the rescue operation takes place.
--- Triggers the FSM event "RTB" that sends the helo home. --- Triggers the FSM event "RTB" that sends the helo home.
-- @function [parent=#RESCUEHELO] RTB -- @function [parent=#RESCUEHELO] RTB
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@ -259,6 +307,14 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- On after "RTB" event user function. Called when a the the helo returns to its home base.
-- @function [parent=#RESCUEHELO] OnAfterRTB
-- @param #RESCUEHELO self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Triggers the FSM event "Run". --- Triggers the FSM event "Run".
-- @function [parent=#RESCUEHELO] Run -- @function [parent=#RESCUEHELO] Run
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@ -268,6 +324,17 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status" that updates the helo status.
-- @function [parent=#RESCUEHELO] Status
-- @param #RESCUEHELO self
--- Triggers the delayed FSM event "Status" that updates the helo status.
-- @function [parent=#RESCUEHELO] __Status
-- @param #RESCUEHELO self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop" that stops the rescue helo. Event handlers are stopped. --- Triggers the FSM event "Stop" that stops the rescue helo. Event handlers are stopped.
-- @function [parent=#RESCUEHELO] Stop -- @function [parent=#RESCUEHELO] Stop
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@ -472,6 +539,21 @@ function RESCUEHELO:SetUseUncontrolledAircraft()
return self return self
end end
--- Activate debug mode. Display debug messages on screen.
-- @param #RESCUEHELO self
-- @return #RESCUEHELO self
function RESCUEHELO:SetDebugModeON()
self.Debug=true
return self
end
--- Deactivate debug mode. This is also the default setting.
-- @param #RESCUEHELO self
-- @return #RESCUEHELO self
function RESCUEHELO:SetDebugModeOFF()
self.Debug=false
return self
end
--- Check if helo is returning to base. --- Check if helo is returning to base.
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@ -510,7 +592,9 @@ function RESCUEHELO:OnEventLand(EventData)
if groupname:match(self.helogroupname) then if groupname:match(self.helogroupname) then
-- Respawn the Helo. -- Respawn the Helo.
self:I(string.format("Respawning rescue helo group %s at home base.", groupname)) local text=string.format("Respawning rescue helo group %s at home base.", groupname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
@ -548,7 +632,9 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
if EventData.IniGroupName~=self.helo:GetName() then if EventData.IniGroupName~=self.helo:GetName() then
-- Debug. -- Debug.
self:T(string.format("Unit %s crashed or ejected.", unitname)) local text=string.format("Unit %s crashed or ejected.", unitname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Unit "alive" and in our rescue zone. -- Unit "alive" and in our rescue zone.
if unit:IsAlive() and unit:IsInZone(self.rescuezone) then if unit:IsAlive() and unit:IsInZone(self.rescuezone) then
@ -557,7 +643,9 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
local coord=unit:GetCoordinate() local coord=unit:GetCoordinate()
-- Debug mark on map. -- Debug mark on map.
coord:MarkToCoalition(string.format("Crash site of unit %s.", unitname), self.helo:GetCoalition()) if self.Debug then
coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.", unitname), self.helo:GetCoalition())
end
-- Only rescue if helo is "running" and not, e.g., rescuing already. -- Only rescue if helo is "running" and not, e.g., rescuing already.
if self:IsRunning() and self.rescueon then if self:IsRunning() and self.rescueon then
@ -568,7 +656,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
else else
self:I(string.format("Rescue helo %s crashed!", unitname)) self:E(self.lid..string.format("Rescue helo %s crashed!", unitname))
end end
@ -588,7 +676,8 @@ end
function RESCUEHELO:onafterStart(From, Event, To) function RESCUEHELO:onafterStart(From, Event, To)
-- Events are handled my MOOSE. -- Events are handled my MOOSE.
self:I(string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.", RESCUEHELO.version, self.carrier:GetName(), self.carriertype)) local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.", RESCUEHELO.version, self.carrier:GetName(), self.carriertype)
self:I(self.lid..text)
-- Handle events. -- Handle events.
--self:HandleEvent(EVENTS.Birth) --self:HandleEvent(EVENTS.Birth)
@ -699,7 +788,8 @@ function RESCUEHELO:onafterStatus(From, Event, To)
-- Report current fuel. -- Report current fuel.
local text=string.format("Rescue Helo %s: state=%s fuel=%.1f", self.helo:GetName(), self:GetState(), fuel) local text=string.format("Rescue Helo %s: state=%s fuel=%.1f", self.helo:GetName(), self:GetState(), fuel)
self:T(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- If fuel < threshold ==> send helo to home base! -- If fuel < threshold ==> send helo to home base!
if fuel<self.lowfuel and self:IsRunning() then if fuel<self.lowfuel and self:IsRunning() then
@ -719,13 +809,23 @@ function RESCUEHELO:onafterRun(From, Event, To)
-- Restart formation if stopped. -- Restart formation if stopped.
if self.formation:Is("Stopped") then if self.formation:Is("Stopped") then
self:I(string.format("Restarting formation of rescue helo %s.", self.helo:GetName())) -- Debug info.
local text=string.format("Restarting formation of rescue helo %s.", self.helo:GetName())
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Start formation.
self.formation:Start() self.formation:Start()
end end
-- Restart route of carrier if it was stopped. -- Restart route of carrier if it was stopped.
if self.carrierstop then if self.carrierstop then
self:I("Carrier resuming route after rescue operation.") -- Debug info.
local text="Carrier resuming route after rescue operation."
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Resume route of carrier.
self.carrier:RouteResume() self.carrier:RouteResume()
self.carrierstop=false self.carrierstop=false
end end
@ -742,7 +842,8 @@ function RESCUEHELO:onafterRescue(From, Event, To, RescueCoord)
-- Debug message. -- Debug message.
local text=string.format("Helo %s is send to rescue mission.", self.helo:GetName()) local text=string.format("Helo %s is send to rescue mission.", self.helo:GetName())
self:I(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Waypoint array. -- Waypoint array.
local wp={} local wp={}
@ -761,7 +862,7 @@ function RESCUEHELO:onafterRescue(From, Event, To, RescueCoord)
wp[2]=RescueCoord:SetAltitude(50):WaypointAirTurningPoint(nil, 200, {RescueTask}, "Crash Site") wp[2]=RescueCoord:SetAltitude(50):WaypointAirTurningPoint(nil, 200, {RescueTask}, "Crash Site")
wp[3]=self.airbase:GetCoordinate():SetAltitude(70):WaypointAirLanding(200, self.airbase, {}, "Land at Home Base") wp[3]=self.airbase:GetCoordinate():SetAltitude(70):WaypointAirLanding(200, self.airbase, {}, "Land at Home Base")
-- Initialize WP and route tanker. -- Initialize WP and route helo.
self.helo:WayPointInitialize(wp) self.helo:WayPointInitialize(wp)
-- Set task. -- Set task.
@ -772,7 +873,10 @@ function RESCUEHELO:onafterRescue(From, Event, To, RescueCoord)
-- Stop carrier. -- Stop carrier.
if self.rescuestopboat then if self.rescuestopboat then
self:I("Stopping carrier for rescue operation.") local text="Stopping carrier for rescue operation."
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.carrier:RouteStop() self.carrier:RouteStop()
self.carrierstop=true self.carrierstop=true
end end
@ -792,7 +896,8 @@ function RESCUEHELO:onbeforeRTB(From, Event, To)
-- Debug message. -- Debug message.
local text=string.format("Respawning rescue helo group %s in air.", self.helo:GetName()) local text=string.format("Respawning rescue helo group %s in air.", self.helo:GetName())
self:I(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Respawn helo. -- Respawn helo.
self.helo:InitHeading(self.helo:GetHeading()) self.helo:InitHeading(self.helo:GetHeading())
@ -805,7 +910,7 @@ function RESCUEHELO:onbeforeRTB(From, Event, To)
return true return true
end end
--- On after RTB event. Send tanker back to carrier. --- On after RTB event. Send helo back to carrier.
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
@ -814,7 +919,8 @@ function RESCUEHELO:onafterRTB(From, Event, To)
-- Debug message. -- Debug message.
local text=string.format("Rescue helo %s is returning to airbase %s.", self.helo:GetName(), self.airbase:GetName()) local text=string.format("Rescue helo %s is returning to airbase %s.", self.helo:GetName(), self.airbase:GetName())
self:I(text) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Waypoint array. -- Waypoint array.
local wp={} local wp={}
@ -823,7 +929,7 @@ function RESCUEHELO:onafterRTB(From, Event, To)
wp[1]=self.helo:GetCoordinate():WaypointAirTurningPoint(nil, 300, {}, "Current Position") wp[1]=self.helo:GetCoordinate():WaypointAirTurningPoint(nil, 300, {}, "Current Position")
wp[2]=self.airbase:GetCoordinate():SetAltitude(70):WaypointAirLanding(300, self.airbase, {}, "Landing at Home Base") wp[2]=self.airbase:GetCoordinate():SetAltitude(70):WaypointAirLanding(300, self.airbase, {}, "Landing at Home Base")
-- Initialize WP and route tanker. -- Initialize WP and route helo.
self.helo:WayPointInitialize(wp) self.helo:WayPointInitialize(wp)
-- Set task. -- Set task.