Files
MOOSE/Moose Development/Moose/Ops/FlightGroup.lua
T
Frank dbc8e8be67 Ops
2020-04-30 17:28:06 +02:00

6286 lines
197 KiB
Lua

--- **Ops** - (AI) Flight Group.
--
-- **Main Features:**
--
-- * Monitor flight status of elements or entire group.
-- * Monitor fuel and ammo status.
-- * Sophisticated task queueing system.
-- * Many additional events for each element and the whole group.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.FlightGroup
-- @image OPS_FlightGroup.png
--- FLIGHTGROUP class.
-- @type FLIGHTGROUP
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #number verbose Verbosity level. 0=silent.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string groupname Name of flight group.
-- @field Wrapper.Group#GROUP group Flight group object.
-- @field #string type Aircraft type of flight group.
-- @field #table elements Table of elements, i.e. units of the group.
-- @field #table waypoints Table of waypoints.
-- @field #table waypoints0 Table of initial waypoints.
-- @field #table currentwp Current waypoint.
-- @field #table taskqueue Queue of tasks.
-- @field #number taskcounter Running number of task ids.
-- @field #number taskcurrent ID of current task. If 0, there is no current task assigned.
-- @field #table taskenroute Enroute task of the group.
-- @field #table taskpaused Paused tasks.
-- @field #table missionqueue Queue of missions.
-- @field #number currentmission The ID (auftragsnummer) of the currently assigned AUFTRAG.
-- @field Core.Set#SET_UNIT detectedunits Set of detected units.
-- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group.
-- @field Wrapper.Airbase#AIRBASE destbase The destination base of the flight group.
-- @field Core.Zone#ZONE homezone The home zone of the flight group. Set when spawn happens in air.
-- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air.
-- @field #string attribute Generalized attribute.
-- @field #string actype Type name of the aircraft.
-- @field #number speedmax Max speed in km/h.
-- @field #number rangemax Max range in km.
-- @field #number ceiling Max altitude the aircraft can fly at in meters.
-- @field #number tankertype The refueling system type (0=boom, 1=probe), if the group is a tanker.
-- @field #number refueltype The refueling system type (0=boom, 1=probe), if the group can refuel from a tanker.
-- @field #boolean ai If true, flight is purely AI. If false, flight contains at least one human player.
-- @field #boolean fuellow Fuel low switch.
-- @field #number fuellowthresh Low fuel threshold in percent.
-- @field #boolean fuellowrtb RTB on low fuel switch.
-- @field #boolean fuelcritical Fuel critical switch.
-- @field #number fuelcriticalthresh Critical fuel threshold in percent.
-- @field #boolean fuelcriticalrtb RTB on critical fuel switch.
-- @field #boolean passedfinalwp Group has passed the final waypoint.
-- @field Ops.AirWing#AIRWING airwing The airwing the flight group belongs to.
-- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group.
-- @field Ops.Airboss#AIRBOSS airboss The airboss handling this group.
-- @field Core.UserFlag#USERFLAG flaghold Flag for holding.
-- @field #number Tholding Abs. mission time stamp when the group reached the holding point.
-- @field #number Tparking Abs. mission time stamp when the group was spawned uncontrolled and is parking.
-- @field #table menu F10 radio menu.
-- @field #string controlstatus Flight control status.
-- @field Core.Set#SET_ZONE checkzones Set of zones.
-- @field Core.Set#SET_ZONE inzones Set of zones in which the group is currently in.
-- @field #boolean groupinitialized If true, group parameters were initialized.
-- @field #boolean ishelo If true, the is a helicopter group.
-- @field #boolean detectionOn If true, detected units of the group are analyzed.
--
-- @field #number tacanChannelDefault The default TACAN channel.
-- @field #string tacanMorseDefault The default TACAN morse code.
-- @field #number tacanChannel The currenly used TACAN channel.
-- @field #string tacanMorse The currently used TACAN morse code.
-- @field #boolean tacanOn If true, TACAN is currently active.
-- @field Wrapper.Unit#UNIT tacanBeacon The unit acting as TACAN beacon.
--
-- @field #number radioFreqDefault Default radio frequency in MHz.
-- @field #number radioFreq Currently used radio frequency in MHz.
-- @field #number radioModuDefault Default Radio modulation `radio.modulation.AM` or `radio.modulation.FM`.
-- @field #number radioModu Currently used radio modulation `radio.modulation.AM` or `radio.modulation.FM`.
-- @field #boolean radioOn If true, radio is currently turned on.
-- @field Core.RadioQueue#RADIOQUEUE radioQueue Radio queue.
--
-- @field #number CallsignName Call sign name.
-- @field #number CallsignNumber Call sign number.
-- @field #boolean EPLRS If true, turn EPLRS data link on.
--
-- @field #string optionROEdefault Default ROE setting.
-- @field #string optionROTdefault Default ROT setting.
-- @field #string optionROEcurrent Current ROE setting.
-- @field #string optionROTcurrent Current ROT setting.
--
-- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission.
--
-- @field Core.Point#COORDINATE position Current position of the group.
-- @field #number traveldist Distance traveled in meters. This is a lower bound!
-- @field #number traveltime Time.
-- @field #number fuelmass Inital fuel in kg.
--
-- @extends Core.Fsm#FSM
--- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal
--
-- ===
--
-- ![Banner Image](..\Presentations\FlightGroup\FLIGHTGROUP_Main.jpg)
--
-- # The FLIGHTGROUP Concept
--
-- # Events
--
-- This class introduces a lot of additional events that will be handy in many situations.
-- Certain events like landing, takeoff etc. are triggered for each element and also have a corresponding event when the whole group reaches this state.
--
-- ## Spawning
--
-- ## Parking
--
-- ## Taxiing
--
-- ## Takeoff
--
-- ## Airborne
--
-- ## Landed
--
-- ## Arrived
--
-- ## Dead
--
-- ## Fuel
--
-- ## Ammo
--
-- ## Detected Units
--
-- ## Check In Zone
--
-- ## Passing Waypoint
--
--
-- # Tasking
--
-- The FLIGHTGROUP class significantly simplifies the monitoring of DCS tasks. Two types of tasks can be set
--
-- * **Scheduled Tasks**
-- * **Waypoint Tasks**
--
-- ## Scheduled Tasks
--
-- ## Waypoint Tasks
--
-- # Missions
--
-- ## Anti-ship
--
-- ## AWACS
--
-- ## INTERCEPT
--
--
-- # Examples
--
-- Here are some examples to show how things are done.
--
-- ## 1. Spawn
--
-- ## 2. Attack Group
--
-- ## 3. Whatever
--
-- ## 4. Simple Tanker
--
-- ## 5. Simple AWACS
--
-- ## 6. Scheduled Tasks
--
-- ## 7. Waypoint Tasks
--
-- ## 8. Enroute Tasks
--
--
--
--
-- @field #FLIGHTGROUP
FLIGHTGROUP = {
ClassName = "FLIGHTGROUP",
Debug = false,
verbose = 0,
lid = nil,
groupname = nil,
group = nil,
grouptemplate = nil,
type = nil,
waypoints = nil,
waypoints0 = nil,
currentwp = -100,
elements = {},
taskqueue = {},
taskcounter = nil,
taskcurrent = nil,
taskenroute = nil,
taskpaused = {},
missionqueue = {},
currentmission = nil,
detectedunits = {},
homebase = nil,
destbase = nil,
homezone = nil,
destzone = nil,
attribute = nil,
actype = nil,
speedmax = nil,
rangemax = nil,
ceiling = nil,
fuellow = false,
fuellowthresh = nil,
fuellowrtb = nil,
fuelcritical = nil,
fuelcriticalthresh = nil,
fuelcriticalrtb = false,
squadron = nil,
flightcontrol = nil,
flaghold = nil,
Tholding = nil,
Tparking = nil,
menu = nil,
checkzones = nil,
inzones = nil,
groupinitialized = nil,
ishelo = nil,
beacon = nil,
}
--- Status of flight group element.
-- @type FLIGHTGROUP.ElementStatus
-- @field #string INUTERO Element was not spawned yet or its status is unknown so far.
-- @field #string SPAWNED Element was spawned into the world.
-- @field #string PARKING Element is parking after spawned on ramp.
-- @field #string ENGINEON Element started its engines.
-- @field #string TAXIING Element is taxiing after engine startup.
-- @field #string TAKEOFF Element took of after takeoff event.
-- @field #string AIRBORNE Element is airborne. Either after takeoff or after air start.
-- @field #string LANDING Element is landing.
-- @field #string LANDED Element landed and is taxiing to its parking spot.
-- @field #string ARRIVED Element arrived at its parking spot and shut down its engines.
-- @field #string DEAD Element is dead after it crashed, pilot ejected or pilot dead events.
FLIGHTGROUP.ElementStatus={
INUTERO="inutero",
SPAWNED="spawned",
PARKING="parking",
ENGINEON="engineon",
TAXIING="taxiing",
TAKEOFF="takeoff",
AIRBORNE="airborne",
LANDING="landing",
LANDED="landed",
ARRIVED="arrived",
DEAD="dead",
}
--- Generalized attribute. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
-- @type FLIGHTGROUP.Attribute
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
-- @field #string AWACS Airborne Early Warning and Control System.
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
-- @field #string TANKER Airplane which can refuel other aircraft.
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
-- @field #string ATTACKHELO Attack helicopter.
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
-- @field #string OTHER Other aircraft type.
FLIGHTGROUP.Attribute = {
TRANSPORTPLANE="TransportPlane",
AWACS="AWACS",
FIGHTER="Fighter",
BOMBER="Bomber",
TANKER="Tanker",
TRANSPORTHELO="TransportHelo",
ATTACKHELO="AttackHelo",
UAV="UAV",
OTHER="Other",
}
--- Flight group element.
-- @type FLIGHTGROUP.Element
-- @field #string name Name of the element, i.e. the unit/client.
-- @field Wrapper.Unit#UNIT unit Element unit object.
-- @field Wrapper.Group#GROUP group Group object of the element.
-- @field #string modex Tail number.
-- @field #string skill Skill level.
-- @field #boolean ai If true, element is AI.
-- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player.
-- @field #table pylons Table of pylons.
-- @field #number fuelmass Mass of fuel in kg.
-- @field #number category Aircraft category.
-- @field #string categoryname Aircraft category name.
-- @field #string callsign Call sign, e.g. "Uzi 1-1".
-- @field #string status Status, i.e. born, parking, taxiing. See @{#FLIGHTGROUP.ElementStatus}.
-- @field #number damage Damage of element in percent.
-- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on.
--- Flight group task status.
-- @type FLIGHTGROUP.TaskStatus
-- @field #string SCHEDULED Task is scheduled.
-- @field #string EXECUTING Task is being executed.
-- @field #string PAUSED Task is paused.
-- @field #string DONE Task is done.
FLIGHTGROUP.TaskStatus={
SCHEDULED="scheduled",
EXECUTING="executing",
PAUSED="paused",
DONE="done",
}
--- Flight group task status.
-- @type FLIGHTGROUP.TaskType
-- @field #string SCHEDULED Task is scheduled and will be executed at a given time.
-- @field #string WAYPOINT Task is executed at a specific waypoint.
FLIGHTGROUP.TaskType={
SCHEDULED="scheduled",
WAYPOINT="waypoint",
}
--- Flight group task structure.
-- @type FLIGHTGROUP.Task
-- @field #string type Type of task: either SCHEDULED or WAYPOINT.
-- @field #number id Task ID. Running number to get the task.
-- @field #number prio Priority.
-- @field #number time Abs. mission time when to execute the task.
-- @field #table dcstask DCS task structure.
-- @field #string description Brief text which describes the task.
-- @field #string status Task status.
-- @field #number duration Duration before task is cancelled in seconds. Default never.
-- @field #number timestamp Abs. mission time, when task was started.
-- @field #number waypoint Waypoint index if task is a waypoint task.
-- @field Core.UserFlag#USERFLAG stopflag If flag is set to 1 (=true), the task is stopped.
--- Enroute task.
-- @type FLIGHTGROUP.EnrouteTask
-- @field DCS#Task DCStask DCS task structure table.
-- @field #number WaypointIndex Waypoint number at which the enroute task is added.
--- Rules of Engagement (ROE).
-- @type FLIGHTGROUP.ROE
-- @field #string RETURNFIRE
-- @field #string WEAPONFREE
-- @field #string WEAPONHOLD
FLIGHTGROUP.ROE={
WEAPONFREE="weapon_free",
OPENFIREPRIO="open_fire_prio_designated",
OPENFIREDESIG="open_fire_only_designated",
RETURNFIRE="return_fire",
WEAPONHOLD="weapon_hold",
}
--- Reaction on Threat (ROT).
-- @type FLIGHTGROUP.ROT
-- @field #string NOREACT No defensive actions will take place to counter threats.
-- @field #string PASSIVE AI will use jammers and other countermeasures in an attempt to defeat the threat. AI will not attempt a maneuver to defeat a threat.
-- @field #string EVADE AI will react by performing defensive maneuvers against incoming threats, AI will also use passive defense.
-- @field #string BYPASS aI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats.
-- @field #string ABORT If a threat is deemed severe enough the AI will abort its mission and return to base.
FLIGHTGROUP.ROT={
NOREACT="no_reaction",
PASSIVE="weapon_free",
EVADE="return_fire",
BYPASS="bypass_and_escape",
ABORT="allow_abort_mission",
}
--- FLIGHTGROUP class version.
-- @field #string version
FLIGHTGROUP.version="0.4.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Use new UnitLost event instead of crash/dead.
-- TODO: Options EPLRS, Afterburner restrict etc.
-- DONE: Add TACAN beacon.
-- TODO: Damage?
-- TODO: shot events?
-- TODO: Marks to add waypoints/tasks on-the-fly.
-- TODO: Mark assigned parking spot on F10 map.
-- TODO: Let user request a parking spot via F10 marker :)
-- TODO: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results?
-- TODO: Out of AG/AA missiles. Safe state of out-of-ammo.
-- DONE: Add tasks.
-- DONE: Waypoints, read, add, insert, detour.
-- DONE: Get ammo.
-- DONE: Get pylons.
-- DONE: Fuel threshhold ==> RTB.
-- DONE: ROE
-- NOGO: Respawn? With correct loadout, fuelstate. Solved in DCS 2.5.6!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLIGHTGROUP object and start the FSM.
-- @param #FLIGHTGROUP self
-- @param #string groupname Name of the group.
-- @param #string autostart (Optional) If `true` or `nil` automatically start the FSM (default). If `false`, use FLIGHTGROUP:Start() manually.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:New(groupname, autostart)
-- First check if we already have a flight group for this group.
local fg=_DATABASE:GetFlightGroup(groupname)
if fg then
fg:I(fg.lid..string.format("WARNING: Flight group %s already exists in data base!", groupname))
return fg
end
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTGROUP
--self.group=AIGroup
self.groupname=tostring(groupname)
-- Set some string id for output to DCS.log file.
self.lid=string.format("FLIGHTGROUP %s | ", self.groupname)
-- Start State.
self:SetStartState("Stopped")
-- Init set of detected units.
self.detectedunits=SET_UNIT:New()
-- Init inzone set.
self.inzones=SET_ZONE:New()
-- Defaults
self:SetFuelLowThreshold()
self:SetFuelCriticalThreshold()
self:SetDefaultROE()
self:SetDefaultROT()
self:SetDetection()
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "InUtero") -- Start FSM.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "FlightStatus", "*") -- Status update.
self:AddTransition("*", "QueueUpdate", "*") -- Update task and mission queues.
self:AddTransition("*", "DetectedUnit", "*") -- Add a newly detected unit to the detected units set.
self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set.
self:AddTransition("*", "DetectedUnitKnown", "*") -- Add a newly detected unit to the detected units set.
self:AddTransition("*", "DetectedUnitLost", "*") -- Group lost a detected target.
self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne.
self:AddTransition("*", "Respawn", "*") -- Respawn group.
self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base.
self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet!
self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern.
self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. Not implemented yet!
self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group is send to refuel at a tanker. Not implemented yet!
self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point.
self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point.
self:AddTransition("*", "PassingWaypoint", "*") -- Group passed a waypoint.
self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint.
self:AddTransition("*", "Wait", "Waiting") -- Group is orbiting.
self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%.
self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%.
self:AddTransition("*", "OutOfAmmo", "*") -- Group is completely out of ammo.
self:AddTransition("*", "OutOfGuns", "*") -- Group is out of gun shells.
self:AddTransition("*", "OutOfRockets", "*") -- Group is out of rockets.
self:AddTransition("*", "OutOfBombs", "*") -- Group is out of bombs.
self:AddTransition("*", "OutOfMissiles", "*") -- Group is out of missiles.
self:AddTransition("*", "OutOfMissilesA2A", "*") -- Group is out of A2A missiles. Not implemented yet!
self:AddTransition("*", "OutOfMissilesA2G", "*") -- Group is out of A2G missiles. Not implemented yet!
self:AddTransition("*", "TaskExecute", "*") -- Group will execute a task.
self:AddTransition("*", "TaskPause", "*") -- Pause current task. Not implemented yet!
self:AddTransition("*", "TaskCancel", "*") -- Cancel current task.
self:AddTransition("*", "TaskDone", "*") -- Task is over.
self:AddTransition("*", "MissionStart", "*") -- Mission is started.
self:AddTransition("*", "MissionExecute", "*") -- Mission execution began.
self:AddTransition("*", "MissionCancel", "*") -- Cancel current mission.
self:AddTransition("*", "PauseMission", "*") -- Pause the current mission.
self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission.
self:AddTransition("*", "MissionDone", "*") -- Mission is over.
self:AddTransition("Airborne", "EngageTargets", "Engaging") -- Engage targets.
self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over.
self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned.
self:AddTransition("*", "ElementParking", "*") -- An element is parking.
self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines.
self:AddTransition("*", "ElementTaxiing", "*") -- An element is taxiing to the runway.
self:AddTransition("*", "ElementTakeoff", "*") -- An element took off.
self:AddTransition("*", "ElementAirborne", "*") -- An element is airborne.
self:AddTransition("*", "ElementLanded", "*") -- An element landed.
self:AddTransition("*", "ElementArrived", "*") -- An element arrived.
self:AddTransition("*", "ElementDead", "*") -- An element crashed, ejected, or pilot dead.
self:AddTransition("*", "ElementOutOfAmmo", "*") -- An element is completely out of ammo.
self:AddTransition("*", "CheckZone", "*") -- Check if flight enters/leaves a certain zone.
self:AddTransition("*", "EnterZone", "*") -- Flight entered a certain zone.
self:AddTransition("*", "LeaveZone", "*") -- Flight leaves a certain zone.
self:AddTransition("*", "FlightSpawned", "Spawned") -- The whole flight group was spawned.
self:AddTransition("*", "FlightParking", "Parking") -- The whole flight group is parking.
self:AddTransition("*", "FlightTaxiing", "Taxiing") -- The whole flight group is taxiing.
self:AddTransition("*", "FlightTakeoff", "Airborne") -- The whole flight group is airborne.
self:AddTransition("*", "FlightAirborne", "Airborne") -- The whole flight group is airborne.
self:AddTransition("*", "FlightLanding", "Landing") -- The whole flight group is landing.
self:AddTransition("*", "FlightLanded", "Landed") -- The whole flight group has landed.
self:AddTransition("*", "FlightArrived", "Arrived") -- The whole flight group has arrived.
self:AddTransition("*", "FlightDead", "Dead") -- The whole flight group is dead.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the FLIGHTGROUP. Initializes parameters and starts event handlers.
-- @function [parent=#FLIGHTGROUP] Start
-- @param #FLIGHTGROUP self
--- Triggers the FSM event "Start" after a delay. Starts the FLIGHTGROUP. Initializes parameters and starts event handlers.
-- @function [parent=#FLIGHTGROUP] __Start
-- @param #FLIGHTGROUP self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the FLIGHTGROUP and all its event handlers.
-- @param #FLIGHTGROUP self
--- Triggers the FSM event "Stop" after a delay. Stops the FLIGHTGROUP and all its event handlers.
-- @function [parent=#FLIGHTGROUP] __Stop
-- @param #FLIGHTGROUP self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "FlightStatus".
-- @function [parent=#FLIGHTGROUP] FlightStatus
-- @param #FLIGHTGROUP self
--- Triggers the FSM event "SkipperStatus" after a delay.
-- @function [parent=#FLIGHTGROUP] __FlightStatus
-- @param #FLIGHTGROUP self
-- @param #number delay Delay in seconds.
-- Debug trace.
if false then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
-- Init task counter.
self.taskcurrent=0
self.taskcounter=0
-- Holding flag.
self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname))
self.flaghold:Set(0)
-- Add to data base.
_DATABASE:AddFlightGroup(self)
-- Autostart.
if autostart==true or autostart==nil then
self:Start()
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a *scheduled* task.
-- @param #FLIGHTGROUP self
-- @param #table task DCS task table structure.
-- @param #string clock Mission time when task is executed. Default in 5 seconds. If argument passed as #number, it defines a relative delay in seconds.
-- @param #string description Brief text describing the task, e.g. "Attack SAM".
-- @param #number prio Priority of the task.
-- @param #number duration Duration before task is cancelled in seconds counted after task started. Default never.
-- @return #FLIGHTGROUP.Task The task structure.
function FLIGHTGROUP:AddTask(task, clock, description, prio, duration)
local newtask=self:NewTaskScheduled(task, clock, description, prio, duration)
-- Add to table.
table.insert(self.taskqueue, newtask)
-- Info.
self:I(self.lid..string.format("Adding SCHEDULED task %s starting at %s", newtask.description, UTILS.SecondsToClock(newtask.time, true)))
self:T3({newtask=newtask})
return newtask
end
--- Create a *scheduled* task.
-- @param #FLIGHTGROUP self
-- @param #table task DCS task table structure.
-- @param #string clock Mission time when task is executed. Default in 5 seconds. If argument passed as #number, it defines a relative delay in seconds.
-- @param #string description Brief text describing the task, e.g. "Attack SAM".
-- @param #number prio Priority of the task.
-- @param #number duration Duration before task is cancelled in seconds counted after task started. Default never.
-- @return #FLIGHTGROUP.Task The task structure.
function FLIGHTGROUP:NewTaskScheduled(task, clock, description, prio, duration)
-- Increase counter.
self.taskcounter=self.taskcounter+1
-- Set time.
local time=timer.getAbsTime()+5
if clock then
if type(clock)=="string" then
time=UTILS.ClockToSeconds(clock)
elseif type(clock)=="number" then
time=timer.getAbsTime()+clock
end
end
-- Task data structure.
local newtask={} --#FLIGHTGROUP.Task
newtask.status=FLIGHTGROUP.TaskStatus.SCHEDULED
newtask.dcstask=task
newtask.description=description or task.id
newtask.prio=prio or 50
newtask.time=time
newtask.id=self.taskcounter
newtask.duration=duration
newtask.waypoint=-1
newtask.type=FLIGHTGROUP.TaskType.SCHEDULED
newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id))
newtask.stopflag:Set(0)
return newtask
end
--- Add a *waypoint* task.
-- @param #FLIGHTGROUP self
-- @param #table task DCS task table structure.
-- @param #number waypointindex Number of waypoint. Counting starts at one! Default is the as *next* waypoint.
-- @param #string description Brief text describing the task, e.g. "Attack SAM".
-- @param #number prio Priority of the task. Number between 1 and 100. Default is 50.
-- @param #number duration Duration before task is cancelled in seconds counted after task started. Default never.
-- @return #FLIGHTGROUP.Task The task structure.
function FLIGHTGROUP:AddTaskWaypoint(task, waypointindex, description, prio, duration)
-- Increase counter.
self.taskcounter=self.taskcounter+1
-- Task data structure.
local newtask={} --#FLIGHTGROUP.Task
newtask.description=description
newtask.status=FLIGHTGROUP.TaskStatus.SCHEDULED
newtask.dcstask=task
newtask.prio=prio or 50
newtask.id=self.taskcounter
newtask.duration=duration
newtask.time=0
newtask.waypoint=waypointindex or (self.currentwp and self.currentwp+1 or 2)
newtask.type=FLIGHTGROUP.TaskType.WAYPOINT
newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id))
newtask.stopflag:Set(0)
-- Add to table.
table.insert(self.taskqueue, newtask)
-- Info.
self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint))
self:T3({newtask=newtask})
-- Update route.
--self:_CheckFlightDone(1)
self:__UpdateRoute(-1)
return newtask
end
--- Add an *enroute* task.
-- @param #FLIGHTGROUP self
-- @param #table task DCS task table structure.
function FLIGHTGROUP:AddTaskEnroute(task)
if not self.taskenroute then
self.taskenroute={}
end
-- Check not to add the same task twice!
local gotit=false
for _,Task in pairs(self.taskenroute) do
if Task.id==task.id then
gotit=true
break
end
end
if not gotit then
table.insert(self.taskenroute, task)
end
end
--- Add an *enroute* task to attack targets in a certain **circular** zone.
-- @param #FLIGHTGROUP self
-- @param Core.Zone#ZONE_RADIUS ZoneRadius The circular zone, where to engage targets.
-- @param #table TargetTypes (Optional) The target types, passed as a table, i.e. mind the curly brackets {}. Default {"Air"}.
-- @param #number Priority (Optional) Priority. Default 0.
function FLIGHTGROUP:AddTaskEnrouteEngageTargetsInZone(ZoneRadius, TargetTypes, Priority)
local Task=self.group:EnRouteTaskEngageTargetsInZone(ZoneRadius:GetVec2(), ZoneRadius:GetRadius(), TargetTypes, Priority)
self:AddTaskEnroute(Task)
end
--- Remove task from task queue.
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Task Task The task to be removed from the queue.
-- @return #boolean True if task could be removed.
function FLIGHTGROUP:RemoveTask(Task)
for i=#self.taskqueue,1,-1 do
local task=self.taskqueue[i] --#FLIGHTGROUP.Task
if task.id==Task.id then
-- Remove task from queue.
table.remove(self.taskqueue, i)
-- Update route if this is a waypoint task.
if task.type==FLIGHTGROUP.TaskType.WAYPOINT and task.status==FLIGHTGROUP.TaskStatus.SCHEDULED then
self:_CheckFlightDone(1)
--self:__UpdateRoute(-1)
end
return true
end
end
return false
end
--- Add mission to queue.
-- @param #FLIGHTGROUP self
-- @param Ops.Auftrag#AUFTRAG Mission Mission for this group.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:AddMission(Mission)
-- Add flight group to mission.
Mission:AddFlightGroup(self)
-- Set flight status to SCHEDULED..
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.SCHEDULED)
-- Set mission status to SCHEDULED.
Mission:Scheduled()
-- Add mission to queue.
table.insert(self.missionqueue, Mission)
-- Info text.
local text=string.format("Added %s mission %s starting at %s, stopping at %s",
tostring(Mission.type), tostring(Mission.name), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF")
self:I(self.lid..text)
return self
end
--- Remove mission from queue.
-- @param #FLIGHTGROUP self
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:RemoveMission(Mission)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission.auftragsnummer==Mission.auftragsnummer then
-- Remove mission waypoint task.
local Task=Mission:GetFlightWaypointTask(self)
if Task then
self:RemoveTask(Task)
end
-- Remove mission from queue.
table.remove(self.missionqueue, i)
return self
end
end
return self
end
--- Set AIRWING the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @param Ops.AirWing#AIRWING airwing The AIRWING object.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetAirwing(airwing)
self:I(self.lid..string.format("Add flight to AIRWING %s", airwing.alias))
self.airwing=airwing
return self
end
--- Get airwing the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return Ops.AirWing#AIRWING The AIRWING object.
function FLIGHTGROUP:GetAirWing()
return self.airwing
end
--- Set detection on or off.
-- @param #FLIGHTGROUP self
-- @param #boolean Switch If true, detection is on. If false or nil, detection is off. Default is off.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDetection(Switch)
self.detectionOn=Switch
return self
end
--- Set the FLIGHTCONTROL controlling this flight group.
-- @param #FLIGHTGROUP self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFlightControl(flightcontrol)
-- Remove flight from previous FC.
if self.flightcontrol then
if self.flightcontrol.airbasename==flightcontrol.airbasename then
-- Flight control is already controlling this flight!
return
else
self.flightcontrol:_RemoveFlight(self)
end
end
-- Set FC.
self:I(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s", flightcontrol.airbasename))
self.flightcontrol=flightcontrol
-- Add flight to all flights.
table.insert(flightcontrol.flights, self)
-- Update flight's F10 menu.
if self.ai==false then
self:_UpdateMenu()
end
return self
end
--- Get the FLIGHTCONTROL controlling this flight group.
-- @param #FLIGHTGROUP self
-- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.
function FLIGHTGROUP:GetFlightControl()
return self.flightcontrol
end
--- Set the AIRBOSS controlling this flight group.
-- @param #FLIGHTGROUP self
-- @param Ops.Airboss#AIRBOSS airboss The AIRBOSS object.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetAirboss(airboss)
self.airboss=airboss
return self
end
--- Set low fuel threshold. Triggers event "FuelLow" and calls event function "OnAfterFuelLow".
-- @param #FLIGHTGROUP self
-- @param #number threshold Fuel threshold in percent. Default 25 %.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFuelLowThreshold(threshold)
self.fuellowthresh=threshold or 25
return self
end
--- Set if low fuel threshold is reached, flight goes RTB.
-- @param #FLIGHTGROUP self
-- @param #boolean switch If true or nil, flight goes RTB. If false, turn this off.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFuelLowRTB(switch)
if switch==false then
self.fuellowrtb=false
else
self.fuellowrtb=true
end
return self
end
--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker.
-- @param #FLIGHTGROUP self
-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFuelLowRefuel(switch)
if switch==false then
self.fuellowrefuel=false
else
self.fuellowrefuel=true
end
return self
end
--- Set fuel critical threshold. Triggers event "FuelCritical" and event function "OnAfterFuelCritical".
-- @param #FLIGHTGROUP self
-- @param #number threshold Fuel threshold in percent. Default 10 %.
-- @param #boolean rtb If true, RTB on fuel critical event.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFuelCriticalThreshold(threshold, rtb)
self.fuelcriticalthresh=threshold or 10
self.fuelcriticalrtb=rtb
return self
end
--- Define a SET of zones that trigger and event if the group enters or leaves any of the zones.
-- @param #FLIGHTGROUP self
-- @param Core.Set#SET_ZONE CheckZonesSet Set of zones.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetCheckZones(CheckZonesSet)
self.checkzones=CheckZonesSet
return self
end
--- Add a zone that triggers and event if the group enters or leaves any of the zones.
-- @param #FLIGHTGROUP self
-- @param Core.Zone#ZONE CheckZone Zone to check.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:AddCheckZone(CheckZone)
if not self.checkzones then
self.checkzones=SET_ZONE:New()
end
self.checkzones:AddZone(CheckZone)
return self
end
--- Get set of detected units.
-- @param #FLIGHTGROUP self
-- @return Core.Set#SET_UNIT Set of detected units.
function FLIGHTGROUP:GetDetectedUnits()
return self.detectedunits
end
--- Get MOOSE group object.
-- @param #FLIGHTGROUP self
-- @return Wrapper.Group#GROUP Moose group object.
function FLIGHTGROUP:GetGroup()
return self.group
end
--- Get flight group name.
-- @param #FLIGHTGROUP self
-- @return #string Group name.
function FLIGHTGROUP:GetName()
return self.groupname
end
--- Get current coordinate of the group.
-- @param #FLIGHTGROUP self
-- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group.
function FLIGHTGROUP:GetCoordinate()
if self:IsAlive()~=nil then
return self.group:GetCoordinate()
else
self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!")
end
return nil
end
--- Get waypoint.
-- @param #FLIGHTGROUP self
-- @param #number indx Waypoint index.
-- @return #table Waypoint table.
function FLIGHTGROUP:GetWaypoint(indx)
return self.waypoints[indx]
end
--- Get final waypoint.
-- @param #FLIGHTGROUP self
-- @return #table Waypoint table.
function FLIGHTGROUP:GetWaypointFinal()
return self.waypoints[#self.waypoints]
end
--- Get next waypoint.
-- @param #FLIGHTGROUP self
-- @return #table Waypoint table.
function FLIGHTGROUP:GetWaypointNext()
local n=math.min(self.currentwp+1, #self.waypoints)
return self.waypoints[n]
end
--- Get current waypoint.
-- @param #FLIGHTGROUP self
-- @return #table Waypoint table.
function FLIGHTGROUP:GetWaypointCurrent()
return self.waypoints[self.currentwp]
end
--- Check if flight is in state in utero.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is not spawned yet.
function FLIGHTGROUP:IsInUtero()
return self:Is("InUtero")
end
--- Check if flight is in state spawned.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is spawned.
function FLIGHTGROUP:IsSpawned()
return self:Is("Spawned")
end
--- Check if flight is parking.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is parking after spawned.
function FLIGHTGROUP:IsParking()
return self:Is("Parking")
end
--- Check if flight is parking.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is taxiing after engine start up.
function FLIGHTGROUP:IsTaxiing()
return self:Is("Taxiing")
end
--- Check if flight is airborne.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is airborne.
function FLIGHTGROUP:IsAirborne()
return self:Is("Airborne")
end
--- Check if flight is waiting after passing final waypoint.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is waiting.
function FLIGHTGROUP:IsWaiting()
return self:Is("Waiting")
end
--- Check if flight is landing.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is landing, i.e. on final approach.
function FLIGHTGROUP:IsLanding()
return self:Is("Landing")
end
--- Check if flight has landed and is now taxiing to its parking spot.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight has landed
function FLIGHTGROUP:IsLanded()
return self:Is("Landed")
end
--- Check if flight has arrived at its destination parking spot.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight has arrived at its destination and is parking.
function FLIGHTGROUP:IsArrived()
return self:Is("Arrived")
end
--- Check if flight is inbound and traveling to holding pattern.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is holding.
function FLIGHTGROUP:IsInbound()
return self:Is("Inbound")
end
--- Check if flight is holding and waiting for landing clearance.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is holding.
function FLIGHTGROUP:IsHolding()
return self:Is("Holding")
end
--- Check if flight is going for fuel.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is refueling.
function FLIGHTGROUP:IsGoing4Fuel()
return self:Is("Going4Fuel")
end
--- Check if helo(!) flight is ordered to land at a specific point.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, group has task to land somewhere.
function FLIGHTGROUP:IsLandingAt()
return self:Is("LandingAt")
end
--- Check if helo(!) flight is currently landed at a specific point.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, group is currently landed at the assigned position and waiting until task is complete.
function FLIGHTGROUP:IsLandedAt()
return self:Is("LandedAt")
end
--- Check if flight is dead.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, all units/elements of the flight are dead.
function FLIGHTGROUP:IsDead()
return self:Is("Dead")
end
--- Check if flight FSM is stopped.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, FSM state is stopped.
function FLIGHTGROUP:IsStopped()
return self:Is("Stopped")
end
--- Check if flight is low on fuel.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is low on fuel.
function FLIGHTGROUP:IsFuelLow()
return self.fuellow
end
--- Check if flight is critical on fuel.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is critical on fuel.
function FLIGHTGROUP:IsFuelCritical()
return self.fuelcritical
end
--- Check if flight is alive.
-- @param #FLIGHTGROUP self
-- @return #boolean *true* if group is exists and is activated, *false* if group is exist but is NOT activated. *nil* otherwise, e.g. the GROUP object is *nil* or the group is not spawned yet.
function FLIGHTGROUP:IsAlive()
if self.group then
return self.group:IsAlive()
end
return nil
end
--- Activate a *late activated* group.
-- @param #FLIGHTGROUP self
-- @param #number delay (Optional) Delay in seconds before the group is activated. Default is immediately.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:Activate(delay)
if delay and delay>0 then
self:T2(self.lid..string.format("Activating late activated group in %d seconds", delay))
self:ScheduleOnce(delay, FLIGHTGROUP.Activate, self)
else
if self:IsAlive()==false then
self:T(self.lid.."Activating late activated group")
self.group:Activate()
self.isLateActivated=false
elseif self:IsAlive()==true then
self:E(self.lid.."WARNING: Activating group that is already activated")
else
self:E(self.lid.."ERROR: Activating group that is does not exist!")
end
end
return self
end
--- Start an *uncontrolled* group.
-- @param #FLIGHTGROUP self
-- @param #number delay (Optional) Delay in seconds before the group is started. Default is immediately.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:StartUncontrolled(delay)
if delay and delay>0 then
self:T2(self.lid..string.format("Starting uncontrolled group in %d seconds", delay))
self:ScheduleOnce(delay, FLIGHTGROUP.StartUncontrolled, self)
else
if self:IsAlive() then
--TODO: check Alive==true and Alive==false ==> Activate first
self:I(self.lid.."Starting uncontrolled group")
self.group:StartUncontrolled(delay)
self.isUncontrolled=true
else
self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!")
end
end
return self
end
--- Set DCS task. Enroute tasks are injected automatically.
-- @param #FLIGHTGROUP self
-- @param #table DCSTask DCS task structure.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetTask(DCSTask)
if self:IsAlive() then
if self.taskcurrent>0 then
-- TODO: Why the hell did I do this? It breaks scheduled tasks. I comment it out for now to see where it fails.
--local task=self:GetTaskCurrent()
--self:RemoveTask(task)
--self.taskcurrent=0
end
-- Inject enroute tasks.
if self.taskenroute and #self.taskenroute>0 then
if tostring(DCSTask.id)=="ComboTask" then
for _,task in pairs(self.taskenroute) do
table.insert(DCSTask.params.tasks, 1, task)
end
else
local tasks=UTILS.DeepCopy(self.taskenroute)
table.insert(tasks, DCSTask)
DCSTask=self.group.TaskCombo(self, tasks)
end
end
-- Set task.
self.group:SetTask(DCSTask)
-- Debug info.
local text=string.format("SETTING Task %s", tostring(DCSTask.id))
if tostring(DCSTask.id)=="ComboTask" then
for i,task in pairs(DCSTask.params.tasks) do
text=text..string.format("\n[%d] %s", i, tostring(task.id))
end
end
self:I(self.lid..text)
end
return self
end
--- Push DCS task.
-- @param #FLIGHTGROUP self
-- @param #table DCSTask DCS task structure.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:PushTask(DCSTask)
if self:IsAlive() then
-- Push task.
self.group:PushTask(DCSTask)
-- Debug info.
local text=string.format("PUSHING Task %s", tostring(DCSTask.id))
if tostring(DCSTask.id)=="ComboTask" then
for i,task in pairs(DCSTask.params.tasks) do
text=text..string.format("\n[%d] %s", i, tostring(task.id))
end
end
self:I(self.lid..text)
end
return self
end
--- Clear DCS tasks.
-- @param #FLIGHTGROUP self
-- @param #table DCSTask DCS task structure.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:ClearTasks()
if self:IsAlive() then
self.group:ClearTasks()
self:I(self.lid..string.format("CLEARING Tasks"))
end
return self
end
--- Clear the group for landing when it is holding.
-- @param #FLIGHTGROUP self
-- @param #number Delay Delay in seconds before landing clearance is given.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:ClearToLand(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, FLIGHTGROUP.ClearToLand, self)
else
if self:IsHolding() then
self:I(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)"))
self.flaghold:Set(1)
end
end
return self
end
--- Route group along waypoints. Enroute tasks are also applied.
-- @param #FLIGHTGROUP self
-- @param #table waypoints Table of waypoints.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:Route(waypoints)
if self:IsAlive() then
-- DCS task combo.
local Tasks={}
-- Enroute tasks.
if self.taskenroute then
for _,TaskEnroute in pairs(self.taskenroute) do
--table.insert(Tasks, TaskEnroute)
end
end
-- Route (Mission) task.
local TaskRoute=self.group:TaskRoute(waypoints)
table.insert(Tasks, TaskRoute)
-- TaskCombo of enroute and mission tasks.
local TaskCombo=self.group:TaskCombo(Tasks)
-- Set tasks.
if #Tasks>1 then
self:SetTask(TaskCombo)
else
self:SetTask(TaskRoute)
end
else
self:E(self.lid.."ERROR: Group is not alive!")
end
return self
end
--- Get min fuel of group. This returns the relative fuel amount of the element lowest fuel in the group.
-- @param #FLIGHTGROUP self
-- @return #number Relative fuel in percent.
function FLIGHTGROUP:GetFuelMin()
local fuelmin=math.huge
for i,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
local unit=element.unit
local life=unit:GetLife()
if unit and unit:IsAlive() and life>1 then
local fuel=unit:GetFuel()
if fuel<fuelmin then
fuelmin=fuel
end
end
end
return fuelmin*100
end
--- Self destruction of group. An explosion is created at the position of each element.
-- @param #FLIGHTGROUP self
-- @param #number Delay Delay in seconds. Default now.
-- @param #number ExplosionPower (Optional) Explosion power in kg TNT. Default 500 kg.
-- @return #number Relative fuel in percent.
function FLIGHTGROUP:SelfDestruction(Delay, ExplosionPower)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, FLIGHTGROUP.SelfDestruction, self, 0, ExplosionPower)
else
-- Loop over all elements.
for i,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
local unit=element.unit
if unit and unit:IsAlive() then
unit:Explode(ExplosionPower)
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting flight group v%s", FLIGHTGROUP.version)
self:I(self.lid..text)
-- Set current waypoint. Counting starts a one.
self.currentwp=1
-- Check if the group is already alive and if so, add its elements.
local group=GROUP:FindByName(self.groupname)
if group then
-- Set group object.
self.group=group
-- Get units of group.
local units=group:GetUnits() or {}
-- Add elemets.
for _,unit in pairs(units) do
local element=self:AddElementByName(unit:GetName())
end
if not self.groupinitialized then
self:_InitGroup()
end
if group:IsAlive() then
-- Debug info.
self:T(self.lid..string.format("Found EXISTING and ALIVE group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements))
-- Trigger spawned event for all elements.
for _,element in pairs(self.elements) do
-- Add a little delay or the OnAfterSpawned function is not even initialized and will not be called.
self:__ElementSpawned(0.1, element)
end
else
-- Debug info.
self:T(self.lid..string.format("Found EXISTING but LATE ACTIVATED group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements))
end
end
-- Handle events:
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff, self.OnEventTakeOff)
self:HandleEvent(EVENTS.Land, self.OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown, self.OnEventEngineShutdown)
self:HandleEvent(EVENTS.PilotDead, self.OnEventPilotDead)
self:HandleEvent(EVENTS.Ejection, self.OnEventEjection)
self:HandleEvent(EVENTS.Crash, self.OnEventCrash)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
-- Start the status monitoring.
self:__CheckZone(-1)
self:__FlightStatus(-2)
self:__QueueUpdate(-3)
end
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterStop(From, Event, To)
-- Check if group is still alive.
if self:IsAlive() and false then
-- Set element parking spot to FREE (after arrived for example).
if self.flightcontrol then
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
self:_SetElementParkingFree(element)
end
end
-- Destroy group.
self.group:Destroy()
end
-- Handle events:
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.RemoveUnit)
self.CallScheduler:Clear()
_DATABASE.FLIGHTGROUPS[self.groupname]=nil
self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.")
end
--- On after "FlightStatus" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightStatus(From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
---
-- Detection
---
-- Check if group has detected any units.
if self.detectionOn then
self:_CheckDetectedUnits()
end
---
-- Parking
---
-- Check if flight began to taxi (if it was parking).
if self:IsParking() then
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.parking then
-- Get distance to assigned parking spot.
local dist=element.unit:GetCoordinate():Get2DDistance(element.parking.Coordinate)
-- If distance >10 meters, we consider the unit as taxiing.
-- TODO: Check distance threshold! If element is taxiing, the parking spot is free again.
-- When the next plane is spawned on this spot, collisions should be avoided!
if dist>10 then
if element.status==FLIGHTGROUP.ElementStatus.ENGINEON then
self:ElementTaxiing(element)
end
end
else
--self:E(self.lid..string.format("Element %s is in PARKING queue but has no parking spot assigned!", element.name))
end
end
end
---
-- Elements
---
local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
-- Short info.
if self.verbose>0 then
local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Current=%d. Missions=%s. Waypoint=%d/%d. Detected=%d. Destination=%s, FC=%s",
fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0,
self.detectedunits:Count(), self.destbase and self.destbase:GetName() or "unknown", self.flightcontrol and self.flightcontrol.airbasename or "none")
self:I(self.lid..text)
end
-- Element status.
if self.verbose>1 then
local text="Elements:"
for i,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
local name=element.name
local status=element.status
local unit=element.unit
local fuel=unit:GetFuel() or 0
local life=unit:GetLifeRelative() or 0
local parking=element.parking and tostring(element.parking.TerminalID) or "X"
-- Check if element is not dead and we missed an event.
if life<0 and element.status~=FLIGHTGROUP.ElementStatus.DEAD and element.status~=FLIGHTGROUP.ElementStatus.INUTERO then
self:ElementDead(element)
end
-- Get ammo.
local nammo=0; local nshells=0 ; local nrockets=0; local nbombs=0; local nmissiles=0
if element.status~=FLIGHTGROUP.ElementStatus.DEAD then
nammo, nshells, nrockets, nbombs, nmissiles=self:GetAmmoElement(element)
end
-- Output text for element.
text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f, shells=%d, rockets=%d, bombs=%d, missiles=%d, parking=%s", i, name, status, fuel*100, life*100, nshells, nrockets, nbombs, nmissiles, parking)
end
if #self.elements==0 then
text=text.." none!"
end
self:I(self.lid..text)
end
---
-- Distance travelled
---
if self.verbose>1 and self:IsAlive() and self.position then
local time=timer.getAbsTime()
-- Current position.
local position=self:GetCoordinate()
-- Travelled distance since last check.
local ds=self.position:Get3DDistance(position)
-- Time interval.
local dt=time-self.traveltime
-- Speed.
local v=ds/dt
-- Add up travelled distance.
self.traveldist=self.traveldist+ds
self:I(self.lid..string.format("FF Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots", self.traveldist/1000, dt, UTILS.MpsToKnots(v)))
local fuelmass=math.huge
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
local fuel=element.unit:GetFuel() or 0
-- Relative fuel used since last check.
local dFrel=element.fuelrel-fuel
local dFabs=element.fuelmass
local fmass=element.fuelmass0*fuel
local dm=element.fuelmass-fmass
local dmds=dm/ds
local dmdt=dm/dt
if fmass<fuelmass then
fuelmass=fmass
end
self:I(self.lid..string.format("Fuel consumption %s F=%.1f: dF=%.3f, dm=%.1f kg ==> dm/ds=%.1f kg/min dm/dt=%.1f 1/s", element.name, fuel*100, dFrel*100, dm, dmds*60, dmdt))
element.fuelrel=fuel
element.fuelmass=fmass
end
-- Log outut.
--self:I(self.lid..string.format("FF Distance travelled = %.1f km v=%.1f knots fuel=%d kg", self.traveldist/1000, UTILS.MpsToKnots(v), fuelmass))
-- Update parameters.
self.traveltime=time
self.position=position
end
---
-- Tasks
---
-- Task queue.
if #self.taskqueue>0 and self.verbose>1 then
local text=string.format("Tasks #%d", #self.taskqueue)
for i,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
local name=task.description
local taskid=task.dcstask.id or "unknown"
local status=task.status
local clock=UTILS.SecondsToClock(task.time, true)
local eta=task.time-timer.getAbsTime()
local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A"
local duration=-1
if task.duration then
duration=task.duration
if task.timestamp then
-- Time the task is running.
duration=task.duration-(timer.getAbsTime()-task.timestamp)
else
-- Time the task is supposed to run.
duration=task.duration
end
end
-- Output text for element.
if task.type==FLIGHTGROUP.TaskType.SCHEDULED then
text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration)
elseif task.type==FLIGHTGROUP.TaskType.WAYPOINT then
text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get())
end
end
self:I(self.lid..text)
end
---
-- Missions
---
-- Current mission name.
if self.verbose>0 then
local Mission=self:GetMissionByID(self.currentmission)
-- Current status.
local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none")
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local Cstart= UTILS.SecondsToClock(mission.Tstart, true)
local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF"
text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d",
i, tostring(mission.name), mission.type, mission:GetFlightStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetFlightWaypointIndex(self)), mission:CountMissionTargets())
end
self:I(self.lid..text)
end
---
-- Fuel State
---
-- Only if group is in air.
if self:IsAlive() and self.group:IsAirborne(true) then
local fuelmin=self:GetFuelMin()
if fuelmin>=self.fuellowthresh then
self.fuellow=false
end
if fuelmin>=self.fuelcriticalthresh then
self.fuelcritical=false
end
-- Low fuel?
if fuelmin<self.fuellowthresh and not self.fuellow then
self:FuelLow()
end
-- Critical fuel?
if fuelmin<self.fuelcriticalthresh and not self.fuelcritical then
self:FuelCritical()
end
end
---
-- Airboss Helo
---
if self.ishelo and self.airboss and self:IsHolding() then
if self.airboss:IsRecovering() or self:IsFuelCritical() then
self:ClearToLand()
end
end
-- Next check in ~30 seconds.
if not self:IsStopped() then
self:__FlightStatus(-30)
end
end
--- On after "CheckZone" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterCheckZone(From, Event, To)
if self:IsAlive()==true then
self:_CheckInZones()
end
if not self:IsStopped() then
self:__CheckZone(-1)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Queue Update: Missions & Tasks
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "QueueUpdate" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterQueueUpdate(From, Event, To)
---
-- Mission
---
-- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically.
if self:IsAlive()~=nil then
local mission=self:_GetNextMission()
if mission then
local currentmission=self:GetMissionCurrent()
if currentmission then
-- Current mission but new mission is urgent with higher prio.
if mission.urgent and mission.prio<currentmission.prio then
self:MissionCancel(currentmission)
self:__MissionStart(1, mission)
end
else
-- No current mission.
self:MissionStart(mission)
end
end
end
---
-- Tasks
---
-- Check no current task.
if self:IsAirborne() and self.taskcurrent<=0 then
-- Get task from queue.
local task=self:_GetNextTask()
-- Execute task if any.
if task then
self:TaskExecute(task)
end
end
-- Update queue every ~5 sec.
if not self:IsStopped() then
self:__QueueUpdate(-5)
end
end
--- Get next task in queue. Task needs to be in state SCHEDULED and time must have passed.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP.Task The next task in line or `nil`.
function FLIGHTGROUP:_GetNextTask()
if self.taskpaused then
--return self.taskpaused
end
if #self.taskqueue==0 then
return nil
end
-- Sort queue wrt prio and start time.
self:_SortTaskQueue()
-- Current time.
local time=timer.getAbsTime()
-- Look for first task that is SCHEDULED.
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.type==FLIGHTGROUP.TaskType.SCHEDULED and task.status==FLIGHTGROUP.TaskStatus.SCHEDULED and time>=task.time then
return task
end
end
return nil
end
--- Get next mission.
-- @param #FLIGHTGROUP self
-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*.
function FLIGHTGROUP:_GetNextMission()
-- Number of missions.
local Nmissions=#self.missionqueue
-- Treat special cases.
if Nmissions==0 then
return nil
end
-- Sort results table wrt times they have already been engaged.
local function _sort(a, b)
local taskA=a --Ops.Auftrag#AUFTRAG
local taskB=b --Ops.Auftrag#AUFTRAG
return (taskA.prio<taskB.prio) or (taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.missionqueue, _sort)
-- Current time.
local time=timer.getAbsTime()
-- Look for first mission that is SCHEDULED.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission:GetFlightStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.airwing) then
return mission
end
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Flightgroup event function, handling the birth of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventBirth(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Set group.
self.group=self.group or EventData.IniGroup
if not self.groupinitialized then
--TODO: actually that is not very good here as if the first unit is born and in initgroup we initialize all elements!
self:_InitGroup()
end
if self.respawning then
local function reset()
self.respawning=nil
end
-- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed.
-- TODO: Can I do this more rigorously?
self:ScheduleOnce(1, reset)
else
-- Set homebase if not already set.
if EventData.Place then
self.homebase=self.homebase or EventData.Place
end
-- Get element.
local element=self:GetElementByName(unitname)
-- Create element spawned event if not already present.
if not self:_IsElement(unitname) then
element=self:AddElementByName(unitname)
end
-- Set element to spawned state.
self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name))
self:ElementSpawned(element)
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventEngineStartup(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
if element then
if self:IsAirborne() or self:IsInbound() or self:IsHolding() then
-- TODO: what?
else
self:T3(self.lid..string.format("EVENT: Element %s started engines ==> taxiing (if AI)", element.name))
-- TODO: could be that this element is part of a human flight group.
-- Problem: when player starts hot, the AI does too and starts to taxi immidiately :(
-- when player starts cold, ?
if self.ai then
self:ElementEngineOn(element)
else
if element.ai then
-- AI wingmen will start taxiing even if the player/client is still starting up his engines :(
self:ElementEngineOn(element)
end
end
end
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventTakeOff(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
if element then
self:T3(self.lid..string.format("EVENT: Element %s took off ==> airborne", element.name))
self:ElementTakeoff(element, EventData.Place)
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventLanding(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
local airbase=EventData.Place
local airbasename="unknown"
if airbase then
airbasename=tostring(airbase:GetName())
end
if element then
self:T3(self.lid..string.format("EVENT: Element %s landed at %s ==> landed", element.name, airbasename))
self:ElementLanded(element, airbase)
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventEngineShutdown(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
if element then
if element.unit and element.unit:IsAlive() then
local airbase=element.unit:GetCoordinate():GetClosestAirbase()
local parking=self:GetParkingSpot(element, 10, airbase)
if airbase and parking then
self:ElementArrived(element, airbase, parking)
self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived", element.name))
else
self:T3(self.lid..string.format("EVENT: Element %s shut down engines (in air) ==> dead", element.name))
self:ElementDead(element)
end
else
self:I(self.lid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name))
end
else
-- element is nil
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventCrash(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
if element then
self:T3(self.lid..string.format("EVENT: Element %s crashed ==> dead", element.name))
self:ElementDead(element)
end
end
end
--- Flightgroup event function handling the crash of a unit.
-- @param #FLIGHTGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function FLIGHTGROUP:OnEventRemoveUnit(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- Get element.
local element=self:GetElementByName(unitname)
if element then
self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name))
self:ElementDead(element)
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ElementSpawned" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element)
self:T(self.lid..string.format("Element spawned %s", Element.name))
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.SPAWNED)
if Element.unit:InAir() then
-- Trigger ElementAirborne event. Add a little delay because spawn is also delayed!
self:__ElementAirborne(0.11, Element)
else
-- Get parking spot.
local spot=self:GetParkingSpot(Element, 10)
if spot then
-- Set
self:_SetElementParkingAt(Element, spot)
-- Trigger ElementParking event. Add a little delay because spawn is also delayed!
self:__ElementParking(0.11, Element)
else
-- TODO: This can happen if spawned on deck of a carrier!
self:E(self.lid..string.format("Element spawned not in air but not on any parking spot."))
self:__ElementParking(0.11, Element)
end
end
end
--- On after "ElementParking" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementParking(From, Event, To, Element)
self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A"))
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.PARKING)
if self:IsTakeoffCold() then
-- Wait for engine startup event.
elseif self:IsTakeoffHot() then
self:__ElementEngineOn(0.5, Element) -- delay a bit to allow all elements
elseif self:IsTakeoffRunway() then
self:__ElementEngineOn(0.5, Element)
end
end
--- On after "ElementEngineOn" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element)
-- Debug info.
self:T(self.lid..string.format("Element %s started engines", Element.name))
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.ENGINEON)
end
--- On after "ElementTaxiing" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element)
-- Get terminal ID.
local TerminalID=Element.parking and tostring(Element.parking.TerminalID) or "N/A"
-- Debug info.
self:I(self.lid..string.format("Element taxiing %s. Parking spot %s is now free", Element.name, TerminalID))
-- Set parking spot to free. Also for FC.
self:_SetElementParkingFree(Element)
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAXIING)
end
--- On after "ElementTakeoff" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil.
function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase)
self:I(self.lid..string.format("Element takeoff %s at %s airbase.", Element.name, airbase and airbase:GetName() or "unknown"))
-- Helos with skids just take off without taxiing!
if Element.parking then
self:_SetElementParkingFree(Element)
end
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAKEOFF, airbase)
-- Trigger element airborne event.
self:__ElementAirborne(10, Element)
end
--- On after "ElementAirborne" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element)
self:T2(self.lid..string.format("Element airborne %s", Element.name))
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.AIRBORNE)
end
--- On after "ElementLanded" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil.
function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase)
self:T2(self.lid..string.format("Element landed %s at %s airbase", Element.name, airbase and airbase:GetName() or "unknown"))
-- Helos with skids land directly on parking spots.
if self.ishelo then
local Spot=self:GetParkingSpot(Element, 10, airbase)
self:_SetElementParkingAt(Element, Spot)
end
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.LANDED, airbase)
end
--- On after "ElementArrived" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase, where the element arrived.
-- @param Wrapper.Airbase#AIRBASE.ParkingSpot Parking The Parking spot the element has.
function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking)
self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99))
self:_SetElementParkingAt(Element, Parking)
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.ARRIVED)
end
--- On after "ElementDead" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementDead(From, Event, To, Element)
self:T(self.lid..string.format("Element dead %s.", Element.name))
if self.flightcontrol and Element.parking then
self.flightcontrol:SetParkingFree(Element.parking)
end
-- Not parking any more.
Element.parking=nil
-- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD)
end
--- On after "FlightSpawned" event. Sets the template, initializes the waypoints.
-- @param #FLIGHTGROUP self
-- @param Wrapper.Group#GROUP Group Flight group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightSpawned(From, Event, To)
self:T(self.lid..string.format("Flight spawned!"))
if self.ai then
-- Set default ROE and ROT options.
self:SetOptionROE()
self:SetOptionROT()
-- TODO: make this input.
self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true)
self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner.
self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false)
--self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH)
-- Turn TACAN beacon on.
if self.tacanChannelDefault then
self:SwitchTACANOn(self.tacanChannelDefault, self.tacanMorseDefault)
end
-- Turn on the radio.
if self.radioFreqDefault then
self:SwitchRadioOn(self.radioFreqDefault, self.radioModuDefault)
end
else
-- F10 other menu.
self.menu=self.menu or {}
self.menu.atc=self.menu.atc or {}
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC")
self:_UpdateMenu()
end
end
--- On after "FlightParking" event. Add flight to flightcontrol of airbase.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightParking(From, Event, To)
self:T(self.lid..string.format("Flight is parking"))
local airbase=self.group:GetCoordinate():GetClosestAirbase()
local airbasename=airbase:GetName() or "unknown"
-- Parking time stamp.
self.Tparking=timer.getAbsTime()
-- Get FC of this airbase.
local flightcontrol=_DATABASE:GetFlightControl(airbasename)
if flightcontrol then
-- Set FC for this flight
self:SetFlightControl(flightcontrol)
if self.flightcontrol then
-- Set flight status.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.PARKING)
end
end
end
--- On after "FlightTaxiing" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightTaxiing(From, Event, To)
self:T(self.lid..string.format("Flight is taxiing"))
-- Parking over.
self.Tparking=nil
-- TODO: need a better check for the airbase.
local airbase=self.group:GetCoordinate():GetClosestAirbase(nil, self.group:GetCoalition())
if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then
-- Add AI flight to takeoff queue.
if self.ai then
-- AI flights go directly to TAKEOFF as we don't know when they finished taxiing.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAKEOFF)
else
-- Human flights go to TAXI OUT queue. They will go to the ready for takeoff queue when they request it.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIOUT)
end
end
end
--- On after "FlightTakeoff" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed.
function FLIGHTGROUP:onafterFlightTakeoff(From, Event, To, airbase)
self:T(self.lid..string.format("Flight takeoff from %s", airbase and airbase:GetName() or "unknown airbase"))
-- Remove flight from all FC queues.
if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then
self.flightcontrol:_RemoveFlight(self)
self.flightcontrol=nil
end
end
--- On after "FlightAirborne" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightAirborne(From, Event, To)
self:I(self.lid..string.format("Flight airborne"))
end
--- On after "FlightLanding" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightLanding(From, Event, To)
self:T(self.lid..string.format("Flight is landing"))
self:_SetElementStatusAll(FLIGHTGROUP.ElementStatus.LANDING)
end
--- On after "FlightLanded" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed.
function FLIGHTGROUP:onafterFlightLanded(From, Event, To, airbase)
self:T(self.lid..string.format("Flight landed at %s", airbase and airbase:GetName() or "unknown place"))
if self:IsLandingAt() then
self:LandedAt()
else
if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then
-- Add flight to taxiinb queue.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB)
end
end
end
--- On after "FlightArrived" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightArrived(From, Event, To)
self:T(self.lid..string.format("Flight arrived"))
-- Flight Control
if self.flightcontrol then
-- Add flight to arrived queue.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED)
end
-- Stop and despawn in 5 min.
self:__Stop(5*60)
end
--- On after "FlightDead" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFlightDead(From, Event, To)
self:I(self.lid..string.format("Flight dead!"))
-- Delete waypoints so they are re-initialized at the next spawn.
self.waypoints=nil
self.groupinitialized=false
-- Remove flight from all FC queues.
if self.flightcontrol then
self.flightcontrol:_RemoveFlight(self)
self.flightcontrol=nil
end
-- Cancel all mission.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
self:MissionCancel(mission)
mission:FlightDead(self)
end
-- Stop
self:Stop()
end
--- On before "UpdateRoute" event. Update route of group, e.g after new waypoints and/or waypoint tasks have been added.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number n Waypoint number.
-- @return #boolean Transision allowed?
function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n)
-- Is transition allowed? We assume yes until proven otherwise.
local allowed=true
local trepeat=nil
if self:IsAlive() and (self:IsAirborne() or self:IsWaiting() or self:IsInbound() or self:IsHolding()) then
-- Alive & Airborne ==> Update route possible.
self:T3(self.lid.."Update route possible. Group is ALIVE and AIRBORNE or WAITING or INBOUND or HOLDING")
elseif self:IsDead() then
-- Group is dead! No more updates.
self:E(self.lid.."Update route denied. Group is DEAD!")
allowed=false
else
-- Not airborne yet. Try again in 1 sec.
self:T3(self.lid.."FF update route denied ==> checking back in 5 sec")
trepeat=-5
allowed=false
end
if n and n<1 then
self:E(self.lid.."Update route denied because waypoint n<1!")
allowed=false
end
if not self.currentwp then
self:E(self.lid.."Update route denied because self.currentwp=nil!")
allowed=false
end
local N=n or self.currentwp+1
if not N or N<1 then
self:E(self.lid.."FF update route denied because N=nil or N<1")
trepeat=-5
allowed=false
end
if self.taskcurrent>0 then
self:E(self.lid.."Update route denied because taskcurrent>0")
allowed=false
end
-- Not good, because mission will never start. Better only check if there is a current task!
--if self.currentmission then
--end
-- Debug info.
self:T2(self.lid..string.format("Onbefore Updateroute allowed=%s state=%s repeat in %s", tostring(allowed), self:GetState(), tostring(trepeat)))
if trepeat then
self:__UpdateRoute(trepeat, n)
end
return allowed
end
--- On after "UpdateRoute" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number n Waypoint number. Default is next waypoint.
function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n)
-- Update route from this waypoint number onwards.
n=n or self.currentwp+1
-- Update waypoint tasks, i.e. inject WP tasks into waypoint table.
self:_UpdateWaypointTasks()
-- Waypoints.
local wp={}
-- Current velocity.
local speed=self.group and self.group:GetVelocityKMH() or 100
-- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP.
local current=self.group:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current")
table.insert(wp, current)
-- Add remaining waypoints to route.
for i=n, #self.waypoints do
table.insert(wp, self.waypoints[i])
end
-- Debug info.
local hb=self.homebase and self.homebase:GetName() or "unknown"
local db=self.destbase and self.destbase:GetName() or "unknown"
self:T(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db))
if #wp>1 then
-- Route group to all defined waypoints remaining.
self:Route(wp, 1)
else
---
-- No waypoints left
---
self:_CheckFlightDone()
end
end
--- On after "Respawn" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #table Template The template used to respawn the group.
function FLIGHTGROUP:onafterRespawn(From, Event, To, Template)
self:T(self.lid.."Respawning group!")
local template=UTILS.DeepCopy(Template or self.template)
if self.group and self.group:InAir() then
template.lateActivation=false
self.respawning=true
self.group=self.group:Respawn(template)
end
end
--- On after "PassingWaypoint" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number n Waypoint number passed.
-- @param #number N Final waypoint number.
function FLIGHTGROUP:onafterPassingWaypoint(From, Event, To, n, N)
local text=string.format("Flight passed waypoint %d/%d", n, N)
self:T(self.lid..text)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
-- Get all waypoint tasks.
local tasks=self:GetTasksWaypoint(n)
-- Debug info.
local text=string.format("WP %d/%d tasks:", n, N)
if #tasks>0 then
for i,_task in pairs(tasks) do
local task=_task --#FLIGHTGROUP.Task
text=text..string.format("\n[%d] %s", i, task.description)
end
else
text=text.." None"
end
self:T(self.lid..text)
-- Tasks at this waypoints.
local taskswp={}
-- TODO: maybe set waypoint enroute tasks?
for _,task in pairs(tasks) do
local Task=task --#FLIGHTGROUP.Task
-- Task execute.
table.insert(taskswp, self.group:TaskFunction("FLIGHTGROUP._TaskExecute", self, Task))
-- Stop condition if userflag is set to 1 or task duration over.
local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration)
-- Controlled task.
table.insert(taskswp, self.group:TaskControlled(Task.dcstask, TaskCondition))
-- Task done.
table.insert(taskswp, self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task))
end
-- Execute waypoint tasks.
if #taskswp>0 then
self:SetTask(self.group:TaskCombo(taskswp))
end
-- Final AIR waypoint reached?
if n==N then
-- Set switch to true.
self.passedfinalwp=true
-- Check if all tasks/mission are done? If so, RTB or WAIT.
-- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there.
if #taskswp==0 then
self:_CheckFlightDone(1)
end
end
end
--- Check if flight is done, i.e.
--
-- * passed the final waypoint,
-- * no current task
-- * no current mission
-- * number of remaining tasks is zero
-- * number of remaining missions is zero
--
-- @param #FLIGHTGROUP self
-- @param #number delay Delay in seconds.
function FLIGHTGROUP:_CheckFlightDone(delay)
if self:IsAlive() then
if delay and delay>0 then
-- Delayed call.
self:ScheduleOnce(delay, FLIGHTGROUP._CheckFlightDone, self)
else
-- First check if there is a paused mission that
if self.missionpaused then
self:UnpauseMission()
return
end
-- Number of tasks remaining.
local nTasks=self:CountRemainingTasks()
-- Number of mission remaining.
local nMissions=self:CountRemainingMissison()
-- Final waypoint passed?
if self.passedfinalwp then
-- Got current mission or task?
if self.currentmission==nil and self.taskcurrent==0 then
-- Number of remaining tasks/missions?
if nTasks==0 and nMissions==0 then
-- Send flight to destination.
if self.destbase then
self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!")
self:__RTB(-1, self.destbase)
elseif self.destzone then
self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!")
self:__RTZ(-1, self.destzone)
else
self:I(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!")
self:__Wait(-1)
end
else
self:I(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions))
self:__Wait(-1)
end
else
self:I(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission)))
end
else
self:I(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState()))
self:__UpdateRoute(-1)
end
end
end
end
--- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number n The goto waypoint number.
function FLIGHTGROUP:onafterGotoWaypoint(From, Event, To, n)
-- The last waypoint passed was n-1
self.currentwp=n-1
-- TODO: switch to re-enable waypoint tasks.
if false then
local tasks=self:GetTasksWaypoint(n)
for _,_task in pairs(tasks) do
local task=_task --#FLIGHTGROUP.Task
task.status=FLIGHTGROUP.TaskStatus.SCHEDULED
end
end
-- Update the route.
self:UpdateRoute()
end
--- On before "RTB" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at.
-- @param #number SpeedTo Speed used for travelling from current position to holding point in knots.
-- @param #number SpeedHold Holding speed in knots.
function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold)
local allowed=true
local Tsuspend=nil
if airbase==nil then
self:E(self.lid.."ERROR: Airbase is nil in RTB() call!")
allowed=false
end
-- Check that coaliton is okay. We allow same (blue=blue, red=red) or landing on neutral bases.
if airbase and airbase:GetCoalition()~=self.group:GetCoalition() and airbase:GetCoalition()>0 then
self:E(self.lid.."ERROR: Wrong airbase coalition in RTB() call! We allow only same as group or neutral airbases.")
allowed=false
end
if not self.group:IsAirborne(true) then
self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 10 sec."))
allowed=false
Tsuspend=-10
end
-- Only if fuel is not low or critical.
if not (self:IsFuelLow() or self:IsFuelCritical()) then
-- Check if there are remaining tasks.
local Ntot,Nsched, Nwp=self:CountRemainingTasks()
if self.taskcurrent>0 then
self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec."))
Tsuspend=-10
allowed=false
end
if Nsched>0 then
self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec.", Nsched))
Tsuspend=-10
allowed=false
end
if Nwp>0 then
self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec.", Nwp))
Tsuspend=-10
allowed=false
end
end
if Tsuspend and not allowed then
self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold)
end
return allowed
end
--- On after "RTB" event. Order flight to hold at an airbase and wait for signal to land.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at.
-- @param #number SpeedTo Speed used for traveling from current position to holding point in knots. Default 75% of max speed.
-- @param #number SpeedHold Holding speed in knots. Default 250 kts.
-- @param #number SpeedLand Landing speed in knots. Default 170 kts.
function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, SpeedLand)
self:I(self.lid..string.format("RTB: event=%s: %s --> %s to %s", Event, From, To, airbase:GetName()))
-- Set the destination base.
self.destbase=airbase
-- Clear holding time in any case.
self.Tholding=nil
-- Defaults:
SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise)
SpeedHold=SpeedHold or (self.ishelo and 80 or 250)
SpeedLand=SpeedLand or (self.ishelo and 40 or 170)
-- Debug message.
local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d", airbase:GetName(), SpeedTo, SpeedHold, SpeedLand)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000
-- Holding points.
local c0=self.group:GetCoordinate()
local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold))
local p1=nil
local wpap=nil
-- Do we have a flight control?
local fc=_DATABASE:GetFlightControl(airbase:GetName())
if fc then
-- Get holding point from flight control.
local HoldingPoint=fc:_GetHoldingpoint(self)
p0=HoldingPoint.pos0
p1=HoldingPoint.pos1
-- Debug marks.
if self.Debug then
p0:MarkToAll("Holding point P0")
p1:MarkToAll("Holding point P1")
end
-- Set flightcontrol for this flight.
self:SetFlightControl(fc)
-- Add flight to inbound queue.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.INBOUND)
end
-- Altitude above ground for a glide slope of 3 degrees.
local x1=self.ishelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10)
local x2=self.ishelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5)
local alpha=math.rad(3)
local h1=x1*math.tan(alpha)
local h2=x2*math.tan(alpha)
local runway=airbase:GetActiveRunway()
-- Clear all tasks.
self:ClearTasks()
-- Set holding flag to 0=false.
self.flaghold:Set(0)
local holdtime=5*60
if fc or self.airboss then
holdtime=nil
end
-- Task fuction when reached holding point.
local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self)
-- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance.
local TaskOrbit = self.group:TaskOrbit(p0, nil, UTILS.KnotsToMps(SpeedHold), p1)
local TaskLand = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, holdtime)
local TaskHold = self.group:TaskControlled(TaskOrbit, TaskLand)
local TaskKlar = self.group:TaskFunction("FLIGHTGROUP._ClearedToLand", self) -- Once the holding flag becomes true, set trigger FLIGHTLANDING, i.e. set flight STATUS to LANDING.
-- Waypoints from current position to holding point.
local wp={}
wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos")
wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point")
-- Approach point: 10 NN in direction of runway.
if airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME then
---
-- Airdrome
---
local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1)
wp[#wp+1]=papp:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach")
-- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach.
local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2)
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing")
elseif airbase:GetAirbaseCategory()==Airbase.Category.SHIP then
---
-- Ship
---
local pland=airbase:GetCoordinate()
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing")
end
local routeto=false
if fc or world.event.S_EVENT_KILL then
routeto=true
end
-- Respawn?
if routeto then
--self:I(self.lid.."FF route (not repawn)")
-- Just route the group. Respawn might happen when going from holding to final.
self:Route(wp, 1)
else
--self:I(self.lid.."FF respawn (not route)")
-- Get group template.
local Template=self.group:GetTemplate()
-- Set route points.
Template.route.points=wp
--Respawn the group with new waypoints.
self:Respawn(Template)
end
end
--- On before "Wait" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position.
-- @param #number Altitude Altitude in feet. Default 10000 ft.
-- @param #number Speed Speed in knots. Default 250 kts.
function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed)
local allowed=true
local Tsuspend=nil
-- Check if there are remaining tasks.
local Ntot,Nsched, Nwp=self:CountRemainingTasks()
if self.taskcurrent>0 then
self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 10 sec."))
Tsuspend=-10
allowed=false
end
if Nsched>0 then
self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched))
Tsuspend=-10
allowed=false
end
if Nwp>0 then
self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp))
Tsuspend=-10
allowed=false
end
if Tsuspend and not allowed then
self:__Wait(Tsuspend, Coord, Altitude, Speed)
end
return allowed
end
--- On after "Wait" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position.
-- @param #number Altitude Altitude in feet. Default 10000 ft.
-- @param #number Speed Speed in knots. Default 250 kts.
function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed)
Coord=Coord or self.group:GetCoordinate()
Altitude=Altitude or (self.ishelo and 1000 or 10000)
Speed=Speed or (self.ishelo and 80 or 250)
-- Debug message.
local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
--TODO: set ROE passive. introduce roe event/state/variable.
-- Orbit task.
local TaskOrbit=self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed))
-- Set task.
self:SetTask(TaskOrbit)
end
--- On after "Refuel" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The coordinate.
function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate)
-- Debug message.
local text=string.format("Flight group set to refuel at the nearest tanker")
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
--TODO: set ROE passive. introduce roe event/state/variable.
--TODO: cancel current task
self:PauseMission()
-- Refueling task.
local TaskRefuel=self.group:TaskRefueling()
local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling", self)
local DCSTasks={TaskRefuel, TaskFunction}
local Speed=self.speedCruise
local coordinate=self.group:GetCoordinate()
Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5), self.group:GetHeading(), true)
local wp0=coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true)
local wp9=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, "Refuel")
self:Route({wp0, wp9})
end
--- On after "Refueled" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterRefueled(From, Event, To)
-- Debug message.
local text=string.format("Flight group finished refuelling")
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Check if flight is done.
self:_CheckFlightDone(1)
end
--- On after "Holding" event. Flight arrived at the holding point.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterHolding(From, Event, To)
-- Set holding flag to 0 (just in case).
self.flaghold:Set(0)
-- Holding time stamp.
self.Tholding=timer.getAbsTime()
local text=string.format("Flight group %s is HOLDING now", self.groupname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Add flight to waiting/holding queue.
if self.flightcontrol then
-- Set flight status to holding
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.HOLDING)
if not self.ai then
self:_UpdateMenu()
end
elseif self.airboss then
if self.ishelo then
local carrierpos=self.airboss:GetCoordinate()
local carrierheading=self.airboss:GetHeading()
local Distance=UTILS.NMToMeters(5)
local Angle=carrierheading+90
local altitude=math.random(12, 25)*100
local oc=carrierpos:Translate(Distance,Angle):SetAltitude(altitude, true)
-- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance.
local TaskOrbit=self.group:TaskOrbit(oc, nil, UTILS.KnotsToMps(50))
local TaskLand=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1)
local TaskHold=self.group:TaskControlled(TaskOrbit, TaskLand)
local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand", self) -- Once the holding flag becomes true, set trigger FLIGHTLANDING, i.e. set flight STATUS to LANDING.
local DCSTask=self.group:TaskCombo({TaskOrbit, TaskHold, TaskKlar})
self:SetTask(DCSTask)
end
end
end
--- On after "EngageTargets" event. Order to engage a set of units.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Set#SET_UNIT TargetUnitSet
function FLIGHTGROUP:onafterEngageTargets(From, Event, To, TargetUnitSet)
local DCSTasks={}
for _,_unit in paris(TargetUnitSet:GetSet()) do
local unit=_unit --Wrapper.Unit#UNIT
local task=self.group:TaskAttackUnit(unit, true)
table.insert(DCSTasks)
end
-- Task combo.
local DCSTask=self.group:TaskCombo(DCSTasks)
--TODO needs a task function that calls EngageDone or so event and updates the route again.
-- Lets try if pushtask actually leaves the remaining tasks untouched.
self:SetTask(DCSTask)
end
--- On before "LandAt" event. Check we have a helo group.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to land. Default is current position.
-- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min).
function FLIGHTGROUP:onbeforeLandAt(From, Event, To, Coordinate, Duration)
return self.ishelo
end
--- On after "LandAt" event. Order helicopter to land at a specific point.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to land. Default is current position.
-- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min).
function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration)
-- Duration.
Duration=Duration or 600
Coordinate=Coordinate or self:GetCoordinate()
local DCStask=self.group:TaskLandAtVec2(Coordinate:GetVec2(), Duration)
local Task=self:NewTaskScheduled(DCStask, 1, "Task_Land_At", 0)
-- Add task with high priority.
--self:AddTask(task, 1, "Task_Land_At", 0)
self:TaskExecute(Task)
end
--- On after "DetectedUnit" event. Add newly detected unit to detected units set.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Unit#UNIT Unit The detected unit.
function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit)
self:T2(self.lid..string.format("Detected unit %s", Unit:GetName()))
self.detectedunits:AddUnit(Unit)
end
--- On after "DetectedUnitNew" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Unit#UNIT Unit The detected unit.
function FLIGHTGROUP:onafterDetectedUnitNew(From, Event, To, Unit)
self:T(self.lid..string.format("Detected New unit %s", Unit:GetName()))
end
--- On after "FuelLow" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFuelLow(From, Event, To)
-- Debug message.
local text=string.format("Low fuel for flight group %s", self.groupname)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Set switch to true.
self.fuellow=true
-- Back to destination or home.
local airbase=self.destbase or self.homebase
if self.airwing then
-- Get closest tanker from airwing that can refuel this flight.
local tanker=self.airwing:GetTankerForFlight(self)
if tanker then
-- Send flight to tanker with refueling task.
self:Refuel(tanker.flightgroup:GetCoordinate())
else
if airbase and self.fuellowrtb then
self:RTB(airbase)
--TODO: RTZ
end
end
else
if self.fuellowrefuel and self.refueltype then
local tanker=self:FindNearestTanker(50)
if tanker then
self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName()))
self:Refuel()
return
end
end
if airbase and self.fuellowrtb then
self:RTB(airbase)
--TODO: RTZ
end
end
end
--- On after "FuelCritical" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterFuelCritical(From, Event, To)
-- Debug message.
local text=string.format("Critical fuel for flight group %s", self.groupname)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Set switch to true.
self.fuelcritical=true
-- Airbase.
local airbase=self.destbase or self.homebase
if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel() then
self:RTB(airbase)
--TODO: RTZ
end
end
--- On after "EnterZone" event. Sets self.inzones[zonename]=true.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE Zone The zone that the group entered.
function FLIGHTGROUP:onafterEnterZone(From, Event, To, Zone)
local zonename=Zone and Zone:GetName() or "unknown"
self:T2(self.lid..string.format("Entered Zone %s", zonename))
self.inzones:Add(Zone:GetName(), Zone)
end
--- On after "LeaveZone" event. Sets self.inzones[zonename]=false.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Zone#ZONE Zone The zone that the group entered.
function FLIGHTGROUP:onafterLeaveZone(From, Event, To, Zone)
local zonename=Zone and Zone:GetName() or "unknown"
self:T2(self.lid..string.format("Left Zone %s", zonename))
self.inzones:Remove(zonename, true)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Task functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get the unfinished waypoint tasks
-- @param #FLIGHTGROUP self
-- @param #number n Waypoint index. Counting starts at one.
-- @return #table Table of tasks. Table could also be empty {}.
function FLIGHTGROUP:GetTasksWaypoint(n)
-- Tasks table.
local tasks={}
-- Sort queue.
self:_SortTaskQueue()
-- Look for first task that SCHEDULED.
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.type==FLIGHTGROUP.TaskType.WAYPOINT and task.status==FLIGHTGROUP.TaskStatus.SCHEDULED and task.waypoint==n then
table.insert(tasks, task)
end
end
return tasks
end
--- Sort task queue.
-- @param #FLIGHTGROUP self
function FLIGHTGROUP:_SortTaskQueue()
-- Sort results table wrt prio and then start time.
local function _sort(a, b)
local taskA=a --#FLIGHTGROUP.Task
local taskB=b --#FLIGHTGROUP.Task
return (taskA.prio<taskB.prio) or (taskA.prio==taskB.prio and taskA.time<taskB.time)
end
--TODO: only needs to be sorted if a task was added, is done, or was removed.
table.sort(self.taskqueue, _sort)
end
--- Count the number of tasks that still pending in the queue.
-- @param #FLIGHTGROUP self
-- @return #number Total number of tasks remaining.
-- @return #number Number of SCHEDULED tasks remaining.
-- @return #number Number of WAYPOINT tasks remaining.
function FLIGHTGROUP:CountRemainingTasks()
local Ntot=0
local Nwp=0
local Nsched=0
-- Loop over tasks queue.
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
-- Task is still scheduled.
if task.status==FLIGHTGROUP.TaskStatus.SCHEDULED then
-- Total number of tasks.
Ntot=Ntot+1
if task.type==FLIGHTGROUP.TaskType.WAYPOINT then
--TODO: maybe check that waypoint was not already passed?
Nwp=Nwp+1
elseif task.type==FLIGHTGROUP.TaskType.SCHEDULED then
Nsched=Nsched+1
end
end
end
return Ntot, Nsched, Nwp
end
--- Count remaining missons.
-- @param #FLIGHTGROUP self
-- @return #number Number of missions to be done.
function FLIGHTGROUP:CountRemainingMissison()
local N=0
-- Loop over mission queue.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission and mission:IsNotOver() then
-- Get flight status.
local status=mission:GetFlightStatus(self)
if status~=AUFTRAG.FlightStatus.DONE and status~=AUFTRAG.FlightStatus.CANCELLED then
N=N+1
end
end
end
return N
end
--- On after TaskExecute event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Task Task The task.
function FLIGHTGROUP:onafterTaskExecute(From, Event, To, Task)
-- Debug message.
local text=string.format("Task %s ID=%d execute.", tostring(Task.description), Task.id)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Cancel current task if there is any.
if self.taskcurrent>0 then
self:TaskCancel()
end
-- Set current task.
self.taskcurrent=Task.id
-- Set time stamp.
Task.timestamp=timer.getAbsTime()
-- Task status executing.
Task.status=FLIGHTGROUP.TaskStatus.EXECUTING
if Task.dcstask.id=="Formation" then
-- Set of group(s) to follow Mother.
local followSet=SET_GROUP:New():AddGroup(self.group)
local param=Task.dcstask.params
local followUnit=UNIT:FindByName(param.unitname)
-- Define AI Formation object.
Task.formation=AI_FORMATION:New(followUnit, followSet, "Formation", "Follow X at given parameters.")
-- Formation parameters.
Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50)
-- Set follow time interval.
Task.formation:SetFollowTimeInterval(param.dtFollow)
-- Formation mode.
Task.formation:SetFlightModeFormation(self.group)
-- Start formation FSM.
Task.formation:Start()
else
-- If task is scheduled (not waypoint) set task.
if Task.type==FLIGHTGROUP.TaskType.SCHEDULED then
local DCStasks={}
if Task.dcstask.id=='ComboTask' then
-- Loop over all combo tasks.
for TaskID, Task in ipairs(Task.dcstask.params.tasks) do
table.insert(DCStasks, Task)
end
else
table.insert(DCStasks, Task.dcstask)
end
-- Combo task.
local TaskCombo=self.group:TaskCombo(DCStasks)
-- Stop condition!
local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration)
-- Controlled task.
local TaskControlled=self.group:TaskControlled(TaskCombo, TaskCondition)
-- Task done.
local TaskDone=self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task)
-- Final task.
local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone})
-- Set task for group.
self:SetTask(TaskFinal, 1)
end
end
-- Get mission of this task (if any).
local Mission=self:GetMissionByTaskID(self.taskcurrent)
if Mission then
-- Set AUFTRAG status.
self:MissionExecute(Mission)
end
end
--- On after "TaskPause" event. Pauses the current task.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterTaskPause(From, Event, To)
local Task=self:GetTaskCurrent()
if Task then
-- Clear all tasks.
self:ClearTasks()
-- Task status executing.
Task.status=FLIGHTGROUP.TaskStatus.PAUSED
end
end
--- On after "TaskUnPause" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterTaskUnpause(From, Event, To)
--self.task
end
--- On after "TaskCancel" event. Cancels the current task or simply sets the status to DONE if the task is not the current one.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Task Task The task to cancel. Default is the current task (if any).
function FLIGHTGROUP:onafterTaskCancel(From, Event, To, Task)
-- Get current task.
local currenttask=self:GetTaskCurrent()
-- If no task, we take the current task. But this could also be *nil*!
Task=Task or currenttask
if Task then
-- Check if the task is the current task?
if currenttask and Task.id==currenttask.id then
-- Current stop flag value. I noticed cases, where setting the flag to 1 would not cancel the task, e.g. when firing HARMS on a dead ship.
local stopflag=Task.stopflag:Get()
-- Debug info.
local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)", Task.description, Task.id, Task.stopflag:GetName(), stopflag)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone()
Task.stopflag:Set(1)
if Task.dcstask.id=="Formation" then
Task.formation:Stop()
self:TaskDone(Task)
elseif stopflag==1 then
-- Manual call TaskDone if setting flag to one was not successful.
self:TaskDone(Task)
end
else
-- Debug info.
self:I(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE", Task.description, Task.id))
-- Call task done function.
self:TaskDone(Task)
--[[
local mission=self:GetMissionByTaskID(Task.id)
-- Is this a waypoint task?
if Task.type==FLIGHTGROUP.TaskType.WAYPOINT and Task.waypoint then
-- Check that this is a mission waypoint and no other tasks are defined here.
if mission and #self:GetTasksWaypoint(Task.waypoint)==0 then
self:RemoveWaypoint(Task.waypoint)
end
end
]]
end
else
local text=string.format("WARNING: No (current) task to cancel!")
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:E(self.lid..text)
end
end
--- On before "TaskDone" event. Deny transition if task status is PAUSED.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Task Task
function FLIGHTGROUP:onbeforeTaskDone(From, Event, To, Task)
local allowed=true
if Task.status==FLIGHTGROUP.TaskStatus.PAUSED then
allowed=false
end
return allowed
end
--- On after "TaskDone" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FLIGHTGROUP.Task Task
function FLIGHTGROUP:onafterTaskDone(From, Event, To, Task)
-- Debug message.
local text=string.format("Task done: %s ID=%d", Task.description, Task.id)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
-- No current task.
if Task.id==self.taskcurrent then
self.taskcurrent=0
end
-- Task status done.
Task.status=FLIGHTGROUP.TaskStatus.DONE
-- Check if this task was the task of the current mission ==> Mission Done!
local Mission=self:GetMissionByTaskID(Task.id)
if Mission and Mission:IsNotOver() then
local status=Mission:GetFlightStatus(self)
if status~=AUFTRAG.FlightStatus.PAUSED then
self:I(self.lid.."FF Task Done ==> Mission Done!")
self:MissionDone(Mission)
else
--Mission paused. Do nothing!
end
else
self:I(self.lid.."FF Task Done but NO mission found ==> _CheckFlightDone in 1 sec")
self:_CheckFlightDone(1)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Mission functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On before "MissionStart" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission table.
function FLIGHTGROUP:onbeforeMissionStart(From, Event, To, Mission)
-- Debug info.
self:I(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled())))
-- Delay for route to mission. Group needs to be activated and controlled.
local delay=1
-- Check if group is spawned.
if self:IsInUtero() then
-- Activate group if it is late activated.
if self:IsLateActivated() then
self:Activate(delay)
delay=delay+1
end
end
-- Startup group if it is uncontrolled.
if self:IsParking() and self:IsUncontrolled() then
self:StartUncontrolled(delay)
else
--env.info("FF hallo!")
--self:StartUncontrolled(5)
end
return true
end
--- On after "MissionStart" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission table.
function FLIGHTGROUP:onafterMissionStart(From, Event, To, Mission)
-- Debug output.
local text=string.format("Starting %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName())
self:T(self.lid..text)
MESSAGE:New(text, 30, self.groupname):ToAllIf(true)
-- Set current mission.
self.currentmission=Mission.auftragsnummer
-- Set flight mission status to STARTED.
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.STARTED)
-- Set mission status to STARTED.
Mission:Started()
-- Route flight to mission zone.
self:RouteToMission(Mission, 3)
end
--- On after "MissionExecute" event. Mission execution began.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission table.
function FLIGHTGROUP:onafterMissionExecute(From, Event, To, Mission)
local text=string.format("Executing %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName())
self:T(self.lid..text)
MESSAGE:New(text, 30, self.groupname):ToAllIf(true)
-- Set flight mission status to EXECUTING.
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.EXECUTING)
-- Set mission status to EXECUTING.
Mission:Executing()
end
--- On after "PauseMission" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterPauseMission(From, Event, To)
local Mission=self:GetMissionCurrent()
if Mission then
-- Set flight mission status to PAUSED.
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.PAUSED)
-- Get mission waypoint task.
local Task=Mission:GetFlightWaypointTask(self)
-- Debug message.
self:I(self.lid..string.format("FF pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF")))
-- Cancelling the mission is actually cancelling the current task.
self:TaskCancel(Task)
-- Set mission to pause so we can unpause it later.
self.missionpaused=Mission
end
end
--- On after "UnpauseMission" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterUnpauseMission(From, Event, To)
self:I(self.lid..string.format("Unpausing mission"))
if self.missionpaused then
local mission=self:GetMissionByID(self.missionpaused.auftragsnummer)
self:MissionStart(mission)
self.missionpaused=nil
else
self:E(self.lid.."ERROR: No mission to unpause!")
end
end
--- On after "MissionCancel" event. Cancels the mission.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled.
function FLIGHTGROUP:onafterMissionCancel(From, Event, To, Mission)
if self.currentmission and Mission.auftragsnummer==self.currentmission then
-- Get mission waypoint task.
local Task=Mission:GetFlightWaypointTask(self)
-- Debug info.
self:I(self.lid..string.format("FF Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF")))
-- Cancelling the mission is actually cancelling the current task.
-- Note that two things can happen.
-- 1.) Flight is still on the way to the waypoint (status should be STARTED). In this case there would not be a current task!
-- 2.) Flight already passed the mission waypoint (status should be EXECUTING).
self:TaskCancel(Task)
else
-- Not the current mission.
-- TODO: remove mission from queue?
-- Set mission flight status.
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.CANCELLED)
-- Send flight RTB or WAIT if nothing left to do.
self:_CheckFlightDone(1)
end
end
--- On after "MissionDone" event.
-- @param #FLIGHTGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission
function FLIGHTGROUP:onafterMissionDone(From, Event, To, Mission)
-- Debug info.
local text=string.format("Mission %s DONE!", Mission.name)
self:I(self.lid..text)
MESSAGE:New(text, 30, self.groupname):ToAllIf(true)
-- Set Flight status.
Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.DONE)
-- Set current mission to nil.
if self.currentmission and Mission.auftragsnummer==self.currentmission then
self.currentmission=nil
end
-- Remove mission waypoint.
local wpidx=Mission:GetFlightWaypointIndex(self)
if wpidx then
self:RemoveWaypoint(wpidx)
end
-- Decrease patrol data.
if Mission.patroldata then
Mission.patroldata.noccupied=Mission.patroldata.noccupied-1
AIRWING.UpdatePatrolPointMarker(Mission.patroldata)
end
-- TODO: reset mission specific parameters like radio, ROE etc.
-- Check if flight is done.
self:_CheckFlightDone(1)
end
--- Route group to mission.
-- @param #FLIGHTGROUP self
-- @param Ops.Auftrag#AUFTRAG mission The mission table.
-- @param #number delay Delay in seconds.
function FLIGHTGROUP:RouteToMission(mission, delay)
if delay and delay>0 then
-- Delayed call.
self:ScheduleOnce(delay, FLIGHTGROUP.RouteToMission, self, mission)
else
-- Next waypoint.
local nextwaypoint=self.currentwp+1
-- Create waypoint coordinate half way between us and the target.
local waypointcoord=self.group:GetCoordinate():GetIntermediateCoordinate(mission:GetTargetCoordinate(), mission.missionFraction)
-- Add some randomization.
waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 500):GetRandomCoordinate()
-- Set altitude of mission waypoint.
if mission.missionAltitude then
waypointcoord:SetAltitude(mission.missionAltitude, true)
end
-- Add enroute tasks.
for _,task in pairs(mission.enrouteTasks) do
self:AddTaskEnroute(task)
end
-- Add waypoint.
self:AddWaypointAir(waypointcoord, nextwaypoint, UTILS.KmphToKnots(self.speedCruise), false)
-- Special for Troop transport.
if mission.type==AUFTRAG.Type.TROOPTRANSPORT then
-- Refresh DCS task with the known controllable.
mission.DCStask=mission:GetDCSMissionTask(self.group)
-- Add task to embark for the troops.
for _,_group in pairs(mission.transportGroupSet.Set) do
local group=_group --Wrapper.Group#GROUP
if group and group:IsAlive() then
local DCSTask=group:TaskEmbarkToTransport(mission.transportPickup, 500)
group:SetTask(DCSTask, 5)
end
end
end
-- Add waypoint task. UpdateRoute is called inside.
local waypointtask=self:AddTaskWaypoint(mission.DCStask, nextwaypoint, mission.name, mission.prio, mission.duration)
-- Set waypoint task.
mission:SetFlightWaypointTask(self, waypointtask)
-- Set waypoint index.
mission:SetFlightWaypointIndex(self, nextwaypoint)
---
-- Mission Specific Settings
---
-- ROE
if mission.optionROE then
self:SetOptionROE(mission.optionROE)
end
-- ROT
if mission.optionROT then
self:SetOptionROT(mission.optionROT)
end
-- Radio
if mission.radioFreq then
self:SwitchRadioOn(mission.radioFreq, mission.radioModu)
end
-- TACAN
if mission.tacanChannel then
self:SwitchTACANOn(mission.tacanChannel, mission.tacanMorse)
end
-- Formation
if mission.optionFormation then
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Special Task Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called when a task is executed.
--@param Wrapper.Group#GROUP group Group which should execute the task.
--@param #FLIGHTGROUP flightgroup Flight group.
--@param #FLIGHTGROUP.Task task Task.
function FLIGHTGROUP._TaskExecute(group, flightgroup, task)
-- Debug message.
local text=string.format("_TaskExecute %s", task.description)
flightgroup:T3(flightgroup.lid..text)
-- Set current task to nil so that the next in line can be executed.
if flightgroup then
flightgroup:TaskExecute(task)
end
end
--- Function called when a task is done.
--@param Wrapper.Group#GROUP group Group for which the task is done.
--@param #FLIGHTGROUP flightgroup Flight group.
--@param #FLIGHTGROUP.Task task Task.
function FLIGHTGROUP._TaskDone(group, flightgroup, task)
-- Debug message.
local text=string.format("_TaskDone %s", task.description)
flightgroup:T3(flightgroup.lid..text)
-- Set current task to nil so that the next in line can be executed.
if flightgroup then
flightgroup:TaskDone(task)
end
end
--- Function called when a group is passing a waypoint.
--@param Wrapper.Group#GROUP group Group that passed the waypoint
--@param #FLIGHTGROUP flightgroup Flightgroup object.
--@param #number i Waypoint number that has been reached.
function FLIGHTGROUP._PassingWaypoint(group, flightgroup, i)
local final=#flightgroup.waypoints or 1
-- Debug message.
local text=string.format("Group passing waypoint %d of %d.", i, final)
flightgroup:T3(flightgroup.lid..text)
-- Set current waypoint.
flightgroup.currentwp=i
-- Trigger PassingWaypoint event.
flightgroup:PassingWaypoint(i, final)
end
--- Function called when flight has reached the holding point.
-- @param Wrapper.Group#GROUP group Group object.
-- @param #FLIGHTGROUP flightgroup Flight group object.
function FLIGHTGROUP._ReachedHolding(group, flightgroup)
flightgroup:I(flightgroup.lid..string.format("Group reached holding point"))
-- Trigger Holding event.
flightgroup:__Holding(-1)
end
--- Function called when flight has reached the holding point.
-- @param Wrapper.Group#GROUP group Group object.
-- @param #FLIGHTGROUP flightgroup Flight group object.
function FLIGHTGROUP._ClearedToLand(group, flightgroup)
flightgroup:I(flightgroup.lid..string.format("Group was cleared to land"))
-- Trigger FlightLanding event.
flightgroup:__FlightLanding(-1)
end
--- Function called when flight finished refuelling.
-- @param Wrapper.Group#GROUP group Group object.
-- @param #FLIGHTGROUP flightgroup Flight group object.
function FLIGHTGROUP._FinishedRefuelling(group, flightgroup)
flightgroup:T(flightgroup.lid..string.format("Group finished refueling"))
-- Trigger Holding event.
flightgroup:__Refueled(-1)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:_InitGroup()
-- First check if group was already initialized.
if self.groupinitialized then
self:E(self.lid.."WARNING: Group was already initialized!")
return
end
-- Get template of group.
self.template=self.group:GetTemplate()
-- Helo group.
self.ishelo=self.group:IsHelicopter()
-- Is (template) group uncontrolled.
self.isUncontrolled=self.template.uncontrolled
-- Is (template) group late activated.
self.isLateActivated=self.template.lateActivation
-- Max speed in km/h.
self.speedmax=self.group:GetSpeedMax()
-- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings.
local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350)
-- Cruise speed: 70% of max speed but within limit.
self.speedCruise=math.min(self.speedmax*0.7, speedCruiseLimit)
-- Initial fuel mass.
-- TODO: this is a unit property!
self.fuelmass=0
self.traveldist=0
self.traveltime=timer.getAbsTime()
self.position=self:GetCoordinate()
-- Radio parameters from template.
self.radioOn=self.template.communication
self.radioFreq=self.template.frequency
self.radioModu=self.template.modulation
-- If not set by the use explicitly yet, we take the template values as defaults.
if not self.radioFreqDefault then
self.radioFreqDefault=self.radioFreq
self.radioModuDefault=self.radioModu
end
-- Get first unit. This is used to extract other parameters.
local unit=self.group:GetUnit(1)
if unit then
self.rangemax=unit:GetRange()
self.descriptors=unit:GetDesc()
self.actype=unit:GetTypeName()
self.ceiling=self.descriptors.Hmax
self.tankertype=select(2, unit:IsTanker())
self.refueltype=select(2, unit:IsRefuelable())
self.ai=not self:_IsHuman(self.group)
-- Init waypoints.
if not self.waypoints then
self:InitWaypoints()
end
-- Debug info.
local text=string.format("Initialized Flight Group %s:\n", self.groupname)
text=text..string.format("AC type = %s\n", self.actype)
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax))
text=text..string.format("Range max = %.1f km\n", self.rangemax/1000)
text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling))
text=text..string.format("Tanker type = %s\n", tostring(self.tankertype))
text=text..string.format("Refuel type = %s\n", tostring(self.refueltype))
text=text..string.format("AI = %s\n", tostring(self.ai))
text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter()))
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn))
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled()))
text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir()))
text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold()))
text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot()))
text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway()))
self:I(self.lid..text)
-- Init done.
self.groupinitialized=true
end
return self
end
--- Add an element to the flight group.
-- @param #FLIGHTGROUP self
-- @param #string unitname Name of unit.
-- @return #FLIGHTGROUP.Element The element or nil.
function FLIGHTGROUP:AddElementByName(unitname)
local unit=UNIT:FindByName(unitname)
if unit then
local element={} --#FLIGHTGROUP.Element
element.name=unitname
element.unit=unit
element.status=FLIGHTGROUP.ElementStatus.INUTERO
element.group=unit:GetGroup()
element.modex=element.unit:GetTemplate().onboard_num
element.skill=element.unit:GetTemplate().skill
element.pylons=element.unit:GetTemplatePylons()
element.fuelmass0=element.unit:GetTemplatePayload().fuel
element.fuelmass=element.fuelmass0
element.fuelrel=element.unit:GetFuel()
element.category=element.unit:GetCategory()
element.categoryname=element.unit:GetCategoryName()
element.callsign=element.unit:GetCallsign()
element.size=element.unit:GetObjectSize()
if element.skill=="Client" or element.skill=="Player" then
element.ai=false
element.client=CLIENT:FindByName(unitname)
else
element.ai=true
end
local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d %%), category=%d, categoryname=%s, callsign=%s, ai=%s",
element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel, element.category, element.categoryname, element.callsign, tostring(element.ai))
self:I(self.lid..text)
-- Add element to table.
table.insert(self.elements, element)
return element
end
return nil
end
--- Check if a unit is and element of the flightgroup.
-- @param #FLIGHTGROUP self
-- @param #string unitname Name of unit.
-- @return #FLIGHTGROUP.Element The element.
function FLIGHTGROUP:GetElementByName(unitname)
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.name==unitname then
return element
end
end
return nil
end
--- Check if a unit is and element of the flightgroup.
-- @param #FLIGHTGROUP self
-- @return Wrapper.Airbase#AIRBASE Final destination airbase or #nil.
function FLIGHTGROUP:GetHomebaseFromWaypoints()
local wp=self:GetWaypoint(1)
if wp then
if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea
or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot
or wp.action==COORDINATE.WaypointAction.FromRunway then
-- Get airbase ID depending on airbase category.
local airbaseID=wp.airdromeId or wp.helipadId
local airbase=AIRBASE:FindByID(airbaseID)
return airbase
end
--TODO: Handle case where e.g. only one WP but that is not landing.
--TODO: Probably other cases need to be taken care of.
end
return nil
end
--- Find the nearest friendly airbase (same or neutral coalition).
-- @param #FLIGHTGROUP self
-- @param #number Radius Search radius in NM. Default 50 NM.
-- @return Wrapper.Airbase#AIRBASE Closest tanker group #nil.
function FLIGHTGROUP:FindNearestAirbase(Radius)
local coord=self:GetCoordinate()
local dmin=math.huge
local airbase=nil --Wrapper.Airbase#AIRBASE
for _,_airbase in pairs(AIRBASE.GetAllAirbases()) do
local ab=_airbase --Wrapper.Airbase#AIRBASE
local coalitionAB=ab:GetCoalition()
if coalitionAB==self:GetCoalition() or coalitionAB==coalition.side.NEUTRAL then
if airbase then
local d=ab:GetCoordinate():Get2DDistance(coord)
if d<dmin then
d=dmin
airbase=ab
end
end
end
end
return airbase
end
--- Find the nearest tanker.
-- @param #FLIGHTGROUP self
-- @param #number Radius Search radius in NM. Default 50 NM.
-- @return Wrapper.Group#GROUP Closest tanker group #nil.
function FLIGHTGROUP:FindNearestTanker(Radius)
Radius=UTILS.NMToMeters(Radius or 50)
if self.refueltype then
local coord=self:GetCoordinate()
local units=coord:ScanUnits(Radius)
local dmin=math.huge
local tanker=nil --Wrapper.Unit#UNIT
for _,_unit in pairs(units.Set) do
local unit=_unit --Wrapper.Unit#UNIT
local istanker, refuelsystem=unit:IsTanker()
if istanker and self.refueltype==refuelsystem then
-- Distance.
local d=unit:GetCoordinate():Get2DDistance(coord)
if d<dmin then
d=dmin
tanker=unit
end
end
end
if tanker then
return tanker:GetGroup()
end
end
return nil
end
--- Check if a unit is and element of the flightgroup.
-- @param #FLIGHTGROUP self
-- @return Wrapper.Airbase#AIRBASE Final destination airbase or #nil.
function FLIGHTGROUP:GetDestinationFromWaypoints()
local wp=self:GetWaypointFinal()
if wp then
if wp and wp.action and wp.action==COORDINATE.WaypointAction.Landing then
-- Get airbase ID depending on airbase category.
local airbaseID=wp.airdromeId or wp.helipadId
local airbase=AIRBASE:FindByID(airbaseID)
return airbase
end
--TODO: Handle case where e.g. only one WP but that is not landing.
--TODO: Probably other cases need to be taken care of.
end
return nil
end
--- Check if this is a hot start.
-- @param #FLIGHTGROUP self
-- @return #boolean Hot start?
function FLIGHTGROUP:IsTakeoffHot()
local wp=self:GetWaypoint(1)
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromParkingAreaHot then
return true
else
return false
end
end
return nil
end
--- Check if this is a cold start.
-- @param #FLIGHTGROUP self
-- @return #boolean Cold start, i.e. engines off when spawned?
function FLIGHTGROUP:IsTakeoffCold()
local wp=self:GetWaypoint(1)
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea then
return true
else
return false
end
end
return nil
end
--- Check if this is a runway start.
-- @param #FLIGHTGROUP self
-- @return #boolean Runway start?
function FLIGHTGROUP:IsTakeoffRunway()
local wp=self:GetWaypoint(1)
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromRunway then
return true
else
return false
end
end
return nil
end
--- Check if this is an air start.
-- @param #FLIGHTGROUP self
-- @return #boolean Air start?
function FLIGHTGROUP:IsTakeoffAir()
local wp=self:GetWaypoint(1)
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.TurningPoint or wp.action==COORDINATE.WaypointAction.FlyoverPoint then
return true
else
return false
end
end
return nil
end
--- Check if the final waypoint is in the air.
-- @param #FLIGHTGROUP self
-- @param #table wp Waypoint. Default final waypoint.
-- @return #boolean If `true` final waypoint is a turning or flyover but not a landing type waypoint.
function FLIGHTGROUP:IsLandingAir(wp)
wp=wp or self:GetWaypointFinal()
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.TurningPoint or wp.action==COORDINATE.WaypointAction.FlyoverPoint then
return true
else
return false
end
end
return nil
end
--- Check if the final waypoint is at an airbase.
-- @param #FLIGHTGROUP self
-- @param #table wp Waypoint. Default final waypoint.
-- @return #boolean If `true`, final waypoint is a landing waypoint at an airbase.
function FLIGHTGROUP:IsLandingAirbase(wp)
wp=wp or self:GetWaypointFinal()
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.LANDING then
return true
else
return false
end
end
return nil
end
--- Check if this group is currently "late activated" and needs to be "activated" to appear in the mission.
-- @param #FLIGHTGROUP self
-- @return #boolean Hot start?
function FLIGHTGROUP:IsLateActivated()
return self.isLateActivated
end
--- Check if this group is currently "uncontrolled" and needs to be "started" to begin its route.
-- @param #FLIGHTGROUP self
-- @return #boolean Hot start?
function FLIGHTGROUP:IsUncontrolled()
return self.isUncontrolled
end
--- Check if task description is unique.
-- @param #FLIGHTGROUP self
-- @param #string description Task destription
-- @return #boolean If true, no other task has the same description.
function FLIGHTGROUP:CheckTaskDescriptionUnique(description)
-- Loop over tasks in queue
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.description==description then
return false
end
end
return true
end
--- Get the currently executed task if there is any.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP.Task Current task or nil.
function FLIGHTGROUP:GetTaskCurrent()
return self:GetTaskByID(self.taskcurrent, FLIGHTGROUP.TaskStatus.EXECUTING)
end
--- Get task by its id.
-- @param #FLIGHTGROUP self
-- @param #number id Task id.
-- @param #string status (Optional) Only return tasks with this status, e.g. FLIGHTGROUP.TaskStatus.SCHEDULED.
-- @return #FLIGHTGROUP.Task The task or nil.
function FLIGHTGROUP:GetTaskByID(id, status)
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.id==id then
if status==nil or status==task.status then
return task
end
end
end
return nil
end
--- Get mission by its id (auftragsnummer).
-- @param #FLIGHTGROUP self
-- @param #number id Mission id (auftragsnummer).
-- @return Ops.Auftrag#AUFTRAG The mission.
function FLIGHTGROUP:GetMissionByID(id)
if not id then
return nil
end
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission.auftragsnummer==id then
return mission
end
end
return nil
end
--- Get mission by its task id.
-- @param #FLIGHTGROUP self
-- @param #number taskid The id of the (waypoint) task of the mission.
-- @return Ops.Auftrag#AUFTRAG The mission.
function FLIGHTGROUP:GetMissionByTaskID(taskid)
if taskid then
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local task=mission:GetFlightWaypointTask(self)
if task and task.id and task.id==taskid then
return mission
end
end
end
return nil
end
--- Get current mission.
-- @param #FLIGHTGROUP self
-- @return Ops.Auftrag#AUFTRAG The current mission or *nil*.
function FLIGHTGROUP:GetMissionCurrent()
return self:GetMissionByID(self.currentmission)
end
--- Get next waypoint of the flight group.
-- @param #FLIGHTGROUP self
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
-- @return #number Number of waypoint.
function FLIGHTGROUP:GetNextWaypoint()
-- Next waypoint.
local Nextwp=nil
if self.currentwp==#self.waypoints then
Nextwp=1
else
Nextwp=self.currentwp+1
end
-- Next waypoint.
local nextwp=self.waypoints[Nextwp] --Core.Point#COORDINATE
return nextwp,Nextwp
end
--- Get next waypoint coordinates.
-- @param #FLIGHTGROUP self
-- @param #table wp Waypoint table.
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
function FLIGHTGROUP:GetWaypointCoordinate(wp)
-- TODO: move this to COORDINATE class.
return COORDINATE:New(wp.x, wp.alt, wp.y)
end
--- Initialize Mission Editor waypoints.
-- @param #FLIGHTGROUP self
function FLIGHTGROUP:_UpdateWaypointTasks()
local waypoints=self.waypoints
local nwaypoints=#waypoints
for i,wp in pairs(waypoints) do
if i>self.currentwp or nwaypoints==1 then
-- Debug info.
self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d.", i, nwaypoints, self.currentwp))
-- Tasks of this waypoint
local taskswp={}
-- At each waypoint report passing.
local TaskPassingWaypoint=self.group:TaskFunction("FLIGHTGROUP._PassingWaypoint", self, i)
table.insert(taskswp, TaskPassingWaypoint)
-- Waypoint task combo.
wp.task=self.group:TaskCombo(taskswp)
end
end
end
--- Initialize Mission Editor waypoints.
-- @param #FLIGHTGROUP self
-- @param #table waypoints Table of waypoints. Default is from group template.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:InitWaypoints(waypoints)
-- Template waypoints.
self.waypoints0=self.group:GetTemplateRoutePoints()
-- Waypoints of group as defined in the ME.
self.waypoints=waypoints or UTILS.DeepCopy(self.waypoints0)
-- Get home and destination airbases from waypoints.
self.homebase=self:GetHomebaseFromWaypoints()
self.destbase=self:GetDestinationFromWaypoints()
-- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint.
if self.destbase then
table.remove(self.waypoints, #self.waypoints)
else
self.destbase=self.homebase
end
-- Debug info.
self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown"))
-- Update route.
if #self.waypoints>0 then
-- Check if only 1 wp?
if #self.waypoints==1 then
self.passedfinalwp=true
end
-- Update route (when airborne).
--self:_CheckFlightDone(1)
self:__UpdateRoute(-1)
end
return self
end
--- Add a waypoint to the flight plan.
-- @param #FLIGHTGROUP self
-- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude.
-- @param #number wpnumber Waypoint number. Default at the end.
-- @param #number speed Speed in knots. Default 350 kts.
-- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call.
-- @return #number Waypoint index.
function FLIGHTGROUP:AddWaypointAir(coordinate, wpnumber, speed, updateroute)
-- Waypoint number. Default is at the end.
wpnumber=wpnumber or #self.waypoints+1
if wpnumber>self.currentwp then
self.passedfinalwp=false
end
-- Speed in knots.
speed=speed or 350
-- Speed at waypoint.
local speedkmh=UTILS.KnotsToKmph(speed)
-- Create air waypoint.
local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, string.format("Added Waypoint #%d", wpnumber))
-- Add to table.
table.insert(self.waypoints, wpnumber, wp)
-- Debug info.
self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints))
-- Shift all waypoint tasks after the inserted waypoint.
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.type==FLIGHTGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then
task.waypoint=task.waypoint+1
end
end
-- Shift all mission waypoints after the inserted waypoint.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
-- Get mission waypoint index.
local wpidx=mission:GetFlightWaypointIndex(self)
-- Increase number if this waypoint lies in the future.
if wpidx and wpidx>=wpnumber then
mission:SetFlightWaypointIndex(self, wpidx+1)
end
end
-- Update route.
if updateroute==nil or updateroute==true then
self:_CheckFlightDone(1)
--self:__UpdateRoute(-1)
end
return wpnumber
end
--- Remove a waypoint.
-- @param #FLIGHTGROUP self
-- @param #number wpindex Waypoint number.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:RemoveWaypoint(wpindex)
if self.waypoints then
-- Number of waypoints before delete.
local N=#self.waypoints
-- Remove waypoint.
table.remove(self.waypoints, wpindex)
-- Number of waypoints after delete.
local n=#self.waypoints
self:I(self.lid..string.format("Removing waypoint %d. N %d-->%d", wpindex, N, n))
-- Shift all waypoint tasks after the removed waypoint.
for _,_task in pairs(self.taskqueue) do
local task=_task --#FLIGHTGROUP.Task
if task.type==FLIGHTGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>wpindex then
task.waypoint=task.waypoint-1
end
end
-- Shift all mission waypoints after the removerd waypoint.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
-- Get mission waypoint index.
local wpidx=mission:GetFlightWaypointIndex(self)
-- Reduce number if this waypoint lies in the future.
if wpidx and wpidx>wpindex then
mission:SetFlightWaypointIndex(self, wpidx-1)
end
end
-- Waypoint was not reached yet.
if wpindex > self.currentwp then
-- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true so we RTB or wait.
if self.currentwp>=n then
self.passedfinalwp=true
end
env.info("FF update route -1 after waypoint removed")
self:_CheckFlightDone()
else
-- If an already passed waypoint was deleted, we do not need to update the route.
-- TODO: But what about the self.currentwp number. This is now incorrect!
self.currentwp=self.currentwp-1
end
end
return self
end
--- Check if a unit is an element of the flightgroup.
-- @param #FLIGHTGROUP self
-- @param #string unitname Name of unit.
-- @return #boolean If true, unit is element of the flight group or false if otherwise.
function FLIGHTGROUP:_IsElement(unitname)
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.name==unitname then
return true
end
end
return false
end
--- Check if all elements of the flight group have the same status (or are dead).
-- @param #FLIGHTGROUP self
-- @param #string unitname Name of unit.
function FLIGHTGROUP:_AllSameStatus(status)
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.status==FLIGHTGROUP.ElementStatus.DEAD then
-- Do nothing. Element is already dead and does not count.
elseif element.status~=status then
-- At least this element has a different status.
return false
end
end
return true
end
--- Check if all elements of the flight group have the same status (or are dead).
-- @param #FLIGHTGROUP self
-- @param #string status Status to check.
-- @return #boolean If true, all elements have a similar status.
function FLIGHTGROUP:_AllSimilarStatus(status)
-- Check if all are dead.
if status==FLIGHTGROUP.ElementStatus.DEAD then
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.status~=FLIGHTGROUP.ElementStatus.DEAD then
-- At least one is still alive.
return false
end
end
return true
end
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
self:T3(self.lid..string.format("Status=%s, element %s status=%s", status, element.name, element.status))
-- Dead units dont count ==> We wont return false for those.
if element.status~=FLIGHTGROUP.ElementStatus.DEAD then
----------
-- ALIVE
----------
if status==FLIGHTGROUP.ElementStatus.SPAWNED then
-- Element SPAWNED: Check that others are not still IN UTERO
if element.status~=status and
element.status==FLIGHTGROUP.ElementStatus.INUTERO then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.PARKING then
-- Element PARKING: Check that the other are not still SPAWNED
if element.status~=status or
(element.status==FLIGHTGROUP.ElementStatus.INUTERO or
element.status==FLIGHTGROUP.ElementStatus.SPAWNED) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.ENGINEON then
-- Element TAXIING: Check that the other are not still SPAWNED or PARKING
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.INUTERO or
element.status==FLIGHTGROUP.ElementStatus.SPAWNED or
element.status==FLIGHTGROUP.ElementStatus.PARKING) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.TAXIING then
-- Element TAXIING: Check that the other are not still SPAWNED or PARKING
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.INUTERO or
element.status==FLIGHTGROUP.ElementStatus.SPAWNED or
element.status==FLIGHTGROUP.ElementStatus.PARKING or
element.status==FLIGHTGROUP.ElementStatus.ENGINEON) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.TAKEOFF then
-- Element TAKEOFF: Check that the other are not still SPAWNED, PARKING or TAXIING
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.INUTERO or
element.status==FLIGHTGROUP.ElementStatus.SPAWNED or
element.status==FLIGHTGROUP.ElementStatus.PARKING or
element.status==FLIGHTGROUP.ElementStatus.ENGINEON or
element.status==FLIGHTGROUP.ElementStatus.TAXIING) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then
-- Element AIRBORNE: Check that the other are not still SPAWNED, PARKING, TAXIING or TAKEOFF
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.INUTERO or
element.status==FLIGHTGROUP.ElementStatus.SPAWNED or
element.status==FLIGHTGROUP.ElementStatus.PARKING or
element.status==FLIGHTGROUP.ElementStatus.ENGINEON or
element.status==FLIGHTGROUP.ElementStatus.TAXIING or
element.status==FLIGHTGROUP.ElementStatus.TAKEOFF) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.LANDED then
-- Element LANDED: check that the others are not still AIRBORNE or LANDING
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.AIRBORNE or
element.status==FLIGHTGROUP.ElementStatus.LANDING) then
return false
end
elseif status==FLIGHTGROUP.ElementStatus.ARRIVED then
-- Element ARRIVED: check that the others are not still AIRBORNE, LANDING, or LANDED (taxiing).
if element.status~=status and
(element.status==FLIGHTGROUP.ElementStatus.AIRBORNE or
element.status==FLIGHTGROUP.ElementStatus.LANDING or
element.status==FLIGHTGROUP.ElementStatus.LANDED) then
return false
end
end
else
-- Element is dead. We don't care unless all are dead.
end --DEAD
end
-- Debug info.
self:T3(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE", #self.elements, status))
return true
end
--- Check if all elements of the flight group have the same status or are dead.
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element element Element.
-- @param #string newstatus New status of element
-- @param Wrapper.Airbase#AIRBASE airbase Airbase if applicable.
function FLIGHTGROUP:_UpdateStatus(element, newstatus, airbase)
-- Old status.
local oldstatus=element.status
-- Update status of element.
element.status=newstatus
-- Debug
self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s", element.name, oldstatus, newstatus))
for _,_element in pairs(self.elements) do
local Element=_element -- #FLIGHTGROUP.Element
self:T3(self.lid..string.format("Element %s: %s", Element.name, Element.status))
end
if newstatus==FLIGHTGROUP.ElementStatus.SPAWNED then
---
-- SPAWNED
---
if self:_AllSimilarStatus(newstatus) then
self:__FlightSpawned(-0.5)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.PARKING then
---
-- PARKING
---
if self:_AllSimilarStatus(newstatus) then
self:__FlightParking(-0.5)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.ENGINEON then
---
-- ENGINEON
---
-- No FLIGHT status. Waiting for taxiing.
elseif newstatus==FLIGHTGROUP.ElementStatus.TAXIING then
---
-- TAXIING
---
if self:_AllSimilarStatus(newstatus) then
self:__FlightTaxiing(-0.5)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.TAKEOFF then
---
-- TAKEOFF
---
if self:_AllSimilarStatus(newstatus) then
-- Trigger takeoff event. Also triggers airborne event.
self:__FlightTakeoff(-0.5, airbase)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.AIRBORNE then
---
-- AIRBORNE
---
if self:_AllSimilarStatus(newstatus) then
self:__FlightAirborne(-0.5)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.LANDED then
---
-- LANDED
---
if self:_AllSimilarStatus(newstatus) then
self:FlightLanded(airbase)
end
elseif newstatus==FLIGHTGROUP.ElementStatus.ARRIVED then
---
-- ARRIVED
---
if self:_AllSimilarStatus(newstatus) then
if self:IsLanded() then
self:FlightArrived()
elseif self:IsAirborne() then
self:FlightLanded()
self:FlightArrived()
end
end
elseif newstatus==FLIGHTGROUP.ElementStatus.DEAD then
---
-- DEAD
---
if self:_AllSimilarStatus(newstatus) then
self:FlightDead()
end
end
end
--- Set status for all elements (except dead ones).
-- @param #FLIGHTGROUP self
-- @param #string status Element status.
function FLIGHTGROUP:_SetElementStatusAll(status)
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.status~=FLIGHTGROUP.ElementStatus.DEAD then
element.status=status
end
end
end
--- Set parking spot of element.
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element Element The element.
-- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot.
function FLIGHTGROUP:_SetElementParkingAt(Element, Spot)
-- Element is parking here.
Element.parking=Spot
if Spot and self.flightcontrol then
-- Set parking spot to OCCUPIED.
self.flightcontrol:SetParkingOccupied(Element.parking, Element.name)
end
end
--- Set parking spot of element to free
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element Element The element.
function FLIGHTGROUP:_SetElementParkingFree(Element)
if Element.parking then
-- Set parking to FREE.
if self.flightcontrol then
self.flightcontrol:SetParkingFree(Element.parking)
end
-- Not parking any more.
Element.parking=nil
end
end
--- Check if flight is in zones.
-- @param #FLIGHTGROUP self
function FLIGHTGROUP:_CheckInZones()
if self.checkzones then
local Ncheck=self.checkzones:Count()
local Ninside=self.inzones:Count()
-- Debug info.
self:T(self.lid..string.format("Check if flight is in %d zones. Currently it is in %d zones.", self.checkzones:Count(), self.inzones:Count()))
-- Firstly, check if group is still inside zone it was already in. If not, remove zones and trigger LeaveZone() event.
local leftzones={}
for inzonename, inzone in pairs(self.inzones:GetSet()) do
-- Check if group is still inside the zone.
local isstillinzone=self.group:IsPartlyOrCompletelyInZone(inzone)
-- If not, trigger, LeaveZone event.
if not isstillinzone then
table.insert(leftzones, inzone)
end
end
-- Trigger leave zone event.
for _,leftzone in pairs(leftzones) do
self:LeaveZone(leftzone)
end
-- Now, run of all check zones and see if the group entered a zone.
local enterzones={}
for checkzonename,_checkzone in pairs(self.checkzones:GetSet()) do
local checkzone=_checkzone --Core.Zone#ZONE
-- Is flight currtently in this check zone?
local isincheckzone=self.group:IsPartlyOrCompletelyInZone(checkzone)
if isincheckzone and not self.inzones:_Find(checkzonename) then
table.insert(enterzones, checkzone)
end
end
-- Trigger enter zone event.
for _,enterzone in pairs(enterzones) do
self:EnterZone(enterzone)
end
end
end
--- Check detected units.
-- @param #FLIGHTGROUP self
function FLIGHTGROUP:_CheckDetectedUnits()
if self.group and not self:IsDead() then
-- Get detected DCS units.
local detectedtargets=self.group:GetDetectedTargets()
local detected={}
for DetectionObjectID, Detection in pairs(detectedtargets or {}) do
local DetectedObject=Detection.object -- DCS#Object
if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive() then
-- Name of detected unit
local unitname=unit:GetName()
-- Add unit to detected table of this run.
table.insert(detected, unit)
-- Trigger detected unit event.
self:DetectedUnit(unit)
if self.detectedunits:FindUnit(unitname) then
-- Unit is already in the detected unit set ==> Trigger "DetectedUnitKnown" event.
self:DetectedUnitKnown(unit)
else
-- Unit is was not detected ==> Trigger "DetectedUnitNew" event.
self:DetectedUnitNew(unit)
end
end
end
end
-- Loop over units in detected set.
local lost={}
for _,_unit in pairs(self.detectedunits:GetSet()) do
local unit=_unit --Wrapper.Unit#UNIT
-- Loop over detected units
local gotit=false
for _,_du in pairs(detected) do
local du=_du --Wrapper.Unit#UNIT
if unit:GetName()==du:GetName() then
gotit=true
end
end
if not gotit then
table.insert(lost, unit:GetName())
self:DetectedUnitLost(unit)
end
end
-- Remove lost units from detected set.
self.detectedunits:RemoveUnitsByName(lost)
end
end
--- Get onboard number.
-- @param #FLIGHTGROUP self
-- @param #string unitname Name of the unit.
-- @return #string Modex.
function FLIGHTGROUP:_GetOnboardNumber(unitname)
local group=UNIT:FindByName(unitname):GetGroup()
-- Units of template group.
local units=group:GetTemplate().units
-- Get numbers.
local numbers={}
for _,unit in pairs(units) do
if unitname==unit.name then
return tostring(unit.onboard_num)
end
end
return nil
end
--- Get the number of shells a unit or group currently has. For a group the ammo count of all units is summed up.
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element element The element.
-- @param #boolean display Display ammo table as message to all. Default false.
-- @return #number Total amount of ammo the whole group has left.
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
function FLIGHTGROUP:GetAmmoElement(element, display)
-- Default is display false.
if display==nil then
display=false
end
-- Init counter.
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nbombs=0
local unit=element.unit
-- Output.
local text=string.format("FLIGHTGROUP group %s - unit %s:\n", self.groupname, unit:GetName())
-- Get ammo table.
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
-- Display ammo table
if display then
self:E(FLIGHTGROUP.id..string.format("Number of weapons %d.", weapons))
self:E({ammotable=ammotable})
self:E(FLIGHTGROUP.id.."Ammotable:")
for id,bla in pairs(ammotable) do
self:E({id=id, ammo=bla})
end
end
-- Loop over all weapons.
for w=1,weapons do
-- Number of current weapon.
local Nammo=ammotable[w]["count"]
-- Type name of current weapon.
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString = UTILS.Split(Tammo,"%.")
local _weaponName = _weaponString[#_weaponString]
-- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3
local Category=ammotable[w].desc.category
-- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
-- We are specifically looking for shells or rockets here.
if Category==Weapon.Category.SHELL then
-- Add up all shells.
nshells=nshells+Nammo
-- Debug info.
text=text..string.format("- %d shells of type %s\n", Nammo, _weaponName)
elseif Category==Weapon.Category.ROCKET then
-- Add up all rockets.
nrockets=nrockets+Nammo
-- Debug info.
text=text..string.format("- %d rockets of type %s\n", Nammo, _weaponName)
elseif Category==Weapon.Category.BOMB then
-- Add up all rockets.
nbombs=nbombs+Nammo
-- Debug info.
text=text..string.format("- %d bombs of type %s\n", Nammo, _weaponName)
elseif Category==Weapon.Category.MISSILE then
-- Add up all cruise missiles (category 5)
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
end
-- Debug info.
text=text..string.format("- %d %s missiles of type %s\n", Nammo, self:_MissileCategoryName(MissileCategory), _weaponName)
else
-- Debug info.
text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n", Nammo, Tammo, Category, tostring(MissileCategory))
end
end
end
-- Debug text and send message.
if display then
self:I(self.lid..text)
else
self:T3(self.lid..text)
end
MESSAGE:New(text, 10):ToAllIf(display)
-- Total amount of ammunition.
nammo=nshells+nrockets+nmissiles+nbombs
return nammo, nshells, nrockets, nbombs, nmissiles
end
--- Returns a name of a missile category.
-- @param #FLIGHTGROUP self
-- @param #number categorynumber Number of missile category from weapon missile category enumerator. See https://wiki.hoggitworld.com/view/DCS_Class_Weapon
-- @return #string Missile category name.
function FLIGHTGROUP:_MissileCategoryName(categorynumber)
local cat="unknown"
if categorynumber==Weapon.MissileCategory.AAM then
cat="air-to-air"
elseif categorynumber==Weapon.MissileCategory.SAM then
cat="surface-to-air"
elseif categorynumber==Weapon.MissileCategory.BM then
cat="ballistic"
elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then
cat="anti-ship"
elseif categorynumber==Weapon.MissileCategory.CRUISE then
cat="cruise"
elseif categorynumber==Weapon.MissileCategory.OTHER then
cat="other"
end
return cat
end
--- Checks if a human player sits in the unit.
-- @param #FLIGHTGROUP self
-- @param Wrapper.Unit#UNIT unit Aircraft unit.
-- @return #boolean If true, human player inside the unit.
function FLIGHTGROUP:_IsHumanUnit(unit)
-- Get player unit or nil if no player unit.
local playerunit=self:_GetPlayerUnitAndName(unit:GetName())
if playerunit then
return true
else
return false
end
end
--- Checks if a group has a human player.
-- @param #FLIGHTGROUP self
-- @param Wrapper.Group#GROUP group Aircraft group.
-- @return #boolean If true, human player inside group.
function FLIGHTGROUP:_IsHuman(group)
-- Get all units of the group.
local units=group:GetUnits()
-- Loop over all units.
for _,_unit in pairs(units) do
-- Check if unit is human.
local human=self:_IsHumanUnit(_unit)
if human then
return true
end
end
return false
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FLIGHTGROUP self
-- @param #string _unitName Name of the player unit.
-- @return Wrapper.Unit#UNIT Unit of player or nil.
-- @return #string Name of the player or nil.
function FLIGHTGROUP:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName ~= nil then
-- Get DCS unit from its name.
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
if DCSunit and unit and playername then
return unit, playername
end
end
end
-- Return nil if we could not find a player.
return nil,nil
end
--- Returns the coalition side.
-- @param #FLIGHTGROUP self
-- @return #number Coalition side number.
function FLIGHTGROUP:GetCoalition()
return self.group:GetCoalition()
end
--- Returns the absolute (average) life points of the group.
-- @param #FLIGHTGROUP self
-- @return #number Life points. If group contains more than one element, the average is given.
-- @return #number Initial life points.
function FLIGHTGROUP:GetLifePoints()
if self.group then
return self.group:GetLife(), self.group:GetLife0()
end
end
--- Returns the parking spot of the element.
-- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element element Element of the flight group.
-- @param #number maxdist Distance threshold in meters. Default 5 m.
-- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase to check for parking. Default is closest airbase to the element.
-- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold.
function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase)
local coord=element.unit:GetCoordinate()
airbase=airbase or coord:GetClosestAirbase(nil, self:GetCoalition())
local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot
local dist=nil
local distmin=math.huge
for _,_parking in pairs(airbase.parking) do
local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot
dist=coord:Get2DDistance(parking.Coordinate)
if dist<distmin then
distmin=dist
spot=_parking
end
end
if distmin<=maxdist and not element.unit:InAir() then
return spot
else
return nil
end
end
--- Get holding time.
-- @param #FLIGHTGROUP self
-- @return #number Holding time in seconds or -1 if flight is not holding.
function FLIGHTGROUP:GetHoldingTime()
if self.Tholding then
return timer.getAbsTime()-self.Tholding
end
return -1
end
--- Get parking time.
-- @param #FLIGHTGROUP self
-- @return #number Holding time in seconds or -1 if flight is not holding.
function FLIGHTGROUP:GetParkingTime()
if self.Tparking then
return timer.getAbsTime()-self.Tparking
end
return -1
end
--- Get number of elements alive.
-- @param #FLIGHTGROUP self
-- @param #string status (Optional) Only count number, which are in a special status.
-- @return #number Holding time in seconds or -1 if flight is not holding.
function FLIGHTGROUP:GetNelements(status)
local n=0
for _,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
if element.status~=FLIGHTGROUP.ElementStatus.DEAD then
if element.unit and element.unit:IsAlive() then
if status==nil or element.status==status then
n=n+1
end
end
end
end
return n
end
--- Search unoccupied parking spots at the airbase for all flight elements.
-- @param #FLIGHTGROUP self
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
-- @return #table Table of coordinates and terminal IDs of free parking spots.
function FLIGHTGROUP:GetParking(airbase)
-- Init default
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=true
-- Function calculating the overlap of two (square) objects.
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
local safe = (dist > safedist)
return safe
end
-- Get client coordinates.
local function _clients()
local clients=_DATABASE.CLIENTS
local coords={}
for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units) do
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
coords[unit.name]=coord
end
end
return coords
end
-- Get airbase category.
local airbasecategory=airbase:GetAirbaseCategory()
-- Get parking spot data table. This contains all free and "non-free" spots.
local parkingdata=airbase:GetParkingSpotsTable()
-- List of obstacles.
local obstacles={}
-- Loop over all parking spots and get the currently present obstacles.
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
-- The alternative would be to perform the scan once but with a much larger radius and store all data.
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Scan a radius of 100 meters around the spot.
local _,_,_,_units,_statics,_sceneries=parkingspot.Coordinate:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
-- Check all units.
for _,_unit in pairs(_units) do
local unit=_unit --Wrapper.Unit#UNIT
local _coord=unit:GetCoordinate()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
end
-- Check all clients.
local clientcoords=_clients()
for clientname,_coord in pairs(clientcoords) do
table.insert(obstacles, {coord=_coord, size=15, name=clientname, type="client"})
end
-- Check all statics.
for _,static in pairs(_statics) do
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
end
-- Check all scenery.
for _,scenery in pairs(_sceneries) do
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
end
end
-- Parking data for all assets.
local parking={}
-- Get terminal type.
local terminaltype=self:_GetTerminal(self.attribute, airbase:GetAirbaseCategory())
-- Loop over all units - each one needs a spot.
for i,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
-- Assume free and no problematic obstacle.
local free=true
local problem=nil
-- Safe parking using TO_AC from DCS result.
if verysafe and parkingspot.TOAC then
free=false
self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).", parkingspot.TerminalID))
end
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=parkingspot.Coordinate:Get2DDistance(obstacle.coord)
local safe=_overlap(element.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
free=false
problem=obstacle
problem.dist=dist
break
end
end
-- Check flightcontrol data.
if self.flightcontrol and self.flightcontrol.airbasename==airbase:GetName() then
local problem=self.flightcontrol:IsParkingReserved(parkingspot)
if problem then
free=false
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this element.
table.insert(parking, parkingspot)
self:T2(self.lid..string.format("Parking spot %d is free for element %s!", parkingspot.TerminalID, element.name))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=parkingspot.Coordinate, size=element.size, name=element.name, type="element"})
gotit=true
break
else
-- Debug output for occupied spots.
self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!", parkingspot.TerminalID))
--if self.Debug then
-- local coord=problem.coord --Core.Point#COORDINATE
-- local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
-- coord:MarkToAll(string.format(text))
--end
end
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:E(self.lid..string.format("WARNING: No free parking spot for element %s", element.name))
return nil
end
end -- loop over asset units
return parking
end
--- Size of the bounding box of a DCS object derived from the DCS descriptor table. If boundinb box is nil, a size of zero is returned.
-- @param #FLIGHTGROUP self
-- @param DCS#Object DCSobject The DCS object for which the size is needed.
-- @return #number Max size of object in meters (length (x) or width (z) components not including height (y)).
-- @return #number Length (x component) of size.
-- @return #number Height (y component) of size.
-- @return #number Width (z component) of size.
function FLIGHTGROUP:_GetObjectSize(DCSobject)
local DCSdesc=DCSobject:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) --length
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) --width
return math.max(x,z), x , y, z
end
return 0,0,0,0
end
--- Get the generalized attribute of a group.
-- @param #FLIGHTGROUP self
-- @return #string Generalized attribute of the group.
function FLIGHTGROUP:_GetAttribute()
-- Default
local attribute=FLIGHTGROUP.Attribute.OTHER
local group=self.group
if group then
--- Planes
local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes")
local awacs=group:HasAttribute("AWACS")
local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers"))
local bomber=group:HasAttribute("Strategic bombers")
local tanker=group:HasAttribute("Tankers")
local uav=group:HasAttribute("UAVs")
--- Helicopters
local transporthelo=group:HasAttribute("Transport helicopters")
local attackhelicopter=group:HasAttribute("Attack helicopters")
-- Define attribute. Order is important.
if transportplane then
attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE
elseif awacs then
attribute=FLIGHTGROUP.Attribute.AIR_AWACS
elseif fighter then
attribute=FLIGHTGROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=FLIGHTGROUP.Attribute.AIR_BOMBER
elseif tanker then
attribute=FLIGHTGROUP.Attribute.AIR_TANKER
elseif transporthelo then
attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO
elseif attackhelicopter then
attribute=FLIGHTGROUP.Attribute.AIR_ATTACKHELO
elseif uav then
attribute=FLIGHTGROUP.Attribute.AIR_UAV
end
end
return attribute
end
--- Get the proper terminal type based on generalized attribute of the group.
--@param #FLIGHTGROUP self
--@param #FLIGHTGROUP.Attribute _attribute Generlized attibute of unit.
--@param #number _category Airbase category.
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
function FLIGHTGROUP:_GetTerminal(_attribute, _category)
-- Default terminal is "large".
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==FLIGHTGROUP.Attribute.AIR_FIGHTER then
-- Fighter ==> small.
_terminal=AIRBASE.TerminalType.FighterAircraft
elseif _attribute==FLIGHTGROUP.Attribute.AIR_BOMBER or _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE or _attribute==FLIGHTGROUP.Attribute.AIR_TANKER or _attribute==FLIGHTGROUP.Attribute.AIR_AWACS then
-- Bigger aircraft.
_terminal=AIRBASE.TerminalType.OpenBig
elseif _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO then
-- Helicopter.
_terminal=AIRBASE.TerminalType.HelicopterUsable
else
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
if _category==Airbase.Category.SHIP then
if not (_attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO) then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end
return _terminal
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- OPTION FUNCTIONS
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the default ROE for the group. This is the ROE state gets when the group is spawned or to which it defaults back after a mission.
-- @param #FLIGHTGROUP self
-- @param #number roe ROE of group. Default is ENUMS.ROE.ReturnFire.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDefaultROE(roe)
self.optionROEdefault=roe or ENUMS.ROE.ReturnFire
return self
end
--- Set current ROE for the group.
-- @param #FLIGHTGROUP self
-- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE().
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionROE(roe)
roe=roe or self.optionROEdefault
if self:IsAlive() then
self.group:OptionROE(roe)
self.optionROEcurrent=roe
self:T2(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.optionROEcurrent))
else
-- TODO WARNING
end
return self
end
--- Set the default ROT for the group. This is the ROT state gets when the group is spawned or to which it defaults back after a mission.
-- @param #FLIGHTGROUP self
-- @param #number roe ROE of group. Default is ENUMS.ROT.PassiveDefense.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDefaultROT(roe)
self.optionROTdefault=roe or ENUMS.ROT.PassiveDefense
return self
end
--- Set ROT for the group.
-- @param #FLIGHTGROUP self
-- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT().
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionROT(rot)
rot=rot or self.optionROTdefault
if self:IsAlive() then
self.group:OptionROT(rot)
self.optionROTcurrent=rot
self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.optionROTcurrent))
else
-- TODO WARNING
end
return self
end
--- Set the default formation for the group.
-- @param #FLIGHTGROUP self
-- @param #number Formation Formation of the group
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDefaultFormation(Formation)
-- TODO!
self.optionFormationDefault=Formation or AI.Task.VehicleFormation.ECHELON_LEFT
return self
end
--- Set default TACAN parameters. AA TACANs are always on "Y" band.
-- @param #FLIGHTGROUP self
-- @param #number Channel TACAN channel.
-- @param #string Morse Morse code. Default "XXX".
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDefaultTACAN(Channel, Morse)
self.tacanChannelDefault=Channel
self.tacanMorseDefault=Morse or "XXX"
return self
end
--- Activate TACAN beacon.
-- @param #FLIGHTGROUP self
-- @param #number TACANChannel TACAN Channel.
-- @param #string TACANMorse TACAN morse code.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SwitchTACANOn(TACANChannel, TACANMorse)
if self:IsAlive() then
local unit=self.group:GetUnit(1)
if unit and unit:IsAlive() then
local Type=5
local System=4
local UnitID=unit:GetID()
local TACANMode="Y"
local Frequency=UTILS.TACANToFrequency(TACANChannel, TACANMode)
unit:CommandActivateBeacon(Type, System, Frequency, UnitID, TACANChannel, TACANMode, true, TACANMorse, true)
self.tacanBeacon=unit
self.tacanChannel=TACANChannel
self.tacanMorse=TACANMorse
self.tacanOn=true
end
end
return self
end
--- Deactivate TACAN beacon.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SwitchTACANOff()
if self.tacanBeacon and self.tacanBeacon:IsAlive() then
self.tacanBeacon:CommandDeactivateBeacon()
end
self.tacanOn=false
end
--- Set default Radio frequency and modulation.
-- @param #FLIGHTGROUP self
-- @param #number Frequency Radio frequency in MHz. Default 251 MHz.
-- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetDefaultRadio(Frequency, Modulation)
self.radioFreqDefault=Frequency or 251
self.radioModuDefault=Modulation or radio.modulation.AM
self.radioOn=false
self.radioUse=true
return self
end
--- Get current Radio frequency and modulation.
-- @param #FLIGHTGROUP self
-- @return #number Radio frequency in MHz or nil.
-- @return #number Radio modulation or nil.
function FLIGHTGROUP:GetRadio()
return self.radioFreq, self.radioModu
end
--- Turn radio on.
-- @param #FLIGHTGROUP self
-- @param #number Frequency Radio frequency in MHz.
-- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SwitchRadioOn(Frequency, Modulation)
if self:IsAlive() then
Modulation=Modulation or radio.Modulation.AM
self.group:SetOption(AI.Option.Air.id.SILENCE, false)
self.group:CommandSetFrequency(Frequency, Modulation)
self.radioFreq=Frequency
self.radioModu=Modulation
self.radioOn=true
end
return self
end
--- Turn radio off.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SwitchRadioOff()
if self:IsAlive() then
self.group:SetOption(AI.Option.Air.id.SILENCE, true)
self.radioFreq=nil
self.radioModu=nil
self.radioOn=false
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- MENU FUNCTIONS
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get the proper terminal type based on generalized attribute of the group.
--@param #FLIGHTGROUP self
function FLIGHTGROUP:_UpdateMenu()
self:I(self.lid.."FF updating menu")
-- Get current position of group.
local position=self.group:GetCoordinate()
-- Get all FLIGHTCONTROLS
local fc={}
for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS) do
local airbase=AIRBASE:FindByName(airbasename)
local coord=airbase:GetCoordinate()
local dist=coord:Get2DDistance(position)
local fcitem={airbasename=airbasename, dist=dist}
table.insert(fc, fcitem)
end
-- Sort table wrt distance to airbases.
local function _sort(a,b)
return a.dist<b.dist
end
table.sort(fc, _sort)
-- If there is a designated FC, we put it first.
local N=8
if self.flightcontrol then
self.flightcontrol:_CreatePlayerMenu(self, self.menu.atc)
N=7
end
-- Max 8 entries in F10 menu.
for i=1,math.min(#fc,N) do
local airbasename=fc[i].airbasename
local flightcontrol=_DATABASE:GetFlightControl(airbasename)
flightcontrol:_CreatePlayerMenu(self, self.menu.atc)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------