mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Merge pull request #1112 from FlightControl-Master/FF/Develop
Carrier Ops
This commit is contained in:
commit
264e84649e
@ -7,7 +7,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air
|
||||
-- @image AI_Air_Operations.JPG
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- @type AI_AIR
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
@ -325,6 +325,7 @@ function SPAWN:New( SpawnTemplatePrefix )
|
||||
self.SpawnInitFreq = nil -- No special frequency.
|
||||
self.SpawnInitModu = nil -- No special modulation.
|
||||
self.SpawnInitRadio = nil -- No radio comms setting.
|
||||
self.SpawnInitModex = nil
|
||||
|
||||
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
|
||||
else
|
||||
@ -376,6 +377,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
|
||||
self.SpawnInitFreq = nil -- No special frequency.
|
||||
self.SpawnInitModu = nil -- No special modulation.
|
||||
self.SpawnInitRadio = nil -- No radio comms setting.
|
||||
self.SpawnInitModex = nil
|
||||
|
||||
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
|
||||
else
|
||||
@ -430,6 +432,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
|
||||
self.SpawnInitFreq = nil -- No special frequency.
|
||||
self.SpawnInitModu = nil -- No special modulation.
|
||||
self.SpawnInitRadio = nil -- No radio comms setting.
|
||||
self.SpawnInitModex = nil
|
||||
|
||||
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
|
||||
else
|
||||
@ -642,6 +645,19 @@ function SPAWN:InitRadioModulation(modulation)
|
||||
return self
|
||||
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.
|
||||
-- @param #SPAWN self
|
||||
@ -1219,6 +1235,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
|
||||
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.
|
||||
if self.SpawnInitRadio then
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ## 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.
|
||||
-- * Results of all bombing and strafing runs are stored and top 10 results can be displayed.
|
||||
-- * 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 strafePlayerResults Table containing the strafing 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 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 #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.
|
||||
@ -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 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 defaultsmokebomb If true, initialize player settings to smoke bomb.
|
||||
-- @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.
|
||||
-- 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.
|
||||
-- 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**
|
||||
--
|
||||
-- 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,
|
||||
-- there should be an "On the Range" menu items in the "F10. Other..." menu.
|
||||
--
|
||||
-- ## 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.
|
||||
--
|
||||
@ -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.
|
||||
-- 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.
|
||||
-- * 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!
|
||||
--
|
||||
-- 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.
|
||||
-- * "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.
|
||||
-- * "Weather Report": Temperatur, wind and QFE pressure information is provided.
|
||||
-- * "Weather Report": Temperature, wind and QFE pressure information is provided.
|
||||
--
|
||||
-- ## Examples
|
||||
--
|
||||
@ -243,6 +244,7 @@ RANGE={
|
||||
trackbombs=true,
|
||||
trackrockets=true,
|
||||
trackmissiles=true,
|
||||
defaultsmokebomb=true,
|
||||
}
|
||||
|
||||
--- Default range parameters.
|
||||
@ -266,19 +268,25 @@ RANGE.Defaults={
|
||||
-- @field #table Names
|
||||
RANGE.Names={}
|
||||
|
||||
--- Main radio menu.
|
||||
-- @field #table MenuF10
|
||||
--- Main radio menu on group level.
|
||||
-- @field #table MenuF10 Root menu table on group level.
|
||||
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.
|
||||
-- @field #string id
|
||||
RANGE.id="RANGE | "
|
||||
|
||||
--- Range script version.
|
||||
-- @field #string version
|
||||
RANGE.version="1.2.3"
|
||||
RANGE.version="1.2.4"
|
||||
|
||||
--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: Check if units are still alive.
|
||||
--DONE: Add statics for strafe pits.
|
||||
@ -311,99 +319,111 @@ function RANGE:New(rangename)
|
||||
self:E(RANGE.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
|
||||
-- Defaults
|
||||
self:SetDefaultPlayerSmokeBomb()
|
||||
|
||||
-- Return object.
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initializes number of targets and location of the range. Starts the event handlers.
|
||||
-- @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()
|
||||
|
||||
-- Location/coordinate of range.
|
||||
local _location=nil
|
||||
if delay and delay>0 then
|
||||
SCHEDULER:New(nil, self.Start, {self}, delay)
|
||||
else
|
||||
|
||||
-- Count bomb targets.
|
||||
local _count=0
|
||||
for _,_target in pairs(self.bombingTargets) do
|
||||
_count=_count+1
|
||||
-- Location/coordinate of range.
|
||||
local _location=nil
|
||||
|
||||
-- Get range location.
|
||||
if _location==nil then
|
||||
_location=_target.target:GetCoordinate() --Core.Point#COORDINATE
|
||||
end
|
||||
end
|
||||
self.nbombtargets=_count
|
||||
-- Count bomb targets.
|
||||
local _count=0
|
||||
for _,_target in pairs(self.bombingTargets) do
|
||||
_count=_count+1
|
||||
|
||||
-- Count strafing targets.
|
||||
_count=0
|
||||
for _,_target in pairs(self.strafeTargets) do
|
||||
_count=_count+1
|
||||
|
||||
for _,_unit in pairs(_target.targets) do
|
||||
-- Get range location.
|
||||
if _location==nil then
|
||||
_location=_unit:GetCoordinate()
|
||||
_location=_target.target:GetCoordinate() --Core.Point#COORDINATE
|
||||
end
|
||||
end
|
||||
end
|
||||
self.nstrafetargets=_count
|
||||
self.nbombtargets=_count
|
||||
|
||||
-- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user.
|
||||
if self.location==nil then
|
||||
self.location=_location
|
||||
end
|
||||
-- Count strafing targets.
|
||||
_count=0
|
||||
for _,_target in pairs(self.strafeTargets) do
|
||||
_count=_count+1
|
||||
|
||||
if self.location==nil then
|
||||
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)
|
||||
return
|
||||
end
|
||||
for _,_unit in pairs(_target.targets) do
|
||||
if _location==nil then
|
||||
_location=_unit:GetCoordinate()
|
||||
end
|
||||
end
|
||||
end
|
||||
self.nstrafetargets=_count
|
||||
|
||||
-- Define a MOOSE zone of the range.
|
||||
if self.rangezone==nil then
|
||||
self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius)
|
||||
end
|
||||
-- Location of the range. We simply take the first unit/target we find if it was not explicitly specified by the user.
|
||||
if self.location==nil then
|
||||
self.location=_location
|
||||
end
|
||||
|
||||
-- Starting range.
|
||||
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
|
||||
self:E(RANGE.id..text)
|
||||
MESSAGE:New(text,10):ToAllIf(self.Debug)
|
||||
if self.location==nil then
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
-- Event handling.
|
||||
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
|
||||
-- Define a MOOSE zone of the range.
|
||||
if self.rangezone==nil then
|
||||
self.rangezone=ZONE_RADIUS:New(self.rangename, {x=self.location.x, y=self.location.z}, self.rangeradius)
|
||||
end
|
||||
|
||||
-- Make bomb target move randomly within the range zone.
|
||||
for _,_target in pairs(self.bombingTargets) do
|
||||
-- Starting range.
|
||||
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.
|
||||
local _static=self:_CheckStatic(_target.target:GetName())
|
||||
-- Event handling.
|
||||
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
|
||||
local unit=_target.target --Wrapper.Unit#UNIT
|
||||
_target.target:PatrolZones({self.rangezone}, _target.speed*0.75, "Off road")
|
||||
-- Make bomb target move randomly within the range zone.
|
||||
for _,_target in pairs(self.bombingTargets) do
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
return self
|
||||
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.
|
||||
-- @param #RANGE self
|
||||
-- @param #number maxalt Maximum altitude AGL in meters. Default is 914 m= 3000 ft.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetMaxStrafeAlt(maxalt)
|
||||
self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time interval for tracking bombs. A smaller time step increases accuracy but needs more CPU time.
|
||||
-- @param #RANGE self
|
||||
-- @param #number dt Time interval in seconds. Default is 0.005 s.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetBombtrackTimestep(dt)
|
||||
self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time how long (most) messages are displayed.
|
||||
-- @param #RANGE self
|
||||
-- @param #number time Time in seconds. Default is 30 s.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetMessageTimeDuration(time)
|
||||
self.Tmsg=time or RANGE.Defaults.Tmsg
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set messages to examiner. The examiner will receive messages from all clients.
|
||||
-- @param #RANGE self
|
||||
-- @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.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetMessageToExaminer(examinergroupname, exclusively)
|
||||
self.examinergroupname=examinergroupname
|
||||
self.examinerexclusive=exclusively
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set max number of player results that are displayed.
|
||||
-- @param #RANGE self
|
||||
-- @param #number nmax Number of results. Default is 10.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetDisplayedMaxPlayerResults(nmax)
|
||||
self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set range radius. Defines the area in which e.g. bomb impacts are smoked.
|
||||
-- @param #RANGE self
|
||||
-- @param #number radius Radius in km. Default 5 km.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetRangeRadius(radius)
|
||||
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
|
||||
|
||||
--- 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 #number distance Threshold distance in km. Default 25 km.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetBombtrackThreshold(distance)
|
||||
self.BombtrackThreshold=distance*1000 or 25*1000
|
||||
return self
|
||||
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.
|
||||
-- The range location determines the position at which the weather data is evaluated.
|
||||
-- @param #RANGE self
|
||||
-- @param Core.Point#COORDINATE coordinate Coordinate of the range.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetRangeLocation(coordinate)
|
||||
self.location=coordinate
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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.
|
||||
-- @param #RANGE self
|
||||
-- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetRangeZone(zone)
|
||||
self.rangezone=zone
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
|
||||
-- @param #RANGE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetBombTargetSmokeColor(colorid)
|
||||
self.BombSmokeColor=colorid or SMOKECOLOR.Red
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke.
|
||||
-- @param #RANGE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetStrafeTargetSmokeColor(colorid)
|
||||
self.StrafeSmokeColor=colorid or SMOKECOLOR.Green
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke.
|
||||
-- @param #RANGE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetStrafePitSmokeColor(colorid)
|
||||
self.StrafePitSmokeColor=colorid or SMOKECOLOR.White
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time delay between bomb impact and starting to smoke the impact point.
|
||||
-- @param #RANGE self
|
||||
-- @param #number delay Time delay in seconds. Default is 3 seconds.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetSmokeTimeDelay(delay)
|
||||
self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enable debug modus.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:DebugON()
|
||||
self.Debug=true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disable debug modus.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:DebugOFF()
|
||||
self.Debug=false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enables tracking of all bomb types. Note that this is the default setting.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackBombsON()
|
||||
self.trackbombs=true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disables tracking of all bomb types.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackBombsOFF()
|
||||
self.trackbombs=false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enables tracking of all rocket types. Note that this is the default setting.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackRocketsON()
|
||||
self.trackrockets=true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disables tracking of all rocket types.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackRocketsOFF()
|
||||
self.trackrockets=false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enables tracking of all missile types. Note that this is the default setting.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackMissilesON()
|
||||
self.trackmissiles=true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disables tracking of all missile types.
|
||||
-- @param #RANGE self
|
||||
-- @return #RANGE self
|
||||
function RANGE:TrackMissilesOFF()
|
||||
self.trackmissiles=false
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@ -564,6 +639,7 @@ end
|
||||
-- @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 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)
|
||||
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)
|
||||
self:T(RANGE.id..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@ -696,6 +774,7 @@ end
|
||||
-- @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 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)
|
||||
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)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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 #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.
|
||||
-- @return #RANGE self
|
||||
function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
|
||||
self:F({targetnames=targetnames, goodhitrange=goodhitrange, randommove=randommove})
|
||||
|
||||
@ -757,6 +838,8 @@ function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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 #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.
|
||||
-- @return #RANGE self
|
||||
function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
|
||||
self:F({unit=unit, goodhitrange=goodhitrange, randommove=randommove})
|
||||
|
||||
@ -798,6 +882,8 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
|
||||
|
||||
-- Insert target to table.
|
||||
table.insert(self.bombingTargets, {name=name, target=unit, goodhitrange=goodhitrange, move=randommove, speed=speed})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add all units of a group as bombing targets.
|
||||
@ -805,6 +891,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP group Group of bombing targets.
|
||||
-- @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.
|
||||
-- @return #RANGE self
|
||||
function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
|
||||
self:F({group=group, goodhitrange=goodhitrange, randommove=randommove})
|
||||
|
||||
@ -819,6 +906,7 @@ function RANGE:AddBombingTargetGroup(group, goodhitrange, randommove)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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.
|
||||
self.PlayerSettings[_playername]={}
|
||||
self.PlayerSettings[_playername].smokebombimpact=true
|
||||
self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb
|
||||
self.PlayerSettings[_playername].flaredirecthits=false
|
||||
self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue
|
||||
self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red
|
||||
self.PlayerSettings[_playername].delaysmoke=true
|
||||
self.PlayerSettings[_playername].messages=true
|
||||
|
||||
-- Start check in zone timer.
|
||||
if self.planes[_uid] ~= true then
|
||||
@ -1042,7 +1131,7 @@ function RANGE:OnEventHit(EventData)
|
||||
if _currentTarget.pastfoulline==false and _unit and _playername then
|
||||
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)
|
||||
self:_DisplayMessageToGroup(_unit, text, 10)
|
||||
self:_DisplayMessageToGroup(_unit, text)
|
||||
self:T2(RANGE.id..text)
|
||||
_currentTarget.pastfoulline=true
|
||||
end
|
||||
@ -1320,7 +1409,7 @@ function RANGE:_DisplayMyStrafePitResults(_unitName)
|
||||
end
|
||||
|
||||
-- Send message to group.
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1376,7 +1465,7 @@ function RANGE:_DisplayStrafePitResults(_unitName)
|
||||
end
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1433,7 +1522,7 @@ function RANGE:_DisplayMyBombingResults(_unitName)
|
||||
end
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1489,7 +1578,7 @@ function RANGE:_DisplayBombingResults(_unitName)
|
||||
end
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit, _message, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1566,7 +1655,7 @@ function RANGE:_DisplayRangeInfo(_unitname)
|
||||
text=text..textdelay
|
||||
|
||||
-- Send message to player group.
|
||||
self:_DisplayMessageToGroup(unit, text, nil, true)
|
||||
self:_DisplayMessageToGroup(unit, text, nil, true, true)
|
||||
|
||||
-- Debug output.
|
||||
self:T2(RANGE.id..text)
|
||||
@ -1603,7 +1692,7 @@ function RANGE:_DisplayBombTargets(_unitname)
|
||||
end
|
||||
end
|
||||
|
||||
self:_DisplayMessageToGroup(_unit,_text, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1643,7 +1732,7 @@ function RANGE:_DisplayStrafePits(_unitname)
|
||||
_text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading)
|
||||
end
|
||||
|
||||
self:_DisplayMessageToGroup(_unit,_text, nil, true)
|
||||
self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1705,7 +1794,7 @@ function RANGE:_DisplayRangeWeather(_unitname)
|
||||
end
|
||||
|
||||
-- Send message to player group.
|
||||
self:_DisplayMessageToGroup(unit, text, nil, true)
|
||||
self:_DisplayMessageToGroup(unit, text, nil, true, true)
|
||||
|
||||
-- Debug output.
|
||||
self:T2(RANGE.id..text)
|
||||
@ -1749,7 +1838,7 @@ function RANGE:_CheckInZone(_unitName)
|
||||
local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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.
|
||||
self.MenuAddedTo[_gid] = true
|
||||
|
||||
-- Main F10 menu: F10/On the Range/<Range Name>/
|
||||
if RANGE.MenuF10[_gid] == nil then
|
||||
RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range")
|
||||
-- Range root menu path.
|
||||
local _rangePath=nil
|
||||
|
||||
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
|
||||
local _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid])
|
||||
|
||||
|
||||
local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath)
|
||||
local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _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, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow)
|
||||
-- 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, "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
|
||||
missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, 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.bombPlayerResults[_playername] = nil
|
||||
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
|
||||
|
||||
@ -2124,33 +2236,35 @@ end
|
||||
-- @param #string _text Message text.
|
||||
-- @param #number _time Duration how long the message is displayed.
|
||||
-- @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})
|
||||
|
||||
-- Defaults
|
||||
_time=_time or self.Tmsg
|
||||
if _clear==nil then
|
||||
if _clear==nil or _clear==false then
|
||||
_clear=false
|
||||
else
|
||||
_clear=true
|
||||
end
|
||||
|
||||
-- Group ID.
|
||||
local _gid=_unit:GetGroup():GetID()
|
||||
|
||||
if _gid and not self.examinerexclusive then
|
||||
if _clear == true then
|
||||
trigger.action.outTextForGroup(_gid, _text, _time, _clear)
|
||||
else
|
||||
trigger.action.outTextForGroup(_gid, _text, _time)
|
||||
end
|
||||
-- Get playername and player settings
|
||||
local _, playername=self:_GetPlayerUnitAndName(_unit:GetName())
|
||||
local playermessage=self.PlayerSettings[playername].messages
|
||||
|
||||
-- Send message to player if messages enabled and not only for the examiner.
|
||||
if _gid and (playermessage==true or display) and (not self.examinerexclusive) then
|
||||
trigger.action.outTextForGroup(_gid, _text, _time, _clear)
|
||||
end
|
||||
|
||||
-- Send message to examiner.
|
||||
if self.examinergroupname~=nil then
|
||||
local _examinerid=GROUP:FindByName(self.examinergroupname):GetID()
|
||||
if _examinerid then
|
||||
if _clear == true then
|
||||
trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
|
||||
else
|
||||
trigger.action.outTextForGroup(_examinerid, _text, _time)
|
||||
end
|
||||
trigger.action.outTextForGroup(_examinerid, _text, _time, _clear)
|
||||
end
|
||||
end
|
||||
|
||||
@ -2172,7 +2286,7 @@ function RANGE:_SmokeBombImpactOnOff(unitname)
|
||||
self.PlayerSettigs[playername].smokebombimpact=true
|
||||
text=string.format("%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername)
|
||||
end
|
||||
self:_DisplayMessageToGroup(unit, text, 5)
|
||||
self:_DisplayMessageToGroup(unit, text, 5, false, true)
|
||||
end
|
||||
|
||||
end
|
||||
@ -2193,7 +2307,27 @@ function RANGE:_SmokeBombDelayOnOff(unitname)
|
||||
self.PlayerSettigs[playername].delaysmoke=true
|
||||
text=string.format("%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername)
|
||||
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
|
||||
@ -2214,7 +2348,7 @@ function RANGE:_FlareDirectHitsOnOff(unitname)
|
||||
self.PlayerSettings[playername].flaredirecthits=true
|
||||
text=string.format("%s, %s, flaring direct hits is now ON.", self.rangename, playername)
|
||||
end
|
||||
self:_DisplayMessageToGroup(unit, text, 5)
|
||||
self:_DisplayMessageToGroup(unit, text, 5, false, true)
|
||||
end
|
||||
|
||||
end
|
||||
@ -2332,7 +2466,7 @@ function RANGE:_smokecolor2text(color)
|
||||
elseif color==SMOKECOLOR.White then
|
||||
txt="white"
|
||||
else
|
||||
txt=string.format("unkown color (%s)", tostring(color))
|
||||
txt=string.format("unknown color (%s)", tostring(color))
|
||||
end
|
||||
|
||||
return txt
|
||||
@ -2355,7 +2489,7 @@ function RANGE:_flarecolor2text(color)
|
||||
elseif color==FLARECOLOR.Yellow then
|
||||
txt="yellow"
|
||||
else
|
||||
txt=string.format("unkown color (%s)", tostring(color))
|
||||
txt=string.format("unknown color (%s)", tostring(color))
|
||||
end
|
||||
|
||||
return txt
|
||||
|
||||
@ -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.
|
||||
-- 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
|
||||
--
|
||||
-- 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.
|
||||
-- * 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?
|
||||
--
|
||||
-- * 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 #boolean menusingle If true, menu is optimized for a single carrier.
|
||||
-- @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
|
||||
|
||||
--- 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
|
||||
-- can be called.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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
|
||||
-- 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
|
||||
--
|
||||
@ -274,9 +289,17 @@
|
||||
-- ### 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
|
||||
-- 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).
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
-- However, this is only possible when the Airboss has a nice day - see @{#AIRBOSS.SetAirbossNiceGuy}.
|
||||
--
|
||||
-- ### Request Refueling
|
||||
--
|
||||
@ -441,7 +464,7 @@
|
||||
-- * **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.
|
||||
-- * **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.
|
||||
--
|
||||
@ -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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- For example as
|
||||
--
|
||||
-- airbossStennis:SetSoundfilesFolder("Airboss Soundfiles/")
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # AI Handling
|
||||
--
|
||||
-- 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,
|
||||
menusingle = nil,
|
||||
collisiondist = nil,
|
||||
Tmessage = nil,
|
||||
soundfolder = nil,
|
||||
}
|
||||
|
||||
--- Player aircraft types capable of landing on carriers.
|
||||
@ -1520,13 +1566,17 @@ AIRBOSS.Difficulty={
|
||||
-- @field #boolean subtitles If true, display subtitles of radio messages.
|
||||
-- @extends #AIRBOSS.FlightGroup
|
||||
|
||||
--- Main radio menu: F10 Other/Airboss
|
||||
--- Main group level radio menu: F10 Other/Airboss.
|
||||
-- @field #table MenuF10
|
||||
AIRBOSS.MenuF10={}
|
||||
|
||||
--- Airboss mission level F10 root menu.
|
||||
-- @field #table MenuF10Root
|
||||
AIRBOSS.MenuF10Root=nil
|
||||
|
||||
--- Airboss class version.
|
||||
-- @field #string version
|
||||
AIRBOSS.version="0.9.2"
|
||||
AIRBOSS.version="0.9.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -1707,6 +1757,7 @@ function AIRBOSS:New(carriername, alias)
|
||||
-- Set update time intervals.
|
||||
self:SetQueueUpdateTime()
|
||||
self:SetStatusUpdateTime()
|
||||
self:SetDefaultMessageDuration()
|
||||
|
||||
-- Menu options.
|
||||
self:SetMenuMarkZones()
|
||||
@ -2271,6 +2322,31 @@ function AIRBOSS:SetAirbossNiceGuy(switch)
|
||||
return self
|
||||
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.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number interval Time interval in seconds. Default 0.5 sec.
|
||||
@ -2280,13 +2356,22 @@ function AIRBOSS:SetStatusUpdateTime(interval)
|
||||
return self
|
||||
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.
|
||||
-- The post is 2.5 NM port of the carrier.
|
||||
-- @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
|
||||
function AIRBOSS:SetMarshalRadius(radius)
|
||||
self.marshalradius=UTILS.NMToMeters(radius or 2.75)
|
||||
self.marshalradius=UTILS.NMToMeters(radius or 2.8)
|
||||
return self
|
||||
end
|
||||
|
||||
@ -3940,7 +4025,7 @@ function AIRBOSS:_ClearForLanding(flight)
|
||||
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.
|
||||
self:MessageToMarshal(text, "MARSHAL", flight.onboard, 10, false, 2)
|
||||
self:MessageToMarshal(text, "MARSHAL", flight.onboard, nil, false, 2)
|
||||
|
||||
end
|
||||
|
||||
@ -4141,7 +4226,7 @@ function AIRBOSS:_WaitPlayer(playerData)
|
||||
end
|
||||
|
||||
-- Send message.
|
||||
self:MessageToMarshal(text, "AIRBOSS", playerData.onboard, 10)
|
||||
self:MessageToMarshal(text, "AIRBOSS", playerData.onboard)
|
||||
|
||||
-- Add player flight to waiting queue.
|
||||
table.insert(self.Qwaiting, playerData)
|
||||
@ -6141,7 +6226,7 @@ function AIRBOSS:_Waiting(playerData)
|
||||
-- Warning if player is inside the zone.
|
||||
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.")
|
||||
self:MessageToPlayer(playerData, text, "AIRBOSS", nil, 10)
|
||||
self:MessageToPlayer(playerData, text, "AIRBOSS")
|
||||
playerData.warning=true
|
||||
end
|
||||
|
||||
@ -6373,7 +6458,8 @@ function AIRBOSS:_Commencing(playerData, zonecheck)
|
||||
end
|
||||
|
||||
-- Message to player.
|
||||
self:MessageToPlayer(playerData, text, "MARSHAL", nil, 3)
|
||||
--self:MessageToPlayer(playerData, text, "MARSHAL", nil, 3)
|
||||
self:MessageToPlayer(playerData, text, "MARSHAL")
|
||||
end
|
||||
|
||||
-- Next step: depends on case recovery.
|
||||
@ -7482,7 +7568,7 @@ function AIRBOSS:_CheckFoulDeck(playerData)
|
||||
-- Player hint for flight students.
|
||||
if playerData.difficulty~=AIRBOSS.Difficulty.HARD then
|
||||
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
|
||||
|
||||
-- Set player parameters for foul deck
|
||||
@ -7495,7 +7581,7 @@ function AIRBOSS:_CheckFoulDeck(playerData)
|
||||
if foulunit then
|
||||
local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(), self.flights)
|
||||
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
|
||||
@ -9134,7 +9220,7 @@ function AIRBOSS:_AbortPattern(playerData, X, Z, posData, patternwo)
|
||||
self:T(self.lid..dtext)
|
||||
|
||||
-- Message to player.
|
||||
self:MessageToPlayer(playerData, text, "LSO", nil, 20)
|
||||
self:MessageToPlayer(playerData, text, "LSO")
|
||||
|
||||
if patternwo then
|
||||
|
||||
@ -9579,7 +9665,7 @@ function AIRBOSS:_Debrief(playerData)
|
||||
|
||||
-- Re-enter message.
|
||||
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
|
||||
|
||||
@ -9607,7 +9693,7 @@ function AIRBOSS:_Debrief(playerData)
|
||||
|
||||
-- Airboss talkto!
|
||||
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
|
||||
|
||||
@ -9630,7 +9716,7 @@ function AIRBOSS:_Debrief(playerData)
|
||||
|
||||
-- Airboss talkto!
|
||||
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
|
||||
|
||||
@ -9663,7 +9749,7 @@ function AIRBOSS:_Debrief(playerData)
|
||||
else
|
||||
|
||||
-- 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.
|
||||
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)
|
||||
|
||||
-- Send hint to player.
|
||||
self:MessageToPlayer(playerData, text, "AIRBOSS", "", 10, false, 1)
|
||||
self:MessageToPlayer(playerData, text, "AIRBOSS", "", nil, false, 1)
|
||||
|
||||
end
|
||||
|
||||
@ -10158,7 +10244,7 @@ function AIRBOSS:_CheckPatternUpdate()
|
||||
-- 99, new final bearing XXX
|
||||
local FB=self:GetFinalBearing(true)
|
||||
local text=string.format("new final bearing %03d°.", FB)
|
||||
self:MessageToMarshal(text, "AIRBOSS", "99", 10)
|
||||
self:MessageToMarshal(text, "AIRBOSS", "99")
|
||||
end
|
||||
|
||||
-- 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.
|
||||
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
|
||||
@ -10985,7 +11071,9 @@ function AIRBOSS:_RadioFilename(call, loud)
|
||||
-- Construct file name and subtitle.
|
||||
local prefix=call.file or ""
|
||||
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.
|
||||
if loud then
|
||||
@ -11014,7 +11102,7 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration
|
||||
if playerData and message and message~="" then
|
||||
|
||||
-- Default duration.
|
||||
duration=duration or 10
|
||||
duration=duration or self.Tmessage
|
||||
|
||||
-- Format message.
|
||||
local text
|
||||
@ -11070,9 +11158,6 @@ end
|
||||
-- @param #number delay Delay in seconds, before the message is displayed.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
|
||||
-- Duration of subtitle display.
|
||||
newcall.subduration=subduration or 10
|
||||
newcall.subduration=subduration or self.Tmessage
|
||||
|
||||
-- Tail number of the receiver.
|
||||
if self:_IsOnboard(modexreceiver) then
|
||||
@ -11238,7 +11320,8 @@ function AIRBOSS:_Number2Sound(playerData, sender, number, delay)
|
||||
local call=AIRBOSS[Sender][N] --#AIRBOSS.RadioCall
|
||||
|
||||
-- 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.
|
||||
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.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Main F10 menu: F10/Airboss/<Carrier Name>/
|
||||
if AIRBOSS.MenuF10[gid]==nil then
|
||||
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "Airboss")
|
||||
end
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if AIRBOSS.MenuF10Root then
|
||||
------------------------
|
||||
-- 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
|
||||
_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
|
||||
|
||||
|
||||
@ -12156,7 +12262,7 @@ function AIRBOSS:_DisplayQueue(_unitname, queue, qname)
|
||||
end
|
||||
|
||||
-- Send message.
|
||||
self:MessageToPlayer(playerData, text, nil, "", 10, true)
|
||||
self:MessageToPlayer(playerData, text, nil, "", nil, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -12472,7 +12578,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName)
|
||||
local fuel=playerData.unit:GetFuel()*100
|
||||
local fuelstate=self:_GetFuelState(playerData.unit)
|
||||
|
||||
---
|
||||
-- Number of units in group.
|
||||
local _,nunitsGround=self:_GetFlightUnits(playerData, true)
|
||||
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("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("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("Section Lead: %s (%d/%d)", tostring(playerData.seclead), #playerData.section+1, self.NmaxSection+1)
|
||||
for _,_sec in pairs(playerData.section) do
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
-- @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 callsignnumber Number of the callsign name.
|
||||
-- @field #string modex Tail number of the tanker.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Recovery Tanker.
|
||||
@ -292,6 +293,7 @@ RECOVERYTANKER = {
|
||||
awacs = nil,
|
||||
callsignname = nil,
|
||||
callsignnumber = nil,
|
||||
modex = nil,
|
||||
}
|
||||
|
||||
--- Unique ID (global).
|
||||
@ -300,7 +302,7 @@ RECOVERYTANKER.UID=0
|
||||
|
||||
--- Class version.
|
||||
-- @field #string version
|
||||
RECOVERYTANKER.version="1.0.5"
|
||||
RECOVERYTANKER.version="1.0.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- 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)
|
||||
|
||||
-- Log ID.
|
||||
self.lid=string.format("RECOVERYTANKER %s |", self.alias)
|
||||
self.lid=string.format("RECOVERYTANKER %s | ", self.alias)
|
||||
|
||||
-- Init default parameters.
|
||||
self:SetAltitude()
|
||||
@ -617,6 +619,15 @@ function RECOVERYTANKER:SetCallsign(callsignname, callsignnumber)
|
||||
return self
|
||||
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.
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number takeofftype Takeoff type.
|
||||
@ -810,8 +821,10 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.EngineShutdown)
|
||||
self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explcit functions sice OnEventRefueling and OnEventRefuelingStop did not hook.
|
||||
self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explicit functions since OnEventRefueling and OnEventRefuelingStop did not hook!
|
||||
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.
|
||||
local Spawn=SPAWN:NewWithAlias(self.tankergroupname, self.alias)
|
||||
@ -820,6 +833,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
Spawn:InitRadioCommsOnOff(true)
|
||||
Spawn:InitRadioFrequency(self.RadioFreq)
|
||||
Spawn:InitRadioModulation(self.RadioModu)
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
-- Spawn on carrier.
|
||||
if self.takeoff==SPAWN.Takeoff.Air then
|
||||
@ -935,6 +949,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
|
||||
self.tanker:InitRadioCommsOnOff(true)
|
||||
self.tanker:InitRadioFrequency(self.RadioFreq)
|
||||
self.tanker:InitRadioModulation(self.RadioModu)
|
||||
self.tanker:InitModex(self.modex)
|
||||
|
||||
-- Respawn tanker.
|
||||
self.tanker=self.tanker:Respawn(nil, true)
|
||||
@ -991,14 +1006,17 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
|
||||
-- TANKER is DEAD --
|
||||
--------------------
|
||||
|
||||
-- Stop FSM.
|
||||
self:Stop()
|
||||
if not self:IsStopped() then
|
||||
|
||||
-- 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
|
||||
@ -1103,9 +1121,22 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RECOVERYTANKER:onafterStop(From, Event, To)
|
||||
|
||||
-- Unhandle events.
|
||||
self:UnHandleEvent(EVENTS.EngineShutdown)
|
||||
self:UnHandleEvent(EVENTS.Refueling)
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -1139,6 +1170,7 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
|
||||
group:InitRadioCommsOnOff(true)
|
||||
group:InitRadioFrequency(self.RadioFreq)
|
||||
group:InitRadioModulation(self.RadioModu)
|
||||
group:InitModex(self.modex)
|
||||
|
||||
-- Respawn tanker.
|
||||
-- Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076
|
||||
@ -1221,6 +1253,37 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
|
||||
|
||||
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
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
-- * Multiple helos at different carriers due to object oriented approach.
|
||||
-- * 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**
|
||||
@ -50,6 +56,7 @@
|
||||
-- @field #number hid Unit ID of the helo group. (Global) Running number.
|
||||
-- @field #string alias Alias of the spawn group.
|
||||
-- @field #number uid Unique ID of this helo.
|
||||
-- @field #number modex Tail number of the helo.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Rescue Helo
|
||||
@ -219,6 +226,7 @@ RESCUEHELO = {
|
||||
carrierstop = nil,
|
||||
alias = nil,
|
||||
uid = 0,
|
||||
modex = nil,
|
||||
}
|
||||
|
||||
--- Unique ID (global).
|
||||
@ -227,7 +235,7 @@ RESCUEHELO.UID=0
|
||||
|
||||
--- Class version.
|
||||
-- @field #string version
|
||||
RESCUEHELO.version="1.0.3"
|
||||
RESCUEHELO.version="1.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- 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)
|
||||
|
||||
-- Log ID.
|
||||
self.lid=string.format("RESCUEHELO %s |", self.alias)
|
||||
self.lid=string.format("RESCUEHELO %s | ", self.alias)
|
||||
|
||||
-- Init defaults.
|
||||
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
|
||||
@ -317,12 +325,14 @@ function RESCUEHELO:New(carrierunit, helogroupname)
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running")
|
||||
self:AddTransition("Running", "Rescue", "Rescuing")
|
||||
self:AddTransition("Running", "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("*", "Stop", "Stopped")
|
||||
|
||||
@ -376,6 +386,25 @@ function RESCUEHELO:New(carrierunit, helogroupname)
|
||||
-- @param #string To To state.
|
||||
-- @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".
|
||||
-- @function [parent=#RESCUEHELO] Run
|
||||
@ -599,6 +628,15 @@ function RESCUEHELO:SetRespawnInAir()
|
||||
return self
|
||||
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.
|
||||
-- This can be useful when interfaced with, e.g., a warehouse.
|
||||
-- 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)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Helo has rescued someone.
|
||||
-- TODO: Add "Rescued" event.
|
||||
if self:IsRescuing() then
|
||||
|
||||
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.", groupname))
|
||||
|
||||
end
|
||||
|
||||
-- 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:IsRescuing() then
|
||||
if not self:IsRescuing() then
|
||||
|
||||
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.", 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
|
||||
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))
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Respawn helo at current airbase.
|
||||
if self.respawn then
|
||||
SCHEDULER:New(nil, group.RespawnAtCurrentAirbase, {group}, 3)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Restart the formation.
|
||||
self:__Run(10)
|
||||
-- Trigger returned event. Respawn at current airbase.
|
||||
self:__Returned(3, EventData.Place)
|
||||
|
||||
end
|
||||
end
|
||||
@ -789,6 +807,11 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
|
||||
-- Stop FSM.
|
||||
self:Stop()
|
||||
|
||||
-- Restart.
|
||||
if self.respawn then
|
||||
self:__Start(5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -813,7 +836,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
-- Handle events.
|
||||
--self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.Land)
|
||||
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject)
|
||||
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject)
|
||||
self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject)
|
||||
|
||||
-- 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.
|
||||
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
|
||||
-- Set modex for spawn.
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
-- Spawn in air or at airbase.
|
||||
if self.takeoff==SPAWN.Takeoff.Air then
|
||||
|
||||
@ -920,11 +946,14 @@ function RESCUEHELO:onafterStatus(From, Event, To)
|
||||
-- HELO is ALIVE --
|
||||
-------------------
|
||||
|
||||
-- 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
|
||||
-- Get (relative) fuel wrt to initial fuel of helo (DCS bug https://forums.eagle.ru/showthread.php?t=223712)
|
||||
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.
|
||||
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)
|
||||
self:T(self.lid..text)
|
||||
|
||||
@ -939,9 +968,16 @@ function RESCUEHELO:onafterStatus(From, Event, To)
|
||||
-- Check if respawn is enabled.
|
||||
if self.respawn then
|
||||
|
||||
-- Set modex for respawn.
|
||||
self.helo:InitModex(self.modex)
|
||||
|
||||
-- Respawn helo in air.
|
||||
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
|
||||
|
||||
else
|
||||
@ -955,15 +991,7 @@ function RESCUEHELO:onafterStatus(From, Event, To)
|
||||
|
||||
elseif self:IsRescuing() then
|
||||
|
||||
if self.rtb then
|
||||
|
||||
-- Send helo back to base.
|
||||
--self:RTB()
|
||||
|
||||
-- Switch to false.
|
||||
self.rtb=false
|
||||
|
||||
end
|
||||
-- Helo is on a rescue mission.
|
||||
|
||||
end
|
||||
|
||||
@ -972,25 +1000,28 @@ function RESCUEHELO:onafterStatus(From, Event, To)
|
||||
self:__Status(-30)
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
------------------
|
||||
-- HELO is DEAD --
|
||||
------------------
|
||||
|
||||
-- Stop FSM.
|
||||
self:Stop()
|
||||
if not self:IsStopped() then
|
||||
|
||||
-- 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
|
||||
|
||||
--- 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 #string From From state.
|
||||
-- @param #string Event Event.
|
||||
@ -1037,7 +1068,7 @@ function RESCUEHELO:_TaskRTB()
|
||||
-- Task script.
|
||||
local DCSScript = {}
|
||||
DCSScript[#DCSScript+1] = string.format('local mycarrier = UNIT:FindByName(\"%s\") ', carriername) -- The carrier unit that holds the self object.
|
||||
DCSScript[#DCSScript+1] = string.format('local 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)
|
||||
|
||||
-- Create task.
|
||||
@ -1155,16 +1186,58 @@ function RESCUEHELO:onafterRTB(From, Event, To, airbase)
|
||||
self:RouteRTB(airbase)
|
||||
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 #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RESCUEHELO:onafterStop(From, Event, To)
|
||||
|
||||
-- Stop formation
|
||||
self.formation:Stop()
|
||||
|
||||
-- Unhandle events.
|
||||
self:UnHandleEvent(EVENTS.Land)
|
||||
self:UnHandleEvent(EVENTS.Crash)
|
||||
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
|
||||
|
||||
|
||||
@ -1196,6 +1269,9 @@ function RESCUEHELO:RouteRTB(RTBAirbase, Speed)
|
||||
-- Set route points.
|
||||
Template.route.points=Points
|
||||
|
||||
-- Set modex for respawn.
|
||||
self.helo:InitModex(self.modex)
|
||||
|
||||
-- Respawn the group.
|
||||
self.helo=self.helo:Respawn(Template, true)
|
||||
|
||||
|
||||
@ -890,4 +890,19 @@ function UTILS.GetMagneticDeclination(map)
|
||||
return declination
|
||||
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
|
||||
|
||||
@ -657,7 +657,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
if r1 and r2 then
|
||||
local safedist=(r1+r2)*1.1
|
||||
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
|
||||
else
|
||||
return true
|
||||
@ -710,7 +710,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then
|
||||
|
||||
-- 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
|
||||
|
||||
@ -790,6 +790,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
table.insert(validspots, {Coordinate=_spot, TerminalID=_termid})
|
||||
end
|
||||
nvalid=nvalid+1
|
||||
self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots))
|
||||
end
|
||||
|
||||
end -- loop over units
|
||||
|
||||
@ -400,7 +400,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
|
||||
local Controller = self:_GetController()
|
||||
--self:I( "Before SetTask" )
|
||||
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
|
||||
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
|
||||
end
|
||||
@ -408,7 +409,8 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
|
||||
|
||||
if not WaitTime or WaitTime == 0 then
|
||||
SetTask( self, DCSTask )
|
||||
self:I( { ControllableName = self:GetName(), DCSTask = DCSTask } )
|
||||
-- See above.
|
||||
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
|
||||
else
|
||||
self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime )
|
||||
end
|
||||
|
||||
@ -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.
|
||||
-- @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
|
||||
function GROUP:InitRadioCommsOnOff(switch)
|
||||
self:F({switch=switch} )
|
||||
self.InitRespawnRadio=switch or true
|
||||
self:F({switch=switch})
|
||||
if switch==true or switch==nil then
|
||||
self.InitRespawnRadio=true
|
||||
else
|
||||
self.InitRespawnRadio=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the radio frequency of the group when it is respawned.
|
||||
@ -1469,7 +1474,7 @@ end
|
||||
-- @param #number frequency The frequency in MHz.
|
||||
-- @return #GROUP self
|
||||
function GROUP:InitRadioFrequency(frequency)
|
||||
self:F({frequency=frequency} )
|
||||
self:F({frequency=frequency})
|
||||
|
||||
self.InitRespawnFreq=frequency
|
||||
|
||||
@ -1490,6 +1495,17 @@ function GROUP:InitRadioModulation(modulation)
|
||||
return self
|
||||
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}.
|
||||
-- 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
|
||||
|
||||
-- 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.
|
||||
if self.InitRespawnRadio then
|
||||
Template.communication=self.InitRespawnRadio
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user