Merge pull request #1112 from FlightControl-Master/FF/Develop

Carrier Ops
This commit is contained in:
Frank 2019-01-31 14:21:49 +01:00 committed by GitHub
commit 264e84649e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 692 additions and 252 deletions

View File

@ -7,7 +7,7 @@
-- === -- ===
-- --
-- @module AI.AI_Air -- @module AI.AI_Air
-- @image AI_Air_Operations.JPG -- @image MOOSE.JPG
--- @type AI_AIR --- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE

View File

@ -325,6 +325,7 @@ function SPAWN:New( SpawnTemplatePrefix )
self.SpawnInitFreq = nil -- No special frequency. self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation. self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
@ -376,6 +377,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.SpawnInitFreq = nil -- No special frequency. self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation. self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
@ -430,6 +432,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
self.SpawnInitFreq = nil -- No special frequency. self.SpawnInitFreq = nil -- No special frequency.
self.SpawnInitModu = nil -- No special modulation. self.SpawnInitModu = nil -- No special modulation.
self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitRadio = nil -- No radio comms setting.
self.SpawnInitModex = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else else
@ -642,6 +645,19 @@ function SPAWN:InitRadioModulation(modulation)
return self return self
end end
--- Sets the modex of the first unit of the group. If more units are in the group, the number is increased by one with every unit.
-- @param #SPAWN self
-- @param #number modex Modex of the first unit.
-- @return #SPAWN self
function SPAWN:InitModex(modex)
if modex then
self.SpawnInitModex=tonumber(modex)
end
return self
end
--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. --- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
-- @param #SPAWN self -- @param #SPAWN self
@ -1219,6 +1235,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
end end
end end
-- Set tail number.
if self.SpawnInitModex then
for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].onboard_num = string.format("%03d", self.SpawnInitModex+(UnitID-1))
end
end
-- Set radio comms on/off. -- Set radio comms on/off.
if self.SpawnInitRadio then if self.SpawnInitRadio then

View File

