--- **Ops** - (R2.5) - AI Flight Group for Ops. -- -- **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 #string sid 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 coordinates Table of waypoint coordinates. -- @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 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 destination The destination base of the flight group. -- @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 Ops.Squadron#SQUADRON squadron The squadron the flight group belongs to. -- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group. -- @field Core.UserFlag#USERFLAG flaghold Flag for holding. -- @extends Core.Fsm#FSM --- Be surprised! -- -- === -- -- ![Banner Image](..\Presentations\CarrierAirWing\FLIGHTGROUP_Main.jpg) -- -- # The FLIGHTGROUP Concept -- -- -- -- @field #FLIGHTGROUP FLIGHTGROUP = { ClassName = "FLIGHTGROUP", Debug = nil, sid = nil, groupname = nil, flightgroup = nil, grouptemplate = nil, type = nil, waypoints = {}, coordinates = {}, elements = {}, taskqueue = {}, taskcounter = nil, taskcurrent = nil, detectedunits = {}, homebase = nil, destination = nil, speedmax = nil, range = nil, altmax = nil, fuellow = false, fuellowthresh = nil, fuellowrtb = nil, fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, squadron = nil, flightcontrol = nil, flaghold = 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 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 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", TAXIING="taxiing", TAKEOFF="takeoff", AIRBORNE="airborne", LANDED="landed", ARRIVED="arrived", DEAD="dead", } --- Flight group element. -- @type FLIGHTGROUP.Element -- @field #string name Name of the element. -- @field #number modex Tail number. -- @field #boolean ai If true, element is AI. -- @field #string skill Skill level. -- @field Wrapper.Unit#UNIT unit Element unit object. -- @field Wrapper.Group#GROUP group Group object of the element. -- @field #string status Status, i.e. born, parking, taxiing. See @{#FLIGHTGROUP.ElementStatus}. -- @field #table pylons Table of pylons. -- @field #number damage Damage of element in percent. --- Flight group tasks. -- @type FLIGHTGROUP.Mission -- @param #string INTERCEPT Intercept task. -- @param #string CAP Combat Air Patrol task. -- @param #string BAI Battlefield Air Interdiction task. -- @param #string SEAD Suppression/destruction of enemy air defences. -- @param #string STRIKE Strike task. -- @param #string AWACS AWACS task. -- @param #string TANKER Tanker task. FLIGHTGROUP.Mission={ INTERCEPT="Intercept", CAP="CAP", BAI="BAI", SEAD="SEAD", STRIKE="Strike", CAS="CAS", AWACS="AWACS", TANKER="Tanker", } --- Flight group task status. -- @type FLIGHTGROUP.TaskStatus -- @field #string SCHEDULED Task is scheduled. -- @field #string EXECUTING Task is being executed. -- @field #string ACCOMPLISHED Task is accomplished. -- @field #string WAYPOINT Task is executed at a waypoint. FLIGHTGROUP.TaskStatus={ SCHEDULED="scheduled", EXECUTING="executing", ACCOMPLISHED="accomplished", } --- Flight group task status. -- @type FLIGHTGROUP.TaskType -- @field #string SCHEDULED Task is scheduled. -- @field #string EXECUTING Task is being executed. FLIGHTGROUP.TaskType={ SCHEDULED="scheduled", WAYPOINT="waypoint", } --- Flight group tasks. -- @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. --- FLIGHTGROUP class version. -- @field #string version FLIGHTGROUP.version="0.1.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add tasks. -- TODO: Add EPLRS, TACAN. -- TODO: Get ammo. -- TODO: Get pylons. -- TODO: Fuel threshhold ==> RTB. -- TODO: ROE, Afterburner restrict. -- TODO: Respawn? With correct loadout, fuelstate. -- TODO: Waypoints, read, add, insert, detour. -- TODO: Damage? -- TODO: shot events? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new FLIGHTGROUP object and start the FSM. -- @param #FLIGHTGROUP self -- @param #string groupname Name of the group. -- @return #FLIGHTGROUP self function FLIGHTGROUP:New(groupname) -- Inherit everything from WAREHOUSE 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.sid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Start State. self:SetStartState("Stopped") -- Init set of detected units. self.detectedunits=SET_UNIT:New() -- Defaults self:SetFuelLowThreshold() self:SetFuelCriticalThreshold() -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "FlightStatus", "*") -- FLIGHTGROUP status update. self:AddTransition("*", "QueueUpdate", "*") -- Update task queue. 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("*", "RTB", "Returning") -- Group is returning to base. self:AddTransition("*", "Orbit", "Orbiting") -- Group is holding position. self:AddTransition("*", "Hold", "Holding") -- Group is holding position. self:AddTransition("*", "PassingWaypoint", "*") -- Group passed a waypoint. 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("*", "TaskExecute", "*") -- Group will execute a task. self:AddTransition("*", "TaskDone", "*") -- Group finished a task. self:AddTransition("*", "TaskCancel", "*") -- Cancel current task. self:AddTransition("*", "TaskPause", "*") -- Pause current task. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementParking", "*") -- An element was spawned. self:AddTransition("*", "ElementTaxiing", "*") -- An element spooled up the engines. self:AddTransition("*", "ElementTakeoff", "*") -- An element took off. self:AddTransition("*", "ElementAirborne", "*") -- An element took off. 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("*", "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("*", "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 self.Debug=true 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. self:Start() return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add scheduled task. -- @param #FLIGHTGROUP self -- @param #string description Brief text describing the task, e.g. "Attack SAM". -- @param #table task DCS task table stucture. -- @param #number prio Priority of the task. -- @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 #number duration Duration before task is cancelled in seconds counted after task started. Default never. -- @return #FLIGHTGROUP self function FLIGHTGROUP:AddTask(description, task, prio, clock, duration) -- Increase coutner. 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.description=description newtask.status=FLIGHTGROUP.TaskStatus.SCHEDULED newtask.dcstask=task newtask.prio=prio or 50 newtask.time=time newtask.id=self.taskcounter newtask.duration=duration newtask.waypoint=-1 newtask.type=FLIGHTGROUP.TaskType.SCHEDULED self:T2({newtask=newtask}) -- Add to table. table.insert(self.taskqueue, newtask) return self end --- Add waypoint task. -- @param #FLIGHTGROUP self -- @param #string description Brief text describing the task, e.g. "Attack SAM". -- @param #table task DCS task table stucture. -- @param #number waypointindex Number of waypoint. Counting starts at one! -- @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 self function FLIGHTGROUP:AddTaskWaypoint(description, task, waypointindex, prio, duration) -- Increase coutner. 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 newtask.type=FLIGHTGROUP.TaskType.WAYPOINT self:T2({newtask=newtask}) -- Add to table. table.insert(self.taskqueue, newtask) return self end --- Set squadron the flight group belongs to. -- @param #FLIGHTGROUP self -- @param Ops.Squadron#SQUADRON squadron The squadron object. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetSquadron(squadron) self:I(self.sid..string.format("Add flight to SQUADRON %s", squadron.squadronname)) self.squadron=squadron return self end --- Get squadron the flight group belongs to. -- @param #FLIGHTGROUP self -- @return Ops.Squadron#SQUADRON The squadron object. function FLIGHTGROUP:GetSquadron() return self.squadron 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) self:I(self.sid..string.format("Setting FLIGHTCONTROL to airbase %s", flightcontrol.airbasename)) self.flightcontrol=flightcontrol 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 low fuel threshold. Triggers event "FuelLow" and calls event function "OnAfterFuelLow". -- @param #FLIGHTGROUP self -- @param #number threshold Fuel threshold in percent. Default 25 %. -- @param #boolean rtb If true, RTB on fuel low event. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetFuelLowThreshold(threshold, rtb) self.fuellow=false self.fuellowthresh=threshold or 25 self.fuellowrtb=rtb 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.fuelcritical=false self.fuelcriticalthresh=threshold or 10 self.fuelcriticalrtb=rtb return self end --- Get set of decteded 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.group:GetName() 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 spawned. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsSpawned() return self:Is("Spawned") end --- Check if flight is parking. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsParking() return self:Is("Parking") end --- Check if flight is parking. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsTaxiing() return self:Is("Taxiing") end --- Check if flight is airborne. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsAirborne() return self:Is("Airborne") end --- Check if flight has landed and is now taxiing to its parking spot. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsLanded() return self:Is("Landed") end --- Check if flight has arrived at its destination parking spot. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsArrived() return self:Is("Arrived") 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 dead. -- @param #FLIGHTGROUP self -- @return #boolean function FLIGHTGROUP:IsDead() return self:Is("Dead") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. -- @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:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting flight group %s.", self.groupname) self:I(self.sid..text) -- Check if the group is already alive and if so, add its elements. local group=GROUP:FindByName(self.groupname) if group and group:IsAlive() then self.group=group local units=group:GetUnits() for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT local element=self:AddElementByName(unit:GetName()) -- Trigger Spawned event. Delay a bit to start. self:__ElementSpawned(0.1, element) 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) -- Start the status monitoring. self:__FlightStatus(-1) end --- On after "FlightStatus" event. -- @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:onafterFlightStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() -- Check if group has detected any units. self:_CheckDetectedUnits() -- Short info. local text=string.format("Flight status %s [%d/%d]. Task=%d/%d. Waypoint=%d/%d. Detected=%d. FC=%s. Destination=%s", fsmstate, #self.elements, #self.elements, self.taskcurrent, #self.taskqueue, self.currentwp or 0, #self.waypoints or 0, self.detectedunits:Count(), self.flightcontrol and self.flightcontrol.airbasename or "none", self.destination and self.destination:GetName() or "unknown") self:I(self.sid..text) -- Element status. text="Elements:" local fuelmin=999999 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 if fuel*100task.timestamp+task.duration --self:E(string.format("FF timestap=%d , duration=%d, time=%d, stamp+duration=%s < time = %s", task.timestamp, task.duration, timer.getAbsTime(), task.timestamp+task.duration, tostring(cancel))) -- Cancel task if task is running longer than duration. if cancel then self:TaskCancel() end end end -- Update queue every ~5 sec. self:__QueueUpdate(-5) 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 -- Set homebase if not already set. if EventData.Place then self.homebase=self.homebase or EventData.Place end -- Create element spawned event if not already present. if not self:_IsElement(unitname) then local element=self:AddElementByName(unitname) self:I(self.sid..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:IsHolding() then else self:I(self.sid..string.format("EVENT: Element %s started engines ==> taxiing", element.name)) self:ElementTaxiing(element) 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:I(self.sid..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:I(self.sid..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 coord=unit:GetCoordinate() local airbase=coord:GetClosestAirbase() local _,_,dist,parking=coord:GetClosestParkingSpot(airbase) if dist and dist<10 and unit:InAir()==false then self:ElementArrived(element, airbase, parking) self:I(self.sid..string.format("EVENT: Element %s shut down engines ==> arrived", element.name)) else self:I(self.sid..string.format("EVENT: Element %s shut down engines (in air) ==> dead", element.name)) self:ElementDead(element) end else self:I(self.sid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name)) end else 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:I(self.sid..string.format("EVENT: Element %s crashed ==> 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:I(self.sid..string.format("Element spawned %s.", Element.name)) -- Set element status. self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.SPAWNED) if Element.unit:InAir() then self:ElementAirborne(Element) else self:ElementParking(Element) 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:I(self.sid..string.format("Element parking %s.", Element.name)) -- Set element status. self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.PARKING) 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) self:I(self.sid..string.format("Element taxiing %s.", Element.name)) -- 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.sid..string.format("Element airborne %s.", Element.name)) -- Set element status. self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAKEOFF, airbase) 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:I(self.sid..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:I(self.sid..string.format("Element landed %s.", Element.name)) -- 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. function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element) self:I(self.sid..string.format("Element arrived %s.", Element.name)) -- 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:I(self.sid..string.format("Element dead %s.", Element.name)) -- 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) -- Get template of group. self.template=self.group:GetTemplate() -- Max speed in km/h. self.speedmax=self.group:GetSpeedMax() -- Init waypoints. self:_InitWaypoints() -- Update route. self:_UpdateRoute(1) -- Route flight group but now with passing waypoint tasks. --self.group:Route(self.waypoints) end --- On after "FlightTaxiing" event. -- @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:onafterFlightTaxiing(From, Event, To) self:I(self.sid..string.format("Flight is taxiing %s.", self.groupname)) -- 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 self.flightcontrol:_AddFlightToTakeoffQueue(self) 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:I(self.sid..string.format("Flight takeoff %s at %s.", self.groupname, airbase and airbase:GetName() or "unknown airbase")) if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qtakeoff, self) end -- Trigger airborne event. self:FlightAirborne(airbase) end --- On after "FlightAirborne" 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:onafterFlightAirborne(From, Event, To, airbase) self:I(self.sid..string.format("Flight airborne %s at %s.", self.groupname,airbase and airbase:GetName() or "unknown airbase")) -- Remove flight from FC takeoff queue. if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qtakeoff, self) end -- Update queue. self:__QueueUpdate(-1) 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:I(self.sid..string.format("Flight landed %s at %s.", self.groupname, airbase and airbase:GetName() or "unknown airbase")) if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qlanding, self) 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("Group %s passed waypoint %d/%d", self.groupname, n, N) self:I(self.sid..text) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) 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, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) -- Set switch to true. self.fuellow=true -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. local airbase=self.destination or self.homebase if airbase and self.fuellowrtb then self:RTB(airbase) 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, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) -- Set switch to true. self.fuelcritical=true -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. local airbase=self.destination or self.homebase if airbase and self.fuelcriticalrtb then self:RTB(airbase) end end --- On after "RTB" event. Send flightgroup back to base. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Airbase#AIRBASE airbase The base to return to. function FLIGHTGROUP:onafterRTB(From, Event, To, airbase) -- Debug message. local text=string.format("Flight group returning to airbase %s.", airbase:GetName()) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. self:RouteRTB(airbase) end --- On after "Orbit" 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. -- @param #number Altitude Altitude in meters. -- @param #number Speed Speed in km/h. function FLIGHTGROUP:onafterOrbit(From, Event, To, Coord, Altitude, Speed) -- Debug message. local text=string.format("Flight group set to orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) --TODO: set ROE passive. introduce roe event/state/variable. local TaskOrbit=self.group:TaskOrbit(Coord, Altitude, UTILS.KmphToMps(Speed)) self.group:SetTask(TaskOrbit) end --- On before "Hold" 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:onbeforeHold(From, Event, To, airbase, SpeedTo, SpeedHold) if airbase==nil then self:E("FF airbase is nil!") return false end if airbase and airbase:GetCoalition()~=self.group:GetCoalition() then self:E("FF wrong coalition!") return false end return true end --- On after "Hold" 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 travelling from current position to holding point in knots. Default 350 kts. -- @param #number SpeedHold Holding speed in knots. Default 250 kts. function FLIGHTGROUP:onafterHold(From, Event, To, airbase, SpeedTo, SpeedHold) -- Defaults: SpeedTo=SpeedTo or 350 SpeedHold=SpeedHold or 250 -- Debug message. local text=string.format("Flight group set to hold at airbase %s", airbase:GetName()) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) local fc=_DATABASE:GetFlightControl(airbase:GetName()) local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(6000)) local p1=nil local wpap=nil if fc then -- Get holding point from flight control. local HoldingPoint=fc:_GetHoldingpoint(self) p0=HoldingPoint.pos0 p1=HoldingPoint.pos1 p0:MarkToAll("P0") p1:MarkToAll("P1") -- Get active runway. local runway=fc:_GetActiveRunway() -- TODO: make dependend on AC type helos etc. -- Approach point: 10 NN in direction of runway. local papproach=runway.position:Translate(UTILS.NMToMeters(10), runway.direction):SetAltitude(1000) papproach:MarkToAll("Approach Point") -- Approach waypoint. wpap=papproach:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedTo), {}, "Final Approach") -- Set flightcontrol for this flight. self:SetFlightControl(fc) end self.group:ClearTasks() self.flaghold:Set(333) -- Task fuction when reached holding point. local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self) -- Orbit until flaghold=1 (=true) local TaskOrbit=self.group:TaskOrbit(p0, nil, UTILS.KnotsToMps(SpeedHold), p1) local TaskStop=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1) local TaskControlled=self.group:TaskControlled(TaskOrbit, TaskStop) -- Waypoints. local wp={} wp[#wp+1]=self.group:GetCoordinate():WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") wp[#wp+1]= p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskControlled}, "Holding Point") if wpap then wp[#wp+1]=wpap end wp[#wp+1]=airbase:GetCoordinate():WaypointAirLanding(SpeedHold, airbase, {}, "Landing") local respawn=true if respawn then -- Get group template. local Template=self.group:GetTemplate() -- Set route points. Template.route.points=wp MESSAGE:New("Respawning group"):ToAll() --Respawn the group. self.group=self.group:Respawn(Template, true) end self.group:Route(wp, 1) end --- On after TaskPause event. -- @param Wrapper.Group#GROUP group -- @param #FLIGHTGROUP flightgroup function FLIGHTGROUP._ReachedHolding(group, flightgroup) env.info(string.format("FF group %s reached holding point", group:GetName())) group:GetCoordinate():MarkToAll("Holding Point Reached") flightgroup.flaghold:Set(666) if flightgroup.flightcontrol then table.insert(flightgroup.flightcontrol.Qwaiting, flightgroup) end 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.", Task.description, Task.id) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..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 is scheduled (not waypoint) set task. if Task.type==FLIGHTGROUP.TaskType.SCHEDULED then -- Clear all tasks. self.group:ClearTasks() -- Task done. local TaskDone=self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task) -- Combo task. local TaskCombo=self.group:TaskCombo({Task.dcstask, TaskDone}) -- Set task for group. self.group:SetTask(TaskCombo) end end --- On after TaskPause 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:onafterTaskPause(From, Event, To, Task) if self.taskcurrent>0 then -- Clear all tasks. self.group:ClearTasks() -- Task status executing. Task.status=1 --FLIGHTGROUP.TaskStatus.PAUSED end end --- On after "TaskCancel" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #FLIGHTGROUP.Task Task function FLIGHTGROUP:onafterTaskCancel(From, Event, To) -- Get current task. local task=self:GetTaskCurrent() if task then local text=string.format("Task %s ID=%d cancelled.", task.description, task.id) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) -- Clear tasks. self.group:ClearTasks() self:TaskDone(task) else local text=string.format("WARNING: No current task to cancel!") MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) end 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.sid..text) -- No current task. self.taskcurrent=0 -- Task status done. Task.status=FLIGHTGROUP.TaskStatus.ACCOMPLISHED -- Update route. self:_UpdateRoute() 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 -- @parma #string assignment The (optional) assignment for the asset. function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit) self:I(self.sid..string.format("Detected unit %s.", Unit:GetName())) self.detectedunits:AddUnit(Unit) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Get next task in queue. Task needs to be in state SCHEDULED and time must have passed. -- @param #FLIGHTGROUP self function FLIGHTGROUP:GetTask() if #self.taskqueue==0 then return nil else -- Sort results table wrt times they have already been engaged. local function _sort(a, b) local taskA=a --#FLIGHTGROUP.Task local taskB=b --#FLIGHTGROUP.Task return (taskA.time=task.time then return task end end end return nil end --- 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) if #self.taskqueue==0 then return {} else -- Sort results table wrt times they have already been engaged. local function _sort(a, b) local taskA=a --#FLIGHTGROUP.Task local taskB=b --#FLIGHTGROUP.Task return (taskA.time0 then local TaskOverhead=self.group:TaskFunction("FLIGHTGROUP._DestinationOverhead", self, self.destination) local wpoverhead=self.destination:GetCoordinate():SetAltitude(3000):WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, 500, false, nil, {TaskOverhead}, "Destination Overhead") self:I(self.sid..string.format("Adding overhead waypoint as #%d", #wp)) table.insert(wp, #wp, wpoverhead) end self:I(self.sid..string.format("Updating route: #WP=%d homebase=%s destination=%s", #wp, self.homebase and self.homebase:GetName() or "unknown", self.destination and self.destination:GetName() or "unknown")) if #wp>0 then -- Route group to all defined waypoints remaining. self.group:Route(wp, 1) else --- -- No waypoints left --- -- Get destination or home airbase. local airbase=self.destination or self.homebase if self:IsAirborne() then -- TODO: check if no more scheduled tasks. if airbase then -- Route flight to destination/home. self:RTB(airbase) else -- Let flight orbit. --self:Orbit(self.group:GetCoordinate(), UTILS.FeetToMeters(20000), self.group:GetSpeedMax()*0.4) end end end return self end --- Update route of group, e.g after new waypoints and/or waypoint tasks have been added. -- @param Wrapper.Group#GROUP group The Moose group object. -- @param #FLIGHTGROUP flightgroup The flight group object. -- @param Wrapper.Airbase#AIRBASE destination Destination airbase function FLIGHTGROUP._DestinationOverhead(group, flightgroup, destination) -- Tell the flight to hold. -- WARNING: This needs to be delayed or we get a CTD! flightgroup:__Hold(1, destination) end --- Initialize Mission Editor waypoints. -- @param #FLIGHTGROUP self function FLIGHTGROUP:_UpdateWaypointTasks() for i,wp in pairs(self.waypoints) do -- Tasks of this waypoint local taskswp={} -- At each waypoint report passing. local TaskPassingWaypoint=self.group:TaskFunction("FLIGHTGROUP._PassingWaypoint", self, i) table.insert(taskswp, TaskPassingWaypoint) -- Get taks local tasks=self:GetTasksWaypoint(i) if #tasks>0 then for _,task in pairs(tasks) do local Task=task --#FLIGHTGROUP.Task -- Add task execute. table.insert(taskswp, self.group:TaskFunction("FLIGHTGROUP._TaskExecute", self, Task)) -- Add task itself. table.insert(taskswp, Task.dcstask) -- Add task done. table.insert(taskswp, self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task)) end end -- Waypoint task combo. wp.task=self.group:TaskCombo(taskswp) -- Debug info. self:T3({wptask=taskswp}) end end --- Initialize Mission Editor waypoints. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self function FLIGHTGROUP:_InitWaypoints() -- Waypoints of group as defined in the ME. self.waypoints=self.group:GetTemplateRoutePoints() -- Update waypoint tasks. self:_UpdateWaypointTasks() -- Init array. self.coordinates={} -- Set waypoint table. for i,point in ipairs(self.waypoints or {}) do -- Coordinate of the waypoint local coord=COORDINATE:New(point.x, point.alt, point.y) -- Set velocity of the coordinate. coord:SetVelocity(point.speed) -- Add to table. table.insert(self.coordinates, coord) -- Debug info. if self.Debug then coord:MarkToAll(string.format("Flight %s waypoint %d, Speed=%.1f knots", self.groupname, i, UTILS.MpsToKnots(point.speed))) end end -- Set current waypoint. Counting starts a one. self.currentwp=1 return self 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 %s passing waypoint %d of %d.", group:GetName(), i, final) -- Debug smoke and marker. if flightgroup.Debug then local pos=group:GetCoordinate() pos:SmokeRed() local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d", group:GetName(), i)) end -- Debug message. MESSAGE:New(text,10):ToAllIf(flightgroup.Debug) flightgroup:I(flightgroup.sid..text) -- Set current waypoint. flightgroup.currentwp=i -- Passing Waypoint event. flightgroup:PassingWaypoint(i, final) -- If final waypoint reached, do route all over again. if i==final and final>1 then --TODO: final waypoint reached! what next? end end --- Check if a unit is and 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) local similar=true for _,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element -- Dead units dont count. if element.status~=FLIGHTGROUP.ElementStatus.DEAD then if status==FLIGHTGROUP.ElementStatus.SPAWNED then -- Element SPAWNED: Nothing to check. if element.status~=status then end elseif status==FLIGHTGROUP.ElementStatus.PARKING then -- Element PARKING: Check that the other are not stil SPAWNED if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.SPAWNED then return false end elseif status==FLIGHTGROUP.ElementStatus.TAXIING then -- Element TAXIING: Check that the other are not stil SPAWNED or PARKING if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING then return false end elseif status==FLIGHTGROUP.ElementStatus.TAKEOFF then -- Element AIRBORNE: Check that the other are not stil SPAWNED, PARKING or TAXIING if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING or element.status==FLIGHTGROUP.ElementStatus.TAXIING then return false end elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then -- Element AIRBORNE: Check that the other are not stil SPAWNED, PARKING or TAXIING if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING 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 stil AIRBORNE if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.AIRBORNE then return false end elseif status==FLIGHTGROUP.ElementStatus.ARRIVED then -- Element ARRIVED: check that the others are not stil AIRBORNE or TAXIING if element.status~=status and element.status==FLIGHTGROUP.ElementStatus.AIRBORNE or element.status==FLIGHTGROUP.ElementStatus.TAXIING then return false end end 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 --DEAD end 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 local group=element.group if newstatus==FLIGHTGROUP.ElementStatus.SPAWNED then --- -- SPAWNED --- if self:_AllSimilarStatus(newstatus) then self:FlightSpawned() end --[[ if element.unit:InAir() then self:ElementAirborne(element) else self:ElementParking(element) end ]] elseif newstatus==FLIGHTGROUP.ElementStatus.PARKING then --- -- PARKING --- if self:_AllSimilarStatus(newstatus) then self:FlightParking() end elseif newstatus==FLIGHTGROUP.ElementStatus.TAXIING then --- -- TAXIING --- if self:_AllSimilarStatus(newstatus) then self:FlightTaxiing() end elseif newstatus==FLIGHTGROUP.ElementStatus.TAKEOFF then --- -- TAKEOFF --- if self:_AllSimilarStatus(newstatus) then self:FlightTakeoff(airbase) --TODO: also trigger Airborne() ? end elseif newstatus==FLIGHTGROUP.ElementStatus.AIRBORNE then --- -- AIRBORNE --- if self:_AllSimilarStatus(newstatus) then if self:IsTaxiing() then self:FlightAirborne() elseif self:IsParking() then --self:FlightTaxiing(group) self:FlightAirborne() elseif self:IsSpawned() then --self:FlightParking(group) --self:FlightTaxiing(group) self:FlightAirborne() end end elseif newstatus==FLIGHTGROUP.ElementStatus.LANDED then --- -- LANDED --- if self:_AllSimilarStatus(newstatus) then self:FlightLanded(group) 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 --- 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. 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 self:DetectedUnitLost(unit) end end 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.sid..text) else self:T3(self.sid..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) self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------