@ -11,7 +11,7 @@
-- --
-- ## Features: -- ## Features:
-- --
-- * Impact points of bombs, rockets and missils are recorded and distance to closest range target is measured and reported to the player. -- * Impact points of bombs, rockets and missiles are recorded and distance to closest range target is measured and reported to the player.
-- * Number of hits on strafing passes are counted and reported. Also the percentage of hits w.r.t fired shots is evaluated. -- * Number of hits on strafing passes are counted and reported. Also the percentage of hits w.r.t fired shots is evaluated.
-- * Results of all bombing and strafing runs are stored and top 10 results can be displayed. -- * Results of all bombing and strafing runs are stored and top 10 results can be displayed.
-- * Range targets can be marked by smoke. -- * Range targets can be marked by smoke.
@ -56,9 +56,9 @@
-- @field #table strafeStatus Table containing the current strafing target a player as assigned to. -- @field #table strafeStatus Table containing the current strafing target a player as assigned to.
-- @field #table strafePlayerResults Table containing the strafing results of each player. -- @field #table strafePlayerResults Table containing the strafing results of each player.
-- @field #table bombPlayerResults Table containing the bombing results of each player. -- @field #table bombPlayerResults Table containing the bombing results of each player.
-- @field #table PlayerSettings Indiviual player settings. -- @field #table PlayerSettings Individual player settings.
-- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds. -- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds.
-- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threashold [m]. Default 25000 m. -- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threshold [m]. Default 25000 m.
-- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec.
-- @field #string examinergroupname Name of the examiner group which should get all messages. -- @field #string examinergroupname Name of the examiner group which should get all messages.
-- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. -- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages.
@ -75,10 +75,11 @@
-- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor. --- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor.
-- The parameter "rangename" defindes the name of the range. It has to be unique since this is also the name displayed in the radio menu. -- The parameter "rangename" defines the name of the range. It has to be unique since this is also the name displayed in the radio menu.
-- --
-- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated. -- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated.
-- For bombing targets, the distance from the impact point of the bomb, rocket or missile to the closest range target is measured and tabulated. -- For bombing targets, the distance from the impact point of the bomb, rocket or missile to the closest range target is measured and tabulated.
@ -89,12 +90,12 @@
-- **IMPORTANT** -- **IMPORTANT**
-- --
-- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that -- Due to a DCS bug, it is not possible to directly monitor when a player enters a plane. So in a mission with client slots, it is vital that
-- a player first enters as spector and **after that** jumps into the slot of his aircraft! -- a player first enters as spectator or hits ESC twice and **after that** jumps into the slot of his aircraft!
-- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly, -- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly,
-- there should be an "On the Range" menu items in the "F10. Other..." menu. -- there should be an "On the Range" menu items in the "F10. Other..." menu.
-- --
-- ## Strafe Pits -- ## Strafe Pits
-- Each strafe pit can consist of multiple targets. Often one findes two or three strafe targets next to each other. -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other.
-- --
-- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function.
-- --
@ -104,7 +105,7 @@
-- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. -- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME.
-- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the -- The parameter *inverseheading* turns the heading around by 180 degrees. This is sometimes useful, since the default heading of strafe target units point in the
-- wrong/opposite direction. -- wrong/opposite direction.
-- * The parameter *goodpass* defines the number of hits a pilot has to achive during a run to be judged as a "good" pass. -- * The parameter *goodpass* defines the number of hits a pilot has to achieve during a run to be judged as a "good" pass.
-- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted!
-- --
-- Another function to add a strafe pit is @{#RANGE.AddStrafePitGroup}(*group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*). Here, -- Another function to add a strafe pit is @{#RANGE.AddStrafePitGroup}(*group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*). Here,
@ -151,7 +152,7 @@
-- * "F2. My Settings": Player specific settings. -- * "F2. My Settings": Player specific settings.
-- * "F3. Stats" Player: statistics and scores. -- * "F3. Stats" Player: statistics and scores.
-- * "Range Information": Information about the range, such as bearing and range. Also range and player specific settings are displayed. -- * "Range Information": Information about the range, such as bearing and range. Also range and player specific settings are displayed.
-- * "Weather Report": Temperatur, wind and QFE pressure information is provided. -- * "Weather Report": Temperature, wind and QFE pressure information is provided.
-- --
-- ## Examples -- ## Examples
-- --
@ -243,6 +244,7 @@ RANGE={
trackbombs=true, trackbombs=true,
trackrockets=true, trackrockets=true,
trackmissiles=true, trackmissiles=true,
defaultsmokebomb=true,
} }
--- Default range parameters. --- Default range parameters.
@ -266,19 +268,25 @@ RANGE.Defaults={
-- @field #table Names -- @field #table Names
RANGE.Names={} RANGE.Names={}
--- Main radio menu. --- Main radio menu on group level.
-- @field #table MenuF10 -- @field #table MenuF10 Root menu table on group level.
RANGE.MenuF10={} RANGE.MenuF10={}
--- Main radio menu on mission level.
-- @field #table MenuF10Root Root menu on mission level.
RANGE.MenuF10Root=nil
--- Some ID to identify who we are in output of the DCS.log file. --- Some ID to identify who we are in output of the DCS.log file.
-- @field #string id -- @field #string id
RANGE.id="RANGE | " RANGE.id="RANGE | "
--- Range script version. --- Range script version.
-- @field #string version -- @field #string version
RANGE.version="1.2.3" RANGE.version="1.2.4"
--TODO list: --TODO list:
--TODO: Verbosity level for messages.
--TODO: Add option for default settings such as smoke off.
--TODO: Add custom weapons, which can be specified by the user. --TODO: Add custom weapons, which can be specified by the user.
--TODO: Check if units are still alive. --TODO: Check if units are still alive.
--DONE: Add statics for strafe pits. --DONE: Add statics for strafe pits.
@ -311,99 +319,111 @@ function RANGE:New(rangename)
self:E(RANGE.id..text) self:E(RANGE.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug) MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Defaults
self:SetDefaultPlayerSmokeBomb()
-- Return object. -- Return object.
return self return self
end end
--- Initializes number of targets and location of the range. Starts the event handlers. --- Initializes number of targets and location of the range. Starts the event handlers.
-- @param #RANGE self -- @param #RANGE self
function RANGE:Start() -- @param #number delay Delay in seconds, before the RANGE is started. Default immediately.
-- @return self
function RANGE:Start(delay)
self:F() self:F()
-- Location/coordinate of range. if delay and delay>0 then
local _location=nil SCHEDULER:New(nil, self.Start, {self}, delay)
else
-- Count bomb targets. -- Location/coordinate of range.
local _count=0 local _location=nil
for _,_target in pairs(self.bombingTargets) do
_count=_count+1
-- Get range location. -- Count bomb targets.
if _location==nil then local _count=0
_location=_target.target:GetCoordinate() --Core.Point#COORDINATE for _,_target in pairs(self.bombingTargets) do
end _count=_count+1
end
self.nbombtargets=_count
-- Count strafing targets. -- Get range location.
_count=0
for _,_target in pairs(self.strafeTargets) do
_count=_count+1
for _,_unit in pairs(_target.targets) do
if _location==nil then if _location==nil then
_location=_unit:GetCoordinate() _location=_target.target:GetCoordinate() --Core.Point#COORDINATE
end end
end end
end self.nbombtargets=_count
self.nstrafetargets=_count
-- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user. -- Count strafing targets.
if self.location==nil then _count=0
self.location=_location for _,_target in pairs(self.strafeTargets) do
end _count=_count+1
if self.location==nil then for _,_unit in pairs(_target.targets) do
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets) if _location==nil then
self:E(RANGE.id..text) _location=_unit:GetCoordinate()
return end
end end
end
self.nstrafetargets=_count
-- Define a MOOSE zone of the range. -- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user.
if self.rangezone==nil then if self.location==nil then
self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius) self.location=_location
end end
-- Starting range. if self.location==nil then
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets) local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:E(RANGE.id..text) self:E(RANGE.id..text)
MESSAGE:New(text,10):ToAllIf(self.Debug) return
end
-- Event handling. -- Define a MOOSE zone of the range.
if self.eventmoose then if self.rangezone==nil then
-- Events are handled my MOOSE. self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius)
self:T(RANGE.id.."Events are handled by MOOSE.") end
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
-- Events are handled directly by DCS.
self:T(RANGE.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
-- Make bomb target move randomly within the range zone. -- Starting range.
for _,_target in pairs(self.bombingTargets) do local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:I(RANGE.id..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
-- Check if it is a static object. -- Event handling.
local _static=self:_CheckStatic(_target.target:GetName()) if self.eventmoose then
-- Events are handled my MOOSE.
self:T(RANGE.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
-- Events are handled directly by DCS.
self:T(RANGE.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
if _target.move and _static==false and _target.speed>1 then -- Make bomb target move randomly within the range zone.
local unit=_target.target --Wrapper.Unit#UNIT for _,_target in pairs(self.bombingTargets) do
_target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road")
-- Check if it is a static object.
local _static=self:_CheckStatic(_target.target:GetName())
if _target.move and _static==false and _target.speed>1 then
local unit=_target.target --Wrapper.Unit#UNIT
_target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road")
end
end
-- Debug mode: smoke all targets and range zone.
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end end
end end
-- Debug mode: smoke all targets and range zone. return self
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -412,144 +432,199 @@ end
--- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass. --- Set maximal strafing altitude. Player entering a strafe pit above that altitude are not registered for a valid pass.
-- @param #RANGE self -- @param #RANGE self
-- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft. -- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft.
-- @return #RANGE self
function RANGE:SetMaxStrafeAlt(maxalt) function RANGE:SetMaxStrafeAlt(maxalt)
self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt
return self
end end
--- Set time interval for tracking bombs. A smaller time step increases accuracy but needs more CPU time. --- Set time interval for tracking bombs. A smaller time step increases accuracy but needs more CPU time.
-- @param #RANGE self -- @param #RANGE self
-- @param #number dt Time interval in seconds. Default is 0.005 s. -- @param #number dt Time interval in seconds. Default is 0.005 s.
-- @return #RANGE self
function RANGE:SetBombtrackTimestep(dt) function RANGE:SetBombtrackTimestep(dt)
self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack
return self
end end
--- Set time how long (most) messages are displayed. --- Set time how long (most) messages are displayed.
-- @param #RANGE self -- @param #RANGE self
-- @param #number time Time in seconds. Default is 30 s. -- @param #number time Time in seconds. Default is 30 s.
-- @return #RANGE self
function RANGE:SetMessageTimeDuration(time) function RANGE:SetMessageTimeDuration(time)
self.Tmsg=time or RANGE.Defaults.Tmsg self.Tmsg=time or RANGE.Defaults.Tmsg
return self
end end
--- Set messages to examiner. The examiner will receive messages from all clients. --- Set messages to examiner. The examiner will receive messages from all clients.
-- @param #RANGE self -- @param #RANGE self
-- @param #string examinergroupname Name of the group of the examiner. -- @param #string examinergroupname Name of the group of the examiner.
-- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. -- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients.
-- @return #RANGE self
function RANGE:SetMessageToExaminer(examinergroupname, exclusively) function RANGE:SetMessageToExaminer(examinergroupname, exclusively)
self.examinergroupname=examinergroupname self.examinergroupname=examinergroupname
self.examinerexclusive=exclusively self.examinerexclusive=exclusively
return self
end end
--- Set max number of player results that are displayed. --- Set max number of player results that are displayed.
-- @param #RANGE self -- @param #RANGE self
-- @param #number nmax Number of results. Default is 10. -- @param #number nmax Number of results. Default is 10.
-- @return #RANGE self
function RANGE:SetDisplayedMaxPlayerResults(nmax) function RANGE:SetDisplayedMaxPlayerResults(nmax)
self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult
return self
end end
--- Set range radius. Defines the area in which e.g. bomb impacts are smoked. --- Set range radius. Defines the area in which e.g. bomb impacts are smoked.
-- @param #RANGE self -- @param #RANGE self
-- @param #number radius Radius in km. Default 5 km. -- @param #number radius Radius in km. Default 5 km.
-- @return #RANGE self
function RANGE:SetRangeRadius(radius) function RANGE:SetRangeRadius(radius)
self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius
return self
end
--- Set player setting whether bomb impact points are smoked or not
-- @param #RANGE self
-- @param #boolean If true nor nil default is to smoke impact points of bombs.
-- @return #RANGE self
function RANGE:SetDefaultPlayerSmokeBomb(switch)
if switch==true or switch==nil then
self.defaultsmokebomb=true
else
self.defaultsmokebomb=false
end
return self
end end
--- Set bomb track threshold distance. Bombs/rockets/missiles are only tracked if player-range distance is less than this distance. Default 25 km. --- Set bomb track threshold distance. Bombs/rockets/missiles are only tracked if player-range distance is less than this distance. Default 25 km.
-- @param #RANGE self -- @param #RANGE self
-- @param #number distance Threshold distance in km. Default 25 km. -- @param #number distance Threshold distance in km. Default 25 km.
-- @return #RANGE self
function RANGE:SetBombtrackThreshold(distance) function RANGE:SetBombtrackThreshold(distance)
self.BombtrackThreshold=distance*1000 or 25*1000 self.BombtrackThreshold=distance*1000 or 25*1000
return self
end end
--- Set range location. If this is not done, one (random) unit position of the range is used to determine the location of the range. --- Set range location. If this is not done, one (random) unit position of the range is used to determine the location of the range.
-- The range location determines the position at which the weather data is evaluated. -- The range location determines the position at which the weather data is evaluated.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the range. -- @param Core.Point#COORDINATE coordinate Coordinate of the range.
-- @return #RANGE self
function RANGE:SetRangeLocation(coordinate) function RANGE:SetRangeLocation(coordinate)
self.location=coordinate self.location=coordinate
return self
end end
--- Set range zone. For example, no bomb impact points are smoked if a bomb falls outside of this zone. --- Set range zone. For example, no bomb impact points are smoked if a bomb falls outside of this zone.
-- If a zone is not explicitly specified, the range zone is determined by its location and radius. -- If a zone is not explicitly specified, the range zone is determined by its location and radius.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters.
-- @return #RANGE self
function RANGE:SetRangeZone(zone) function RANGE:SetRangeZone(zone)
self.rangezone=zone self.rangezone=zone
return self
end end
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke. --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red.
-- @return #RANGE self
function RANGE:SetBombTargetSmokeColor(colorid) function RANGE:SetBombTargetSmokeColor(colorid)
self.BombSmokeColor=colorid or SMOKECOLOR.Red self.BombSmokeColor=colorid or SMOKECOLOR.Red
return self
end end
--- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke. --- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green.
-- @return #RANGE self
function RANGE:SetStrafeTargetSmokeColor(colorid) function RANGE:SetStrafeTargetSmokeColor(colorid)
self.StrafeSmokeColor=colorid or SMOKECOLOR.Green self.StrafeSmokeColor=colorid or SMOKECOLOR.Green
return self
end end
--- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke. --- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White.
-- @return #RANGE self
function RANGE:SetStrafePitSmokeColor(colorid) function RANGE:SetStrafePitSmokeColor(colorid)
self.StrafePitSmokeColor=colorid or SMOKECOLOR.White self.StrafePitSmokeColor=colorid or SMOKECOLOR.White
return self
end end
--- Set time delay between bomb impact and starting to smoke the impact point. --- Set time delay between bomb impact and starting to smoke the impact point.
-- @param #RANGE self -- @param #RANGE self
-- @param #number delay Time delay in seconds. Default is 3 seconds. -- @param #number delay Time delay in seconds. Default is 3 seconds.
-- @return #RANGE self
function RANGE:SetSmokeTimeDelay(delay) function RANGE:SetSmokeTimeDelay(delay)
self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke
return self
end end
--- Enable debug modus. --- Enable debug modus.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:DebugON() function RANGE:DebugON()
self.Debug=true self.Debug=true
return self
end end
--- Disable debug modus. --- Disable debug modus.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:DebugOFF() function RANGE:DebugOFF()
self.Debug=false self.Debug=false
return self
end end
--- Enables tracking of all bomb types. Note that this is the default setting. --- Enables tracking of all bomb types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackBombsON() function RANGE:TrackBombsON()
self.trackbombs=true self.trackbombs=true
return self
end end
--- Disables tracking of all bomb types. --- Disables tracking of all bomb types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackBombsOFF() function RANGE:TrackBombsOFF()
self.trackbombs=false self.trackbombs=false
return self
end end
--- Enables tracking of all rocket types. Note that this is the default setting. --- Enables tracking of all rocket types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackRocketsON() function RANGE:TrackRocketsON()
self.trackrockets=true self.trackrockets=true
return self
end end
--- Disables tracking of all rocket types. --- Disables tracking of all rocket types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackRocketsOFF() function RANGE:TrackRocketsOFF()
self.trackrockets=false self.trackrockets=false
return self
end end
--- Enables tracking of all missile types. Note that this is the default setting. --- Enables tracking of all missile types. Note that this is the default setting.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackMissilesON() function RANGE:TrackMissilesON()
self.trackmissiles=true self.trackmissiles=true
return self
end end
--- Disables tracking of all missile types. --- Disables tracking of all missile types.
-- @param #RANGE self -- @param #RANGE self
-- @return #RANGE self
function RANGE:TrackMissilesOFF() function RANGE:TrackMissilesOFF()
self.trackmissiles=false self.trackmissiles=false
return self
end end
@ -564,6 +639,7 @@ end
-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false.
-- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20.
-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line.
-- @return #RANGE self
function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
self:F({targetnames=targetnames, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline}) self:F({targetnames=targetnames, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline})
@ -681,6 +757,8 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline) local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline)
self:T(RANGE.id..text) self:T(RANGE.id..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug) MESSAGE:New(text, 5):ToAllIf(self.Debug)
return self
end end
@ -696,6 +774,7 @@ end
-- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false. -- @param #boolean inverseheading (Optional) Take inverse heading (heading --> heading - 180 Degrees). Default is false.
-- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20. -- @param #number goodpass (Optional) Number of hits for a "good" strafing pass. Default is 20.
-- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line. -- @param #number foulline (Optional) Foul line distance. Hits from closer than this distance are not counted. Default 610 m = 2000 ft. Set to 0 for no foul line.
-- @return #RANGE self
function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
self:F({group=group, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline}) self:F({group=group, boxlength=boxlength, boxwidth=boxwidth, heading=heading, inverseheading=inverseheading, goodpass=goodpass, foulline=foulline})
@ -721,6 +800,7 @@ function RANGE:AddStrafePitGroup(group, boxlength, boxwidth, heading, inversehea
self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline) self:AddStrafePit(_names, boxlength, boxwidth, heading, inverseheading, goodpass, foulline)
end end
return self
end end
--- Add bombing target(s) to range. --- Add bombing target(s) to range.
@ -728,6 +808,7 @@ end
-- @param #table targetnames Table containing names of unit or static objects serving as bomb targets. -- @param #table targetnames Table containing names of unit or static objects serving as bomb targets.
-- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m. -- @param #number goodhitrange (Optional) Max distance from target unit (in meters) which is considered as a good hit. Default is 25 m.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove) function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove}) self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove})
@ -757,6 +838,8 @@ function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
end end
end end
return self
end end
--- Add a unit or static object as bombing target. --- Add a unit or static object as bombing target.
@ -764,6 +847,7 @@ end
-- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target. -- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove) function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove}) self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove})
@ -798,6 +882,8 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
-- Insert target to table. -- Insert target to table.
table.insert(self.bombingTargets, {name=name, target=unit, goodhitrange=goodhitrange, move=randommove, speed=speed}) table.insert(self.bombingTargets, {name=name, target=unit, goodhitrange=goodhitrange, move=randommove, speed=speed})
return self
end end
--- Add all units of a group as bombing targets. --- Add all units of a group as bombing targets.
@ -805,6 +891,7 @@ end
-- @param Wrapper.Group#GROUP group Group of bombing targets. -- @param Wrapper.Group#GROUP group Group of bombing targets.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove) function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
self:F({group=group, goodhitrange=goodhitrange, randommove=randommove}) self:F({group=group, goodhitrange=goodhitrange, randommove=randommove})
@ -819,6 +906,7 @@ function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
end end
end end
return self
end end
--- Measures the foule line distance between two unit or static objects. --- Measures the foule line distance between two unit or static objects.
@ -971,11 +1059,12 @@ function RANGE:OnEventBirth(EventData)
-- By default, some bomb impact points and do not flare each hit on target. -- By default, some bomb impact points and do not flare each hit on target.
self.PlayerSettings[_playername]={} self.PlayerSettings[_playername]={}
self.PlayerSettings[_playername].smokebombimpact=true self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb
self.PlayerSettings[_playername].flaredirecthits=false self.PlayerSettings[_playername].flaredirecthits=false
self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue
self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red
self.PlayerSettings[_playername].delaysmoke=true self.PlayerSettings[_playername].delaysmoke=true
self.PlayerSettings[_playername].messages=true
-- Start check in zone timer. -- Start check in zone timer.
if self.planes[_uid] ~= true then if self.planes[_uid] ~= true then
@ -1042,7 +1131,7 @@ function RANGE:OnEventHit(EventData)
if _currentTarget.pastfoulline==false and _unit and _playername then if _currentTarget.pastfoulline==false and _unit and _playername then
local _d=_currentTarget.zone.foulline local _d=_currentTarget.zone.foulline
local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname(_unitName), _d, targetname) local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname(_unitName), _d, targetname)
self:_DisplayMessageToGroup(_unit, text, 10) self:_DisplayMessageToGroup(_unit, text)
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
_currentTarget.pastfoulline=true _currentTarget.pastfoulline=true
end end
@ -1320,7 +1409,7 @@ function RANGE:_DisplayMyStrafePitResults(_unitName)
end end
-- Send message to group. -- Send message to group.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@ -1376,7 +1465,7 @@ function RANGE:_DisplayStrafePitResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@ -1433,7 +1522,7 @@ function RANGE:_DisplayMyBombingResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@ -1489,7 +1578,7 @@ function RANGE:_DisplayBombingResults(_unitName)
end end
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
end end
end end
@ -1566,7 +1655,7 @@ function RANGE:_DisplayRangeInfo(_unitname)
text=text..textdelay text=text..textdelay
-- Send message to player group. -- Send message to player group.
self:_DisplayMessageToGroup(unit, text, nil, true) self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output. -- Debug output.
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
@ -1603,7 +1692,7 @@ function RANGE:_DisplayBombTargets(_unitname)
end end
end end
self:_DisplayMessageToGroup(_unit,_text, nil, true) self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
end end
end end
@ -1643,7 +1732,7 @@ function RANGE:_DisplayStrafePits(_unitname)
_text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading) _text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading)
end end
self:_DisplayMessageToGroup(_unit,_text, nil, true) self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
end end
end end
@ -1705,7 +1794,7 @@ function RANGE:_DisplayRangeWeather(_unitname)
end end
-- Send message to player group. -- Send message to player group.
self:_DisplayMessageToGroup(unit, text, nil, true) self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output. -- Debug output.
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
@ -1749,7 +1838,7 @@ function RANGE:_CheckInZone(_unitName)
local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit
-- Debug output -- Debug output
local text=string.format("Checking stil in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading) local text=string.format("Checking still in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
self:T2(RANGE.id..text) self:T2(RANGE.id..text)
-- Check if player is in strafe zone and below max alt. -- Check if player is in strafe zone and below max alt.
@ -1895,11 +1984,32 @@ function RANGE:_AddF10Commands(_unitName)
-- Enable switch so we don't do this twice. -- Enable switch so we don't do this twice.
self.MenuAddedTo[_gid] = true self.MenuAddedTo[_gid] = true
-- Main F10 menu: F10/On the Range/<Range Name>/ -- Range root menu path.
if RANGE.MenuF10[_gid] == nil then local _rangePath=nil
RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range")
if RANGE.MenuF10Root then
-------------------
-- MISSION LEVEL --
-------------------
_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root)
else
-----------------
-- GROUP LEVEL --
-----------------
-- Main F10 menu: F10/On the Range/<Range Name>/
if RANGE.MenuF10[_gid] == nil then
RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range")
end
_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid])
end end
local _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid])
local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath) local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath)
local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath) local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath)
local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath)
@ -1932,9 +2042,11 @@ function RANGE:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(_gid, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) missionCommands.addCommandForGroup(_gid, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White)
missionCommands.addCommandForGroup(_gid, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) missionCommands.addCommandForGroup(_gid, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow)
-- F10/On the Range/<Range Name>/My Settings/ -- F10/On the Range/<Range Name>/My Settings/
missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName)
-- F10/On the Range/<Range Name>/Range Information -- F10/On the Range/<Range Name>/Range Information
missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName)
@ -2114,7 +2226,7 @@ function RANGE:_ResetRangeStats(_unitName)
self.strafePlayerResults[_playername] = nil self.strafePlayerResults[_playername] = nil
self.bombPlayerResults[_playername] = nil self.bombPlayerResults[_playername] = nil
local text=string.format("%s, %s, your range stats were cleared.", self.rangename, _playername) local text=string.format("%s, %s, your range stats were cleared.", self.rangename, _playername)
self:DisplayMessageToGroup(_unit, text, 5) self:DisplayMessageToGroup(_unit, text, 5, false, true)
end end
end end
@ -2124,33 +2236,35 @@ end
-- @param #string _text Message text. -- @param #string _text Message text.
-- @param #number _time Duration how long the message is displayed. -- @param #number _time Duration how long the message is displayed.
-- @param #boolean _clear Clear up old messages. -- @param #boolean _clear Clear up old messages.
function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear) -- @param #boolean display If true, display message regardless of player setting "Messages Off".
function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear, display)
self:F({unit=_unit, text=_text, time=_time, clear=_clear}) self:F({unit=_unit, text=_text, time=_time, clear=_clear})
-- Defaults
_time=_time or self.Tmsg _time=_time or self.Tmsg
if _clear==nil then if _clear==nil or _clear==false then
_clear=false _clear=false
else
_clear=true
end end
-- Group ID. -- Group ID.
local _gid=_unit:GetGroup():GetID() local _gid=_unit:GetGroup():GetID()
if _gid and not self.examinerexclusive then -- Get playername and player settings
if _clear == true then local _, playername=self:_GetPlayerUnitAndName(_unit:GetName())
trigger.action.outTextForGroup(_gid, _text, _time, _clear) local playermessage=self.PlayerSettings[playername].messages
else
trigger.action.outTextForGroup(_gid, _text, _time) -- Send message to player if messages enabled and not only for the examiner.
end if _gid and (playermessage==true or display) and (not self.examinerexclusive) then
trigger.action.outTextForGroup(_gid, _text, _time, _clear)
end end
-- Send message to examiner.
if self.examinergroupname~=nil then if self.examinergroupname~=nil then
local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() local _examinerid=GROUP:FindByName(self.examinergroupname):GetID()
if _examinerid then if _examinerid then
if _clear == true then trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
else
trigger.action.outTextForGroup(_examinerid, _text, _time)
end
end end
end end
@ -2172,7 +2286,7 @@ function RANGE:_SmokeBombImpactOnOff(unitname)
self.PlayerSettigs[playername].smokebombimpact=true self.PlayerSettigs[playername].smokebombimpact=true
text=string.format("%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername) text=string.format("%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end end
end end
@ -2193,7 +2307,27 @@ function RANGE:_SmokeBombDelayOnOff(unitname)
self.PlayerSettigs[playername].delaysmoke=true self.PlayerSettigs[playername].delaysmoke=true
text=string.format("%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername) text=string.format("%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end
end
--- Toggle display messages to player.
-- @param #RANGE self
-- @param #string unitname Name of the player unit.
function RANGE:_MessagesToPlayerOnOff(unitname)
self:F(unitname)
local unit, playername = self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].messages==true then
text=string.format("%s, %s, display of ALL messages is now OFF.", self.rangename, playername)
else
text=string.format("%s, %s, display of ALL messages is now ON.", self.rangename, playername)
end
self:_DisplayMessageToGroup(unit, text, 5, false, true)
self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages
end end
end end
@ -2214,7 +2348,7 @@ function RANGE:_FlareDirectHitsOnOff(unitname)
self.PlayerSettings[playername].flaredirecthits=true self.PlayerSettings[playername].flaredirecthits=true
text=string.format("%s, %s, flaring direct hits is now ON.", self.rangename, playername) text=string.format("%s, %s, flaring direct hits is now ON.", self.rangename, playername)
end end
self:_DisplayMessageToGroup(unit, text, 5) self:_DisplayMessageToGroup(unit, text, 5, false, true)
end end
end end
@ -2332,7 +2466,7 @@ function RANGE:_smokecolor2text(color)
elseif color==SMOKECOLOR.White then elseif color==SMOKECOLOR.White then
txt="white" txt="white"
else else
txt=string.format("unkown color (%s)", tostring(color)) txt=string.format("unknown color (%s)", tostring(color))
end end
return txt return txt
@ -2355,7 +2489,7 @@ function RANGE:_flarecolor2text(color)
elseif color==FLARECOLOR.Yellow then elseif color==FLARECOLOR.Yellow then
txt="yellow" txt="yellow"
else else
txt=string.format("unkown color (%s)", tostring(color)) txt=string.format("unknown color (%s)", tostring(color))
end end
return txt return txt

View File

@ -45,6 +45,11 @@
-- **PLEASE NOTE** that his class is work in progress and in an early **alpha** stage. Many/most things work already very nicely but there a lot of cases I did not run into yet. -- **PLEASE NOTE** that his class is work in progress and in an early **alpha** stage. Many/most things work already very nicely but there a lot of cases I did not run into yet.
-- Therefore, your *constructive* feedback is both necessary and appreciated! -- Therefore, your *constructive* feedback is both necessary and appreciated!
-- --
-- ## Discussion
--
-- If you have questions or suggestions, visit the MOOSE Discord [#ops-airboss](https://discordapp.com/channels/378590350614462464/527363141185830915) channel.
-- There you also find an example mission and the necessary voice over sound files. Check the **pinned messages**.
--
-- ## IMPORTANT -- ## IMPORTANT
-- --
-- Due to technical restrictions of DCS make sure you have: -- Due to technical restrictions of DCS make sure you have:
@ -52,6 +57,12 @@
-- * Each player slot in a separate group. DCS does only allow to send messages to groups and not to individual units. -- * Each player slot in a separate group. DCS does only allow to send messages to groups and not to individual units.
-- * Players are identified by their player name. Ensure that no two player have the same name, e.g. "New Callsign", as this will lead to unexpected results. -- * Players are identified by their player name. Ensure that no two player have the same name, e.g. "New Callsign", as this will lead to unexpected results.
-- --
-- ## Youtube Videos
--
-- * [[MOOSE] Airboss - Groove Testing (WIP)](https://www.youtube.com/watch?v=94KHQxxX3UI)
-- * [[MOOSE] Airboss - Groove Test A-4E Community Mod](https://www.youtube.com/watch?v=ZbjD7FHiaHo)
--
--
-- ### Open Questions? -- ### Open Questions?
-- --
-- * Currently the script does not support spin patterns. Marshal releases flights only when there is a free slot in the landing pattern. How is this handled in real life? -- * Currently the script does not support spin patterns. Marshal releases flights only when there is a free slot in the landing pattern. How is this handled in real life?
@ -163,6 +174,8 @@
-- @field Core.Set#SET_GROUP squadsetAI AI groups in this set will be handled by the airboss. -- @field Core.Set#SET_GROUP squadsetAI AI groups in this set will be handled by the airboss.
-- @field #boolean menusingle If true, menu is optimized for a single carrier. -- @field #boolean menusingle If true, menu is optimized for a single carrier.
-- @field #number collisiondist Distance up to which collision checks are done. -- @field #number collisiondist Distance up to which collision checks are done.
-- @field #number Tmessage Default duration in seconds messages are displayed to players.
-- @field #string soundfolder Folder within the mission (miz) file where airboss sound files are located.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Be the boss! --- Be the boss!
@ -246,9 +259,11 @@
-- The F10 radio menu can be used to post requests to Marshal but also provides information about the player and carrier status. Additionally, helper functions -- The F10 radio menu can be used to post requests to Marshal but also provides information about the player and carrier status. Additionally, helper functions
-- can be called. -- can be called.
-- --
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_MenuMain.png)
--
-- By default, the script creates a submenu "Airboss" in the "F10 Other ..." menu and each @{#AIRBOSS} carrier gets its own submenu. -- By default, the script creates a submenu "Airboss" in the "F10 Other ..." menu and each @{#AIRBOSS} carrier gets its own submenu.
-- If you intend to have only one carrier, you can simplify the menu structure using the @{#AIRBOSS.SetMenuSingleCarrier} function, which will create all carrier specific menu entries directly -- If you intend to have only one carrier, you can simplify the menu structure using the @{#AIRBOSS.SetMenuSingleCarrier} function, which will create all carrier specific menu entries directly
-- in the "Airboss" submenu. (Needless to say, that if you enable this and define mulitiple carriers, the menu structure will get completely screwed up.) -- in the "Airboss" submenu. (Needless to say, that if you enable this and define multiple carriers, the menu structure will get completely screwed up.)
-- --
-- ## Root Menu -- ## Root Menu
-- --
@ -274,9 +289,17 @@
-- ### Request Commence -- ### Request Commence
-- --
-- This command can be used to request commencing from the marshal stack to the landing pattern. Necessary condition is that the player is in the lowest marshal stack -- This command can be used to request commencing from the marshal stack to the landing pattern. Necessary condition is that the player is in the lowest marshal stack
-- and that the number of aircraft in the landing pattern is smaller than four. -- and that the number of aircraft in the landing pattern is smaller than four (or the number set by the mission designer).
--
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1Pattern.png)
--
-- The image displays the standard Case I Marshal pattern recovery. Pilots are supposed to fly a clockwise circle and descent between the **3** and **1** positions.
--
-- Commence should be performed at around the **3** position. If the pilot is in the lowest Marshal stack, and flies through this area, he is automatically cleared for the
-- landing pattern. In other words, there is no need for the "Request Commence" radio command. The zone can be marked via smoke or flared using the player's F10 radio menu.
-- --
-- A player can also request commencing if he is not registered in a marshal stack yet. If the pattern is free, Marshal will allow him to directly enter the landing pattern. -- A player can also request commencing if he is not registered in a marshal stack yet. If the pattern is free, Marshal will allow him to directly enter the landing pattern.
-- However, this is only possible when the Airboss has a nice day - see @{#AIRBOSS.SetAirbossNiceGuy}.
-- --
-- ### Request Refueling -- ### Request Refueling
-- --
@ -441,7 +464,7 @@
-- * **IM** In the Middle (0.5 NM = 926 m), middle one third of the glideslope. -- * **IM** In the Middle (0.5 NM = 926 m), middle one third of the glideslope.
-- * **IC** In Close (0.25 NM = 463 m), last one third of the glideslope. -- * **IC** In Close (0.25 NM = 463 m), last one third of the glideslope.
-- * **AR** At the Ramp (0.027 NM = 50 m). -- * **AR** At the Ramp (0.027 NM = 50 m).
-- * **IW** In the Wiress (at the landing position). -- * **IW** In the Wires (at the landing position).
-- --
-- Grading at each step includes the above calls, i.e. -- Grading at each step includes the above calls, i.e.
-- --
@ -689,6 +712,27 @@
-- --
-- === -- ===
-- --
-- # Sound Files
--
-- An important aspect of the AIRBOSS is that it uses voice overs for greater immersion. The necessary sound files can be obtained from the
-- MOOSE Discord in the [#ops-airboss](https://discordapp.com/channels/378590350614462464/527363141185830915) channel. Check out the **pinned messages**.
--
-- However, including sound files into a new mission is tedious as these usually need to be included into the mission **miz** file via (unused) triggers.
--
-- The default location inside the miz file is "l10n/DEFAULT/". But simply opening the *miz* file with e.g. [7-zip](https://www.7-zip.org/) and copying the files into that folder does not work.
-- The next time the mission is saved, files not included via trigger are automatically removed by DCS.
--
-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used. The location of the sound files can be specified
-- via the @{#AIRBOSS.SetSoundfilesFolder}(*folderpath*) function. The parameter *folderpath* defines the location of the sound files folder within the mission *miz* file.
--
-- ![Banner Image](..\Presentations\AIRBOSS\Airboss_SoundfilesFolder.png)
--
-- For example as
--
-- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles/")
--
-- ===
--
-- # AI Handling -- # AI Handling
-- --
-- The @{#AIRBOSS} class allows to handle incoming AI units and integrate them into the marshal and landing pattern. -- The @{#AIRBOSS} class allows to handle incoming AI units and integrate them into the marshal and landing pattern.
@ -855,6 +899,8 @@ AIRBOSS = {
squadsetAI = nil, squadsetAI = nil,
menusingle = nil, menusingle = nil,
collisiondist = nil, collisiondist = nil,
Tmessage = nil,
soundfolder = nil,
} }
--- Player aircraft types capable of landing on carriers. --- Player aircraft types capable of landing on carriers.
@ -1520,13 +1566,17 @@ AIRBOSS.Difficulty={
-- @field #boolean subtitles If true, display subtitles of radio messages. -- @field #boolean subtitles If true, display subtitles of radio messages.
-- @extends #AIRBOSS.FlightGroup -- @extends #AIRBOSS.FlightGroup
--- Main radio menu: F10 Other/Airboss --- Main group level radio menu: F10 Other/Airboss.
-- @field #table MenuF10 -- @field #table MenuF10
AIRBOSS.MenuF10={} AIRBOSS.MenuF10={}
--- Airboss mission level F10 root menu.
-- @field #table MenuF10Root
AIRBOSS.MenuF10Root=nil
--- Airboss class version. --- Airboss class version.
-- @field #string version -- @field #string version
AIRBOSS.version="0.9.2" AIRBOSS.version="0.9.3"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -1707,6 +1757,7 @@ function AIRBOSS:New(carriername, alias)
-- Set update time intervals. -- Set update time intervals.
self:SetQueueUpdateTime() self:SetQueueUpdateTime()
self:SetStatusUpdateTime() self:SetStatusUpdateTime()
self:SetDefaultMessageDuration()
-- Menu options. -- Menu options.
self:SetMenuMarkZones() self:SetMenuMarkZones()
@ -2271,6 +2322,31 @@ function AIRBOSS:SetAirbossNiceGuy(switch)
return self return self
end end
--- Set folder where the airboss sound files are located **within you mission (miz) file**.
-- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission.
-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used.
-- @param #AIRBOSS self
-- @param #string folderpath The path to the sound files, e.g. "Airboss Soundfiles/".
-- @return #AIRBOSS self
function AIRBOSS:SetSoundfilesFolder(folderpath)
-- Check that it ends with /
if folderpath then
local lastchar=string.sub(folderpath, -1)
if lastchar~="/" then
folderpath=folderpath.."/"
end
end
-- Folderpath.
self.soundfolder=folderpath
-- Info message.
self:I(self.lid..string.format("Setting sound files folder to: %s", self.soundfolder))
return self
end
--- Set time interval for updating player status and other things. --- Set time interval for updating player status and other things.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number interval Time interval in seconds. Default 0.5 sec. -- @param #number interval Time interval in seconds. Default 0.5 sec.
@ -2280,13 +2356,22 @@ function AIRBOSS:SetStatusUpdateTime(interval)
return self return self
end end
--- Set duration how long messages are displayed to players.
-- @param #AIRBOSS self
-- @param #number duration Duration in seconds. Default 10 sec.
-- @return #AIRBOSS self
function AIRBOSS:SetDefaultMessageDuration(duration)
self.Tmessage=duration or 10
return self
end
--- Set Case I Marshal radius. This is the radius of the valid zone around "the post" aircraft are supposed to be holding in the Case I Marshal stack. --- Set Case I Marshal radius. This is the radius of the valid zone around "the post" aircraft are supposed to be holding in the Case I Marshal stack.
-- The post is 2.5 NM port of the carrier. -- The post is 2.5 NM port of the carrier.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number Radius in NM. Default 2.75 NM, which gives a diameter of 5.5 NM. -- @param #number Radius in NM. Default 2.8 NM, which gives a diameter of 5.6 NM.
-- @return #AIRBOSS self -- @return #AIRBOSS self
function AIRBOSS:SetMarshalRadius(radius) function AIRBOSS:SetMarshalRadius(radius)
self.marshalradius=UTILS.NMToMeters(radius or 2.75) self.marshalradius=UTILS.NMToMeters(radius or 2.8)
return self return self
end end
@ -3940,7 +4025,7 @@ function AIRBOSS:_ClearForLanding(flight)
local text=string.format("you are cleared for Case %d recovery.", flight.case) local text=string.format("you are cleared for Case %d recovery.", flight.case)
-- Add a little delay because message that recovery window opened could come just before. -- Add a little delay because message that recovery window opened could come just before.
self:MessageToMarshal(text, "MARSHAL", flight.onboard, 10, false, 2) self:MessageToMarshal(text, "MARSHAL", flight.onboard, nil, false, 2)
end end
@ -4141,7 +4226,7 @@ function AIRBOSS:_WaitPlayer(playerData)
end end
-- Send message. -- Send message.
self:MessageToMarshal(text, "AIRBOSS", playerData.onboard, 10) self:MessageToMarshal(text, "AIRBOSS", playerData.onboard)
-- Add player flight to waiting queue. -- Add player flight to waiting queue.
table.insert(self.Qwaiting, playerData) table.insert(self.Qwaiting, playerData)
@ -6141,7 +6226,7 @@ function AIRBOSS:_Waiting(playerData)
-- Warning if player is inside the zone. -- Warning if player is inside the zone.
if inzone and Twaiting>3*60 and not playerData.warning then if inzone and Twaiting>3*60 and not playerData.warning then
local text=string.format("You are supposed to wait outside the 10 NM zone.") local text=string.format("You are supposed to wait outside the 10 NM zone.")
self:MessageToPlayer(playerData, text, "AIRBOSS", nil, 10) self:MessageToPlayer(playerData, text, "AIRBOSS")
playerData.warning=true playerData.warning=true
end end
@ -6373,7 +6458,8 @@ function AIRBOSS:_Commencing(playerData, zonecheck)
end end
-- Message to player. -- Message to player.
self:MessageToPlayer(playerData, text, "MARSHAL", nil, 3) --self:MessageToPlayer(playerData, text, "MARSHAL", nil, 3)
self:MessageToPlayer(playerData, text, "MARSHAL")
end end
-- Next step: depends on case recovery. -- Next step: depends on case recovery.
@ -7482,7 +7568,7 @@ function AIRBOSS:_CheckFoulDeck(playerData)
-- Player hint for flight students. -- Player hint for flight students.
if playerData.difficulty~=AIRBOSS.Difficulty.HARD then if playerData.difficulty~=AIRBOSS.Difficulty.HARD then
local text=string.format("overfly landing area and enter bolter pattern.") local text=string.format("overfly landing area and enter bolter pattern.")
self:MessageToPlayer(playerData, text, "LSO", nil, 10, false, 3) self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3)
end end
-- Set player parameters for foul deck -- Set player parameters for foul deck
@ -7495,7 +7581,7 @@ function AIRBOSS:_CheckFoulDeck(playerData)
if foulunit then if foulunit then
local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(), self.flights) local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(), self.flights)
if foulflight and not foulflight.ai then if foulflight and not foulflight.ai then
self:MessageToPlayer(foulflight, "move your ass from my runway. NOW!", "AIRBOSS", nil, 10) self:MessageToPlayer(foulflight, "move your ass from my runway. NOW!", "AIRBOSS")
end end
end end
end end
@ -9134,7 +9220,7 @@ function AIRBOSS:_AbortPattern(playerData, X, Z, posData, patternwo)
self:T(self.lid..dtext) self:T(self.lid..dtext)
-- Message to player. -- Message to player.
self:MessageToPlayer(playerData, text, "LSO", nil, 20) self:MessageToPlayer(playerData, text, "LSO")
if patternwo then if patternwo then
@ -9579,7 +9665,7 @@ function AIRBOSS:_Debrief(playerData)
-- Re-enter message. -- Re-enter message.
local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.", heading, UTILS.MetersToNM(distance)) local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.", heading, UTILS.MetersToNM(distance))
self:MessageToPlayer(playerData, text, "LSO", nil, 10, false, 5) self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 5)
else else
@ -9607,7 +9693,7 @@ function AIRBOSS:_Debrief(playerData)
-- Airboss talkto! -- Airboss talkto!
local text=string.format("the deck was fouled but landed anyway. Airboss wants to talk to you!") local text=string.format("the deck was fouled but landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData, text, "LSO", nil, 10, false, 3) self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3)
end end
@ -9630,7 +9716,7 @@ function AIRBOSS:_Debrief(playerData)
-- Airboss talkto! -- Airboss talkto!
local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!") local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData, text, "LSO", nil, 10, false, 3) self:MessageToPlayer(playerData, text, "LSO", nil, nil, false, 3)
end end
@ -9663,7 +9749,7 @@ function AIRBOSS:_Debrief(playerData)
else else
-- Message to player. -- Message to player.
self:MessageToPlayer(playerData, "Undefined state after landing! Please report.", "ERROR", nil, 10) self:MessageToPlayer(playerData, "Undefined state after landing! Please report.", "ERROR", nil, 20)
-- Next step. -- Next step.
playerData.step=AIRBOSS.PatternStep.UNDEFINED playerData.step=AIRBOSS.PatternStep.UNDEFINED
@ -9739,7 +9825,7 @@ function AIRBOSS:_StepHint(playerData, step)
local text=string.format("Optimal setup at next step %s:%s", step, hint) local text=string.format("Optimal setup at next step %s:%s", step, hint)
-- Send hint to player. -- Send hint to player.
self:MessageToPlayer(playerData, text, "AIRBOSS", "", 10, false, 1) self:MessageToPlayer(playerData, text, "AIRBOSS", "", nil, false, 1)
end end
@ -10158,7 +10244,7 @@ function AIRBOSS:_CheckPatternUpdate()
-- 99, new final bearing XXX -- 99, new final bearing XXX
local FB=self:GetFinalBearing(true) local FB=self:GetFinalBearing(true)
local text=string.format("new final bearing %03d°.", FB) local text=string.format("new final bearing %03d°.", FB)
self:MessageToMarshal(text, "AIRBOSS", "99", 10) self:MessageToMarshal(text, "AIRBOSS", "99")
end end
-- Reset parameters for next update check. -- Reset parameters for next update check.
@ -10919,7 +11005,7 @@ function AIRBOSS:Sound2Player(playerData, radio, call, loud, delay)
-- Only to players with subtitle on or if noise is played. -- Only to players with subtitle on or if noise is played.
if playerData.subtitles or self:_NeedsSubtitle(call) then if playerData.subtitles or self:_NeedsSubtitle(call) then
self:MessageToPlayer(playerData, subtitle, nil, "", call.subduration or 10, false, delay) self:MessageToPlayer(playerData, subtitle, nil, "", call.subduration, false, delay)
end end
end end
@ -10985,7 +11071,9 @@ function AIRBOSS:_RadioFilename(call, loud)
-- Construct file name and subtitle. -- Construct file name and subtitle.
local prefix=call.file or "" local prefix=call.file or ""
local suffix=call.suffix or "ogg" local suffix=call.suffix or "ogg"
local path="l10n/DEFAULT/"
-- Path to sound files. Default is in the ME
local path=self.soundfolder or "l10n/DEFAULT/"
-- Loud version. -- Loud version.
if loud then if loud then
@ -11014,7 +11102,7 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration
if playerData and message and message~="" then if playerData and message and message~="" then
-- Default duration. -- Default duration.
duration=duration or 10 duration=duration or self.Tmessage
-- Format message. -- Format message.
local text local text
@ -11070,9 +11158,6 @@ end
-- @param #number delay Delay in seconds, before the message is displayed. -- @param #number delay Delay in seconds, before the message is displayed.
function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, delay) function AIRBOSS:MessageToPattern(message, sender, receiver, duration, clear, delay)
-- Local delay.
local _delay=delay or 0
-- Create new (fake) radio call to show the subtitile. -- Create new (fake) radio call to show the subtitile.
local call=self:_NewRadioCall(AIRBOSS.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender) local call=self:_NewRadioCall(AIRBOSS.LSOCall.NOISE, sender or "LSO", message, duration, receiver, sender)
@ -11092,9 +11177,6 @@ end
-- @param #number delay Delay in seconds, before the message is displayed. -- @param #number delay Delay in seconds, before the message is displayed.
function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, delay) function AIRBOSS:MessageToMarshal(message, sender, receiver, duration, clear, delay)
-- Local delay.
local _delay=delay or 0
-- Create new (fake) radio call to show the subtitile. -- Create new (fake) radio call to show the subtitile.
local call=self:_NewRadioCall(AIRBOSS.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender) local call=self:_NewRadioCall(AIRBOSS.MarshalCall.NOISE, sender or "MARSHAL", message, duration, receiver, sender)
@ -11123,7 +11205,7 @@ function AIRBOSS:_NewRadioCall(call, sender, subtitle, subduration, modexreceive
newcall.subtitle=subtitle or call.subtitle newcall.subtitle=subtitle or call.subtitle
-- Duration of subtitle display. -- Duration of subtitle display.
newcall.subduration=subduration or 10 newcall.subduration=subduration or self.Tmessage
-- Tail number of the receiver. -- Tail number of the receiver.
if self:_IsOnboard(modexreceiver) then if self:_IsOnboard(modexreceiver) then
@ -11238,7 +11320,8 @@ function AIRBOSS:_Number2Sound(playerData, sender, number, delay)
local call=AIRBOSS[Sender][N] --#AIRBOSS.RadioCall local call=AIRBOSS[Sender][N] --#AIRBOSS.RadioCall
-- Create file name. -- Create file name.
local filename=string.format("%s.%s", call.file, call.suffix) --local filename=string.format("%s.%s", call.file, call.suffix)
local filename=self:_RadioFilename(call, false)
-- Play sound. -- Play sound.
USERSOUND:New(filename):ToGroup(playerData.group, delay+wait) USERSOUND:New(filename):ToGroup(playerData.group, delay+wait)
@ -11332,17 +11415,40 @@ function AIRBOSS:_AddF10Commands(_unitName)
-- Enable switch so we don't do this twice. -- Enable switch so we don't do this twice.
self.menuadded[gid]=true self.menuadded[gid]=true
-- Main F10 menu: F10/Airboss/<Carrier Name>/ -- Set menu root path.
if AIRBOSS.MenuF10[gid]==nil then local _rootPath=nil
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss") if AIRBOSS.MenuF10Root then
end ------------------------
-- MISSON LEVEL MENUE --
------------------------
if self.menusingle then
-- F10/Airboss/...
_rootPath=AIRBOSS.MenuF10Root
else
-- F10/Airboss/<Carrier Alias>/...
_rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10Root)
end
-- F10/Airboss/<Carrier>
local _rootPath
if self.menusingle then
_rootPath=AIRBOSS.MenuF10[gid]
else else
_rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10[gid]) ------------------------
-- GROUP LEVEL MENUES --
------------------------
-- Main F10 menu: F10/Airboss/
if AIRBOSS.MenuF10[gid]==nil then
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss")
end
if self.menusingle then
-- F10/Airboss/...
_rootPath=AIRBOSS.MenuF10[gid]
else
-- F10/Airboss/<Carrier Alias>/...
_rootPath=missionCommands.addSubMenuForGroup(gid, self.alias, AIRBOSS.MenuF10[gid])
end
end end
@ -12156,7 +12262,7 @@ function AIRBOSS:_DisplayQueue(_unitname, queue, qname)
end end
-- Send message. -- Send message.
self:MessageToPlayer(playerData, text, nil, "", 10, true) self:MessageToPlayer(playerData, text, nil, "", nil, true)
end end
end end
end end
@ -12472,7 +12578,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName)
local fuel=playerData.unit:GetFuel()*100 local fuel=playerData.unit:GetFuel()*100
local fuelstate=self:_GetFuelState(playerData.unit) local fuelstate=self:_GetFuelState(playerData.unit)
--- -- Number of units in group.
local _,nunitsGround=self:_GetFlightUnits(playerData, true) local _,nunitsGround=self:_GetFlightUnits(playerData, true)
local _,nunitsAirborne=self:_GetFlightUnits(playerData, false) local _,nunitsAirborne=self:_GetFlightUnits(playerData, false)
@ -12487,8 +12593,6 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName)
text=text..string.format("Skill Level: %s\n", playerData.difficulty) text=text..string.format("Skill Level: %s\n", playerData.difficulty)
text=text..string.format("Tail # %s (%s)\n", playerData.onboard, self:_GetACNickname(playerData.actype)) text=text..string.format("Tail # %s (%s)\n", playerData.onboard, self:_GetACNickname(playerData.actype))
text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n", fuelstate/1000, fuel) text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n", fuelstate/1000, fuel)
--text=text..string.format("Aircraft: %s\n", self:_GetACNickname(playerData.actype))
--text=text..string.format("Group: %s\n", playerData.group:GetName())
text=text..string.format("# units: %d (%d airborne)\n", nunitsGround, nunitsAirborne) text=text..string.format("# units: %d (%d airborne)\n", nunitsGround, nunitsAirborne)
text=text..string.format("Section Lead: %s (%d/%d)", tostring(playerData.seclead), #playerData.section+1, self.NmaxSection+1) text=text..string.format("Section Lead: %s (%d/%d)", tostring(playerData.seclead), #playerData.section+1, self.NmaxSection+1)
for _,_sec in pairs(playerData.section) do for _,_sec in pairs(playerData.section) do

View File

@ -59,6 +59,7 @@
-- @field #boolean awacs If true, the groups gets the enroute task AWACS instead of tanker. -- @field #boolean awacs If true, the groups gets the enroute task AWACS instead of tanker.
-- @field #number callsignname Number for the callsign name. -- @field #number callsignname Number for the callsign name.
-- @field #number callsignnumber Number of the callsign name. -- @field #number callsignnumber Number of the callsign name.
-- @field #string modex Tail number of the tanker.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Recovery Tanker. --- Recovery Tanker.
@ -292,6 +293,7 @@ RECOVERYTANKER = {
awacs = nil, awacs = nil,
callsignname = nil, callsignname = nil,
callsignnumber = nil, callsignnumber = nil,
modex = nil,
} }
--- Unique ID (global). --- Unique ID (global).
@ -300,7 +302,7 @@ RECOVERYTANKER.UID=0
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RECOVERYTANKER.version="1.0.5" RECOVERYTANKER.version="1.0.6"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -358,7 +360,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, RECOVERYTANKER.UID) self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, RECOVERYTANKER.UID)
-- Log ID. -- Log ID.
self.lid=string.format("RECOVERYTANKER %s |", self.alias) self.lid=string.format("RECOVERYTANKER %s | ", self.alias)
-- Init default parameters. -- Init default parameters.
self:SetAltitude() self:SetAltitude()
@ -617,6 +619,15 @@ function RECOVERYTANKER:SetCallsign(callsignname, callsignnumber)
return self return self
end end
--- Set modex (tail number) of the tanker.
-- @param #RECOVERYTANKER self
-- @param #number modex Tail number.
-- @return #RECOVERYTANKER self
function RECOVERYTANKER:SetModex(modex)
self.modex=modex
return self
end
--- Set takeoff type. --- Set takeoff type.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param #number takeofftype Takeoff type. -- @param #number takeofftype Takeoff type.
@ -810,8 +821,10 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
-- Handle events. -- Handle events.
self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explcit functions sice OnEventRefueling and OnEventRefuelingStop did not hook. self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explicit functions since OnEventRefueling and OnEventRefuelingStop did not hook!
self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop) self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop)
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead, self._OnEventCrashOrDead)
-- Spawn tanker. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. -- Spawn tanker. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
local Spawn=SPAWN:NewWithAlias(self.tankergroupname, self.alias) local Spawn=SPAWN:NewWithAlias(self.tankergroupname, self.alias)
@ -820,6 +833,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
Spawn:InitRadioCommsOnOff(true) Spawn:InitRadioCommsOnOff(true)
Spawn:InitRadioFrequency(self.RadioFreq) Spawn:InitRadioFrequency(self.RadioFreq)
Spawn:InitRadioModulation(self.RadioModu) Spawn:InitRadioModulation(self.RadioModu)
Spawn:InitModex(self.modex)
-- Spawn on carrier. -- Spawn on carrier.
if self.takeoff==SPAWN.Takeoff.Air then if self.takeoff==SPAWN.Takeoff.Air then
@ -935,6 +949,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
self.tanker:InitRadioCommsOnOff(true) self.tanker:InitRadioCommsOnOff(true)
self.tanker:InitRadioFrequency(self.RadioFreq) self.tanker:InitRadioFrequency(self.RadioFreq)
self.tanker:InitRadioModulation(self.RadioModu) self.tanker:InitRadioModulation(self.RadioModu)
self.tanker:InitModex(self.modex)
-- Respawn tanker. -- Respawn tanker.
self.tanker=self.tanker:Respawn(nil, true) self.tanker=self.tanker:Respawn(nil, true)
@ -991,14 +1006,17 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
-- TANKER is DEAD -- -- TANKER is DEAD --
-------------------- --------------------
-- Stop FSM. if not self:IsStopped() then
self:Stop()
-- Stop FSM.
self:Stop()
-- Restart FSM after 5 seconds.
if self.respawn then
self:__Start(5)
end
-- Restart FSM after 5 seconds.
if self.respawn then
self:__Start(5)
end end
end end
end end
@ -1103,9 +1121,22 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function RECOVERYTANKER:onafterStop(From, Event, To) function RECOVERYTANKER:onafterStop(From, Event, To)
-- Unhandle events.
self:UnHandleEvent(EVENTS.EngineShutdown) self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Refueling) self:UnHandleEvent(EVENTS.Refueling)
self:UnHandleEvent(EVENTS.RefuelingStop) self:UnHandleEvent(EVENTS.RefuelingStop)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
-- If tanker is alive, despawn it.
if self.helo and self.helo:IsAlive() then
self:I(self.lid.."Stopping FSM and despawning tanker.")
self.tanker:Destroy()
else
self:I(self.lid.."Stopping FSM. Tanker was not alive.")
end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1139,6 +1170,7 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
group:InitRadioCommsOnOff(true) group:InitRadioCommsOnOff(true)
group:InitRadioFrequency(self.RadioFreq) group:InitRadioFrequency(self.RadioFreq)
group:InitRadioModulation(self.RadioModu) group:InitRadioModulation(self.RadioModu)
group:InitModex(self.modex)
-- Respawn tanker. -- Respawn tanker.
-- Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076
@ -1221,6 +1253,37 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
end end
--- A unit crashed or died.
-- @param #RECOVERYTANKER self
-- @param Core.Event#EVENTDATA EventData Event data.
function RECOVERYTANKER:_OnEventCrashOrDead(EventData)
self:F2({eventdata=EventData})
-- Check that there is an initiating unit in the event data.
if EventData and EventData.IniUnit then
-- Crashed or dead unit.
local unit=EventData.IniUnit
local unitname=tostring(EventData.IniUnitName)
-- Check that it was the tanker that crashed.
if EventData.IniGroupName==self.tanker:GetName() then
-- Error message.
self:E(self.lid..string.format("Recovery tanker %s crashed!", unitname))
-- Stop FSM.
self:Stop()
-- Restart.
if self.respawn then
self:__Start(5)
end
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- MISC functions -- MISC functions

View File

@ -11,6 +11,12 @@
-- * Multiple helos at different carriers due to object oriented approach. -- * Multiple helos at different carriers due to object oriented approach.
-- * Finite State Machine (FSM) implementation. -- * Finite State Machine (FSM) implementation.
-- --
-- ## Known (DCS) Issues
--
-- * CH-53E does only report 27.5% fuel even if fuel is set to 100% in the ME. See [bug report](https://forums.eagle.ru/showthread.php?t=223712)
-- * CH-53E does not accept USS Tarawa as landing airbase (even it can be spawned on it).
-- * Helos dont move away from their landing position on carriers.
--
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
@ -50,6 +56,7 @@
-- @field #number hid Unit ID of the helo group. (Global) Running number. -- @field #number hid Unit ID of the helo group. (Global) Running number.
-- @field #string alias Alias of the spawn group. -- @field #string alias Alias of the spawn group.
-- @field #number uid Unique ID of this helo. -- @field #number uid Unique ID of this helo.
-- @field #number modex Tail number of the helo.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Rescue Helo --- Rescue Helo
@ -219,6 +226,7 @@ RESCUEHELO = {
carrierstop = nil, carrierstop = nil,
alias = nil, alias = nil,
uid = 0, uid = 0,
modex = nil,
} }
--- Unique ID (global). --- Unique ID (global).
@ -227,7 +235,7 @@ RESCUEHELO.UID=0
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RESCUEHELO.version="1.0.3" RESCUEHELO.version="1.0.5"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -281,7 +289,7 @@ function RESCUEHELO:New(carrierunit, helogroupname)
self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.helogroupname, RESCUEHELO.UID) self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.helogroupname, RESCUEHELO.UID)
-- Log ID. -- Log ID.
self.lid=string.format("RESCUEHELO %s |", self.alias) self.lid=string.format("RESCUEHELO %s | ", self.alias)
-- Init defaults. -- Init defaults.
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
@ -317,12 +325,14 @@ function RESCUEHELO:New(carrierunit, helogroupname)
self:SetStartState("Stopped") self:SetStartState("Stopped")
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") self:AddTransition("Stopped", "Start", "Running")
self:AddTransition("Running", "Rescue", "Rescuing") self:AddTransition("Running", "Rescue", "Rescuing")
self:AddTransition("Running", "RTB", "Returning") self:AddTransition("Running", "RTB", "Returning")
self:AddTransition("Rescuing", "RTB", "Returning") self:AddTransition("Rescuing", "RTB", "Returning")
self:AddTransition("*", "Run", "Running") self:AddTransition("Returning", "Returned", "Returned")
self:AddTransition("Running", "Run", "Running")
self:AddTransition("Returned", "Run", "Running")
self:AddTransition("*", "Status", "*") self:AddTransition("*", "Status", "*")
self:AddTransition("*", "Stop", "Stopped") self:AddTransition("*", "Stop", "Stopped")
@ -376,6 +386,25 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #string To To state. -- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase to return to. Default is the home base. -- @param Wrapper.Airbase#AIRBASE airbase The airbase to return to. Default is the home base.
--- Triggers the FSM event "Returned" after the helo has landed.
-- @function [parent=#RESCUEHELO] Returned
-- @param #RESCUEHELO self
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the helo has landed.
--- Triggers the delayed FSM event "Returned" after the helo has landed.
-- @function [parent=#RESCUEHELO] __Returned
-- @param #RESCUEHELO self
-- @param #number delay Delay in seconds.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the helo has landed.
--- On after "Returned" event user function. Called when a the the helo has landed at an airbase.
-- @function [parent=#RESCUEHELO] OnAfterReturned
-- @param #RESCUEHELO self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the helo has landed.
--- Triggers the FSM event "Run". --- Triggers the FSM event "Run".
-- @function [parent=#RESCUEHELO] Run -- @function [parent=#RESCUEHELO] Run
@ -599,6 +628,15 @@ function RESCUEHELO:SetRespawnInAir()
return self return self
end end
--- Set modex (tail number) of the helo.
-- @param #RESCUEHELO self
-- @param #number modex Tail number.
-- @return #RESCUEHELO self
function RESCUEHELO:SetModex(modex)
self.modex=modex
return self
end
--- Use an uncontrolled aircraft already present in the mission rather than spawning a new helo as initial rescue helo. --- Use an uncontrolled aircraft already present in the mission rather than spawning a new helo as initial rescue helo.
-- This can be useful when interfaced with, e.g., a warehouse. -- This can be useful when interfaced with, e.g., a warehouse.
-- The group name is the one specified in the @{#RESCUEHELO.New} function. -- The group name is the one specified in the @{#RESCUEHELO.New} function.
@ -694,44 +732,24 @@ function RESCUEHELO:OnEventLand(EventData)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
-- Helo has rescued someone.
-- TODO: Add "Rescued" event.
if self:IsRescuing() then if self:IsRescuing() then
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.", groupname)) self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.", groupname))
end end
-- Check if takeoff air or respawn in air is set. Landing event should not happen unless the helo was on a rescue mission. -- Check if takeoff air or respawn in air is set. Landing event should not happen unless the helo was on a rescue mission.
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
if self:IsRescuing() then if not self:IsRescuing() then
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.", groupname)) self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.", groupname))
-- Respawn helo at current airbase.
SCHEDULER:New(nil, group.RespawnAtCurrentAirbase, {group}, 3)
else
self:T2(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true unless a rescue operation finished.", groupname))
-- Respawn helo at current airbase anyway.
if self.respawn then
SCHEDULER:New(nil, group.RespawnAtCurrentAirbase, {group}, 3)
end
end end
else
-- Respawn helo at current airbase.
if self.respawn then
SCHEDULER:New(nil, group.RespawnAtCurrentAirbase, {group}, 3)
end
end end
-- Restart the formation. -- Trigger returned event. Respawn at current airbase.
self:__Run(10) self:__Returned(3, EventData.Place)
end end
end end
@ -789,6 +807,11 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
-- Stop FSM. -- Stop FSM.
self:Stop() self:Stop()
-- Restart.
if self.respawn then
self:__Start(5)
end
end end
end end
@ -813,7 +836,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- Handle events. -- Handle events.
--self:HandleEvent(EVENTS.Birth) --self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Land) self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject) self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject)
self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject) self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject)
-- Delay before formation is started. -- Delay before formation is started.
@ -822,6 +845,9 @@ function RESCUEHELO:onafterStart(From, Event, To)
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
-- Set modex for spawn.
Spawn:InitModex(self.modex)
-- Spawn in air or at airbase. -- Spawn in air or at airbase.
if self.takeoff==SPAWN.Takeoff.Air then if self.takeoff==SPAWN.Takeoff.Air then
@ -920,11 +946,14 @@ function RESCUEHELO:onafterStatus(From, Event, To)
-- HELO is ALIVE -- -- HELO is ALIVE --
------------------- -------------------
-- Get relative fuel wrt to initial fuel of helo (DCS bug https://forums.eagle.ru/showthread.php?t=223712) -- Get (relative) fuel wrt to initial fuel of helo (DCS bug https://forums.eagle.ru/showthread.php?t=223712)
local fuel=self.helo:GetFuel()/self.HeloFuel0*100 local fuel=self.helo:GetFuel()*100
local fuelrel=fuel/self.HeloFuel0
local life=self.helo:GetUnit(1):GetLife()
local life0=self.helo:GetUnit(1):GetLife0()
-- 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, rel.fuel=%.1f, life=%.1f/%.1f", self.helo:GetName(), self:GetState(), fuel, fuelrel, life, life0)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
@ -939,9 +968,16 @@ function RESCUEHELO:onafterStatus(From, Event, To)
-- Check if respawn is enabled. -- Check if respawn is enabled.
if self.respawn then if self.respawn then
-- Set modex for respawn.
self.helo:InitModex(self.modex)
-- Respawn helo in air. -- Respawn helo in air.
self.helo=self.helo:Respawn(nil, true) self.helo=self.helo:Respawn(nil, true)
-- XXX: ATTENTION: if helo automatically RTBs on low fuel, it goes a bit cazy. The formation is not stopped and he partially dives into the water.
-- Also trying to find a ship to land on he flies right through it.
--self.helo:OptionRTBBingoFuel(false)
end end
else else
@ -955,15 +991,7 @@ function RESCUEHELO:onafterStatus(From, Event, To)
elseif self:IsRescuing() then elseif self:IsRescuing() then
if self.rtb then -- Helo is on a rescue mission.
-- Send helo back to base.
--self:RTB()
-- Switch to false.
self.rtb=false
end
end end
@ -972,25 +1000,28 @@ function RESCUEHELO:onafterStatus(From, Event, To)
self:__Status(-30) self:__Status(-30)
end end
else else
------------------ ------------------
-- HELO is DEAD -- -- HELO is DEAD --
------------------ ------------------
-- Stop FSM. if not self:IsStopped() then
self:Stop()
-- Stop FSM.
self:Stop()
-- Restart FSM after 5 seconds.
if self.respawn then
self:__Start(5)
end
-- Restart FSM after 5 seconds.
if self.respawn then
self:__Start(5)
end end
end end
end end
--- On after "Run" event. FSM will go to "Running" state. If formation is topped, it will be started again. --- On after "Run" event. FSM will go to "Running" state. If formation is stopped, it will be started again.
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
@ -1037,7 +1068,7 @@ function RESCUEHELO:_TaskRTB()
-- Task script. -- Task script.
local DCSScript = {} 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 mycarrier = UNIT:FindByName(\"%s\") ', carriername) -- The carrier unit that holds the self object.
DCSScript[#DCSScript+1] = string.format('local myhelo = mycarrier:GetState(mycarrier, \"RESCUEHELO_%d\") ', self.uid) -- Get the RECOVERYTANKER self object. DCSScript[#DCSScript+1] = string.format('local myhelo = mycarrier:GetState(mycarrier, \"RESCUEHELO_%d\") ', self.uid) -- Get the RESCUEHELO self object.
DCSScript[#DCSScript+1] = string.format('myhelo:RTB()') -- Call the function, e.g. myhelo.(self) DCSScript[#DCSScript+1] = string.format('myhelo:RTB()') -- Call the function, e.g. myhelo.(self)
-- Create task. -- Create task.
@ -1155,16 +1186,58 @@ function RESCUEHELO:onafterRTB(From, Event, To, airbase)
self:RouteRTB(airbase) self:RouteRTB(airbase)
end end
--- On after Stop event. Unhandle events and stop status updates. --- On after Returned event. Helo has landed.
-- @param #RESCUEHELO self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The base to which the helo has returned.
function RESCUEHELO:onafterReturned(From, Event, To, airbase)
if airbase then
local airbasename=airbase:GetName()
self:T(self.lid..string.format("Helo returned to airbase %s", tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!"))
end
-- Respawn helo at current airbase.
if self.respawn then
-- Set modex for respawn.
self.helo:InitModex(self.modex)
-- Respawn helo at current airbase.
self.helo:RespawnAtCurrentAirbase()
-- Restart the formation.
self:__Run(10)
end
end
--- On after Stop event. Unhandle events and stop status updates. If helo is alive, it is despawned.
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function RESCUEHELO:onafterStop(From, Event, To) function RESCUEHELO:onafterStop(From, Event, To)
-- Stop formation
self.formation:Stop() self.formation:Stop()
-- Unhandle events.
self:UnHandleEvent(EVENTS.Land) self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Ejection)
-- If helo is alive, despawn it.
if self.helo and self.helo:IsAlive() then
self:I(self.lid.."Stopping FSM and despawning helo.")
self.helo:Destroy()
else
self:I(self.lid.."Stopping FSM. Helo was not alive.")
end
end end
@ -1196,6 +1269,9 @@ function RESCUEHELO:RouteRTB(RTBAirbase, Speed)
-- Set route points. -- Set route points.
Template.route.points=Points Template.route.points=Points
-- Set modex for respawn.
self.helo:InitModex(self.modex)
-- Respawn the group. -- Respawn the group.
self.helo=self.helo:Respawn(Template, true) self.helo=self.helo:Respawn(Template, true)

View File

@ -890,4 +890,19 @@ function UTILS.GetMagneticDeclination(map)
return declination return declination
end end
--- Checks if a file exists or not. This requires **io** to be desanitized.
-- @param #string file File that should be checked.
-- @return #boolean True if the file exists, false if the file does not exist or nil if the io module is not available and the check could not be performed.
function UTILS.FileExists(file)
if io then
local f=io.open(file, "r")
if f~=nil then
io.close(f)
return true
else
return false
end
else
return nil
end
end

View File

@ -657,7 +657,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
if r1 and r2 then if r1 and r2 then
local safedist=(r1+r2)*1.1 local safedist=(r1+r2)*1.1
local safe = (dist > safedist) local safe = (dist > safedist)
self:E(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe))) self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe)))
return safe return safe
else else
return true return true
@ -710,7 +710,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then
-- DCS getParking() routine returned that spot is not free. -- DCS getParking() routine returned that spot is not free.
self:E(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC)))
else else
@ -790,6 +790,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) table.insert(validspots, {Coordinate=_spot, TerminalID=_termid})
end end
nvalid=nvalid+1 nvalid=nvalid+1
self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots))
end end
end -- loop over units end -- loop over units

View File

@ -400,7 +400,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
local Controller = self:_GetController() local Controller = self:_GetController()
--self:I( "Before SetTask" ) --self:I( "Before SetTask" )
Controller:setTask( DCSTask ) Controller:setTask( DCSTask )
self:I( { ControllableName = self:GetName(), DCSTask = DCSTask } ) -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace.
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
else else
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end end
@ -408,7 +409,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
if not WaitTime or WaitTime == 0 then if not WaitTime or WaitTime == 0 then
SetTask( self, DCSTask ) SetTask( self, DCSTask )
self:I( { ControllableName = self:GetName(), DCSTask = DCSTask } ) -- See above.
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
else else
self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime ) self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime )
end end

View File

@ -1457,11 +1457,16 @@ end
--- Sets the radio comms on or off when the group is respawned. Same as checking/unchecking the COMM box in the mission editor. --- Sets the radio comms on or off when the group is respawned. Same as checking/unchecking the COMM box in the mission editor.
-- @param #GROUP self -- @param #GROUP self
-- @param #number switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group. -- @param #boolean switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group.
-- @return #GROUP self -- @return #GROUP self
function GROUP:InitRadioCommsOnOff(switch) function GROUP:InitRadioCommsOnOff(switch)
self:F({switch=switch} ) self:F({switch=switch})
self.InitRespawnRadio=switch or true if switch==true or switch==nil then
self.InitRespawnRadio=true
else
self.InitRespawnRadio=false
end
return self
end end
--- Sets the radio frequency of the group when it is respawned. --- Sets the radio frequency of the group when it is respawned.
@ -1469,7 +1474,7 @@ end
-- @param #number frequency The frequency in MHz. -- @param #number frequency The frequency in MHz.
-- @return #GROUP self -- @return #GROUP self
function GROUP:InitRadioFrequency(frequency) function GROUP:InitRadioFrequency(frequency)
self:F({frequency=frequency} ) self:F({frequency=frequency})
self.InitRespawnFreq=frequency self.InitRespawnFreq=frequency
@ -1490,6 +1495,17 @@ function GROUP:InitRadioModulation(modulation)
return self return self
end end
--- Sets the modex (tail number) of the first unit of the group. If more units are in the group, the number is increased with every unit.
-- @param #GROUP self
-- @param #string modex Tail number of the first unit.
-- @return #GROUP self
function GROUP:InitModex(modex)
self:F({modex=modex})
if modex then
self.InitRespawnModex=tonumber(modex)
end
return self
end
--- Respawn the @{Wrapper.Group} at a @{Point}. --- Respawn the @{Wrapper.Group} at a @{Point}.
-- The method will setup the new group template according the Init(Respawn) settings provided for the group. -- The method will setup the new group template according the Init(Respawn) settings provided for the group.
@ -1641,6 +1657,13 @@ function GROUP:Respawn( Template, Reset )
end end
-- Set tail number.
if self.InitRespawnModex then
for UnitID=1,#Template.units do
Template.units[UnitID].onboard_num=string.format("%03d", self.InitRespawnModex+(UnitID-1))
end
end
-- Set radio frequency and modulation. -- Set radio frequency and modulation.
if self.InitRespawnRadio then if self.InitRespawnRadio then
Template.communication=self.InitRespawnRadio Template.communication=self.InitRespawnRadio