--- **OPS** - (R2.5) - Manage recovery of aircraft at airdromes. -- -- -- -- **Main Features:** -- -- * Manage aircraft recovery. -- -- === -- -- ### Author: **funkyfranky** -- @module OPS.FlightControl -- @image OPS_FlightControl.png --- FLIGHTCONTROL class. -- @type FLIGHTCONTROL -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string theatre The DCS map used in the mission. -- @field #string lid Class id string for output to DCS log file. -- @field #string airbasename Name of airbase. -- @field #number airbasetype Type of airbase. -- @field Wrapper.Airbase#AIRBASE airbase Airbase object. -- @field Core.Zone#ZONE zoneAirbase Zone around the airbase. -- @field #table parking Parking spots table. -- @field #table runways Runway table. -- @field #table flights All flights table. -- @field #table clients Table with all clients spawning at this airbase. -- @field #table Qinbound Queue of aircraft inbound and traveling to the holding position. -- @field #table Qholding Queue of aircraft waiting for landing permission. -- @field #table Qlanding Queue of aircraft currently on final approach. -- @field #table Qtaxiinb Queue of aircraft taxiing to parking after landing. -- @field #table Qarrived Queue of aircraft that have arrived at their parking spot after landing -- @field #table Qparking Queue of aircraft parking and waiting for taxi & takeoff clearance. -- @field #table Qtaxiout Queue of aircraft taxiing from parking to runway for takeoff. -- @field #table Qreadyto Queue of aircraft ready for takeoff. Only human players. -- @field #table Qtakeoff Queue of aircraft about to takeoff. -- @field Ops.ATIS#ATIS atis ATIS object. -- @field #number activerwyno Number of active runway. -- @field #number atcfreq ATC radio frequency. -- @field Core.RadioQueue#RADIOQUEUE atcradio ATC radio queue. -- @field #table playermenu Player Menu. -- @field #number Nlanding Max number of aircraft groups in the landing pattern. -- @field #number dTlanding Time interval in seconds between landing clearance. -- @field #number Nparkingspots Total number of parking spots. -- @extends Core.Fsm#FSM --- **Ground Control**: Airliner X, Good news, you are clear to taxi to the active. -- **Pilot**: Roger, What's the bad news? -- **Ground Control**: No bad news at the moment, but you probably want to get gone before I find any. -- -- === -- -- ![Banner Image](..\Presentations\FLIGHTCONTROL\FlightControl_Main.jpg) -- -- # The FLIGHTCONTROL Concept -- -- -- -- @field #FLIGHTCONTROL FLIGHTCONTROL = { ClassName = "FLIGHTCONTROL", Debug = false, lid = nil, theatre = nil, airbasename = nil, airbase = nil, airbasetype = nil, zoneAirbase = nil, parking = {}, runways = {}, flights = {}, clients = {}, Qinbound = {}, Qholding = {}, Qlanding = {}, Qtaxiinb = {}, Qarrived = {}, Qparking = {}, Qtaxiout = {}, Qreadyto = {}, Qtakeoff = {}, atis = nil, activerwyno = 1, atcfreq = nil, atcradio = nil, atcradiounitname = nil, playermenu = nil, Nlanding = nil, dTlanding = nil, Nparkingspots = nil, } --- Holding point -- @type FLIGHTCONTROL.HoldingPoint -- @field Core.Point#COORDINATE pos0 First poosition of racetrack holding point. -- @field Core.Point#COORDINATE pos1 Second position of racetrack holding point. -- @field #number angelsmin Smallest holding altitude in angels. -- @field #number angelsmax Largest holding alitude in angels. --- Player menu data. -- @type FLIGHTCONTROL.PlayerMenu -- @field Core.Menu#MENU_GROUP root Root menu. -- @field Core.Menu#MENU_GROUP_COMMAND RequestTaxi Request taxi. --- Parking spot data. -- @type FLIGHTCONTROL.ParkingSpot -- @field #boolean reserved If true, reserved. -- @field #number markerid ID of the marker. -- @extends Wrapper.Airbase#AIRBASE.ParkingSpot --- Runway data. -- @type FLIGHTCONTROL.Runway -- @field #number direction Direction of the runway. -- @field #number length Length of runway in meters. -- @field #number width Width of runway in meters. -- @field Core.Point#COORDINATE position Position of runway start. --- FlightControl class version. -- @field #string version FLIGHTCONTROL.version="0.2.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add FARPS? -- TODO: Add helos. -- TODO: Talk me down option. -- TODO: ATIS option. -- TODO: ATC voice overs. -- TODO: Check runways and clean up. -- TODO: Interface with FLIGHTGROUP. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new FLIGHTCONTROL class object for an associated airbase. -- @param #FLIGHTCONTROL self -- @param #string airbasename Name of the airbase. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:New(airbasename) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTCONTROL -- Try to get the airbase. self.airbase=AIRBASE:FindByName(airbasename) -- Check if the airbase exists. if not self.airbase then self:E(string.format("ERROR: Could not find airbase %s!", tostring(airbasename))) return nil end -- Check if airbase is an airdrome. if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.", tostring(airbasename))) return nil end -- Name of the airbase. self.airbasename=airbasename -- Airbase category airdrome, FARP, SHIP. self.airbasetype=self.airbase:GetAirbaseCategory() -- Set some string id for output to DCS.log file. self.lid=string.format("FLIGHTCONTROL %s | ", airbasename) -- Current map. self.theatre=env.mission.theatre -- 5 NM zone around the airbase. self.zoneAirbase=ZONE_RADIUS:New("FC", self:GetCoordinate():GetVec2(), UTILS.NMToMeters(5)) -- Defaults self:SetLandingMax() self:SetLandingInterval() -- Init runways. self:_InitRunwayData() -- Init parking spots. self:_InitParkingSpots() self.playermenu={} -- Start State. self:SetStartState("Stopped") -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- Update status. -- Debug trace. if false then self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end -- Add to data base. _DATABASE:AddFlightControl(self) return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Set the number of aircraft groups, that are allowed to land simultaniously. -- @param #FLIGHTCONTROL self -- @param #number n Max number of aircraft landing simultaniously. Default 2. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:SetLandingMax(n) self.Nlanding=n or 2 return self end --- Set time interval between landing clearance of groups. -- @param #FLIGHTCONTROL self -- @param #number dt Time interval in seconds. Default 180 sec (3 min). -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:SetLandingInterval(dt) self.dTlanding=dt or 180 return self end --- Set runway. This clears all auto generated runways. -- @param #FLIGHTCONTROL self -- @param #FLIGHTCONTROL.Runway Runway. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:SetRunway(runway) -- Reset table. self.runways={} -- Set runway. table.insert(self.runways, runway) return self end --- Add runway. -- @param #FLIGHTCONTROL self -- @param #FLIGHTCONTROL.Runway Runway. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:AddRunway(runway) -- Set runway. table.insert(self.runways, runway) return self end --- Set active runway number. Counting refers to the position in the table entry. -- @param #FLIGHTCONTROL self -- @param #number no Number in the runways table. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:SetActiveRunwayNumber(no) self.activerwyno=no return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start FLIGHTCONTROL FSM. Handle events. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStart() -- Events are handled my MOOSE. self:I(self.lid..string.format("Starting FLIGHTCONTROL v%s for airbase %s of type %d on map %s", FLIGHTCONTROL.version, self.airbasename, self.airbasetype, self.theatre)) -- Handle events. self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.EngineStartup) self:HandleEvent(EVENTS.Takeoff) self:HandleEvent(EVENTS.Land) self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.Crash) self.atcradio=RADIOQUEUE:New(self.atcfreq or 305, nil, string.format("FC %s", self.airbasename)) self.atcradio:Start(1, 0.1) -- Init status updates. self:__Status(-1) end --- Update status. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStatus() -- Check status of all registered flights. self:_CheckFlights() -- Check parking spots. self:_CheckParking() -- Check waiting and landing queue. self:_CheckQueues() -- Get runway. local runway=self:GetActiveRunway() local Nflights= #self.flights local NQparking=#self.Qparking local NQtaxiout=#self.Qtaxiout local NQreadyto=#self.Qreadyto local NQtakeoff=#self.Qtakeoff local NQinbound=#self.Qinbound local NQholding=#self.Qholding local NQlanding=#self.Qlanding local NQtaxiinb=#self.Qtaxiinb local NQarrived=#self.Qarrived -- ========================================================================================================= local Nqueues = (NQparking+NQtaxiout+NQreadyto+NQtakeoff) + (NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived) -- Count free parking spots. --TODO: get and substract number of reserved parking spots. local nfree=self.Nparkingspots-NQarrived-NQparking -- Info text. local text=string.format("State %s - Runway %s - Parking %d/%d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d", self:GetState(), runway.idx, nfree, self.Nparkingspots, #self.flights, #self.Qparking, #self.Qtaxiout, #self.Qreadyto, #self.Qtakeoff, #self.Qinbound, #self.Qholding, #self.Qlanding, #self.Qtaxiinb, #self.Qarrived) self:I(self.lid..text) if Nflights==Nqueues then --Check! else self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!", Nflights, Nqueues)) end -- Next status update in ~30 seconds. self:__Status(-20) end --- Start FLIGHTCONTROL FSM. Handle events. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStop() -- Handle events. self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.EngineStartup) self:HandleEvent(EVENTS.Takeoff) self:HandleEvent(EVENTS.Land) self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.Crash) self.atcradio:Stop() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Event Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event handler for event birth. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventBirth(EventData) self:F3({EvendData=EventData}) if EventData and EventData.IniGroupName and EventData.Place and EventData.Place:GetName()==self.airbasename then self:I(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) -- We delay this, to have all elements of the group in the game. self:ScheduleOnce(0.1, self._CreateFlightGroup, self, EventData.IniGroup) end end --- Event handler for event land. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventLand(EventData) self:F3({EvendData=EventData}) self:T2(self.lid..string.format("LAND: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("LAND: group = %s", tostring(EventData.IniGroupName))) end --- Event handler for event takeoff. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventTakeoff(EventData) self:F3({EvendData=EventData}) self:T2(self.lid..string.format("TAKEOFF: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("TAKEOFF: group = %s", tostring(EventData.IniGroupName))) -- This would be the closest airbase. local airbase=EventData.Place -- Unit that took off. local unit=EventData.IniUnit -- Nil check for airbase. Crashed as player gave me no airbase. if not (airbase or unit) then self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!") return end end --- Event handler for event engine startup. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventEngineStartup(EventData) self:F3({EvendData=EventData}) self:I(self.lid..string.format("ENGINESTARTUP: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("ENGINESTARTUP: group = %s", tostring(EventData.IniGroupName))) -- Unit that took off. local unit=EventData.IniUnit -- Nil check for unit. if not unit then return end end --- Event handler for event engine shutdown. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventEngineShutdown(EventData) self:F3({EvendData=EventData}) self:I(self.lid..string.format("ENGINESHUTDOWN: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("ENGINESHUTDOWN: group = %s", tostring(EventData.IniGroupName))) -- Unit that took off. local unit=EventData.IniUnit -- Nil check for unit. if not unit then return end end --- Event handler for event crash. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventCrash(EventData) self:F3({EvendData=EventData}) self:T2(self.lid..string.format("CRASH: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("CRASH: group = %s", tostring(EventData.IniGroupName))) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Queue Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Scan airbase zone. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_CheckQueues() -- Print queues if true then self:_PrintQueue(self.flights, "All flights") self:_PrintQueue(self.Qparking, "Parking") self:_PrintQueue(self.Qtaxiout, "TaxiOut") self:_PrintQueue(self.Qreadyto, "ReadyTO") self:_PrintQueue(self.Qtakeoff, "Takeoff") self:_PrintQueue(self.Qinbound, "Inbound") self:_PrintQueue(self.Qholding, "Holding") self:_PrintQueue(self.Qlanding, "Landing") self:_PrintQueue(self.Qtaxiinb, "TaxiInb") self:_PrintQueue(self.Qarrived, "Arrived") end -- Number of holding groups. local nholding=#self.Qholding -- Number of groups landing. local nlanding=#self.Qlanding -- Number of parking groups. local nparking=#self.Qparking -- Number of groups taking off. local ntakeoff=#self.Qtakeoff -- Get next flight in line: either holding or parking. local flight, isholding, parking=self:_GetNextFlight() -- Check if somebody wants something. if flight then if isholding then -------------------- -- Holding flight -- -------------------- -- No other flight is taking off and number of landing flights is below threshold. if ntakeoff==0 and nlanding=self.dTlanding then -- Message. local text=string.format("Flight %s, you are cleared to land.", flight.groupname) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() -- Give AI the landing signal. -- TODO: Humans have to confirm via F10 menu. if flight.ai then flight:FlightLanding() self:_LandAI(flight, parking) end -- Set time last flight got landing clearance. self.Tlanding=timer.getAbsTime() end else self:I(self.lid..string.format("FYI: Landing clearance for flight %s denied as other flights are taking off (N=%d) or max. landing reached (N=%d/%d).", flight.groupname, ntakeoff, nlanding, self.Nlanding)) end else -------------------- -- Takeoff flight -- -------------------- -- No other flight is taking of or landing. if ntakeoff==0 and nlanding==0 then -- Check if flight is AI. Humans have to request taxi via F10 menu. if flight.ai then -- NOTE that AI will start taxiing once they started their engine. -- Message. local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) self:I(self.lid..text) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() -- Start uncontrolled aircraft. -- TODO: handle case with engines hot. That does not trigger a ENGINE_START event. More a FLIGHTGROUP issue. flight.group:StartUncontrolled() env.info("FF remove flight from parking queue - if possible.") self:_RemoveFlightFromQueue(self.Qparking, flight, "parking") -- Add flight to takeoff queue. self:_AddFlightToTakeoffQueue(flight) else local text=string.format("HUMAN Flight %s, you are cleared for takeoff.", flight.groupname) self:I(self.lid..text) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() end else self:I(self.lid..string.format("FYI: Take of for flight %s denied as other flights are taking off (N=%d) or landing (N=%d).", flight.groupname, ntakeoff, nlanding)) end end else self:I(self.lid..string.format("FYI: No flight in queue for takeoff or landing.")) end end --- Get next flight in line, either waiting for landing or waiting for takeoff. -- @param #FLIGHTCONTROL self -- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. -- @return #boolean If true, flight is holding and waiting for landing, if false, flight is parking and waiting for takeoff. -- @return #table Parking data for holding flights or nil. function FLIGHTCONTROL:_GetNextFlight() local flightholding=self:_GetNextFightHolding() local flightparking=self:_GetNextFightParking() -- If no flight is waiting for landing just return the takeoff flight or nil. if not flightholding then return flightparking, false, nil end -- Get number of alive elements of the holding flight. local nH=flightholding:GetNelements() -- Free parking spots. local parking=flightholding:GetParking(self.airbase) -- If no flight is waiting for takeoff return the holding flight or nil. if not flightparking then if parking then return flightholding, true, parking else self:E(string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", #parking, nH)) return nil, nil, nil end end -- We got flights waiting for landing and for takeoff. if flightholding and flightparking then -- Return holding flight if fuel is low. if flightholding.fuellow then if parking then -- Enough parking ==> land return flightholding, true, parking else -- Not enough parking ==> take off return flightparking, false, nil end end -- Return the flight which is waiting longer. if flightholding.Tholding>flightparking.Tparking and parking then return flightholding, true, parking else return flightparking, false, nil end end return nil, nil, nil end --- Get next flight waiting for landing clearance. -- @param #FLIGHTCONTROL self -- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. function FLIGHTCONTROL:_GetNextFightHolding() if #self.Qholding==0 then return nil elseif #self.Qholding==1 then return self.Qholding[1] end -- Sort flights by low fuel. local function _sortByFuel(a, b) local flightA=a --Ops.FlightGroup#FLIGHTGROUP local flightB=b --Ops.FlightGroup#FLIGHTGROUP local fuelA=flightA.group:GetFuelMin() local fuelB=flightB.group:GetFuelMin() return fuelAflightB.Tholding end -- Sort flights by fuel. table.sort(self.Qholding, _sortByFuel) -- Loop over all holding flights. for _,_flight in pairs(self.Qholding) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP -- Return flight that is lowest on fuel. if flight.fuellow then return flight end end -- Return flight waiting longest. table.sort(self.Qholding, _sortByTholding) return self.Qholding[1] end --- Get next flight waiting for taxi and takeoff clearance. -- @param #FLIGHTCONTROL self -- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. function FLIGHTCONTROL:_GetNextFightParking() -- First check human players. if #self.Qtaxiout>0 then -- TODO: Could be sorted by distance to active runway! Take the runway spawn point for distance measure. -- First come, first serve. return self.Qtaxiout[1] end -- Check special cases where only up to one flight is waiting for takeoff. if #self.Qparking==0 then return nil elseif #self.Qparking==1 then return self.Qparking[1] end -- Sort flights parking time. local function _sortByTparking(a, b) local flightA=a --Ops.FlightGroup#FLIGHTGROUP local flightB=b --Ops.FlightGroup#FLIGHTGROUP return flightA.Tparking>flightB.Tparking end -- Return flight waiting longest. table.sort(self.Qparking, _sortByTparking) return self.Qparking[1] end --- Print queue. -- @param #FLIGHTCONTROL self -- @param #table queue Queue to print. -- @param #string name Queue name. -- @return #string Queue text. function FLIGHTCONTROL:_PrintQueue(queue, name) local text=string.format("%s Queue N=%d:", name, #queue) if #queue==0 then -- Queue is empty. text=text.." empty." else local time=timer.getAbsTime() -- Loop over all flights in queue. for i,_flight in ipairs(queue) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP -- Gather info. local fuel=flight.group:GetFuelMin()*100 local ai=tostring(flight.ai) local actype=tostring(flight.actype) -- Holding and parking time. local holding=flight.Tholding and UTILS.SecondsToClock(time-flight.Tholding, true) or "X" local parking=flight.Tparking and UTILS.SecondsToClock(time-flight.Tparking, true) or "X" local holding=flight:GetHoldingTime() if holding>=0 then holding=UTILS.SecondsToClock(holding, true) else holding="X" end local parking=flight:GetParkingTime() if parking>=0 then parking=UTILS.SecondsToClock(parking, true) else parking="X" end local nunits=flight.nunits or 1 -- Main info. text=text..string.format("\n[%d] %s (%s*%d): status=%s, ai=%s, fuel=%d, holding=%s, parking=%s", i, flight.groupname, actype, nunits, flight:GetState(), ai, fuel, holding, parking) -- Elements info. for j,_element in pairs(flight.elements) do local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element local life=element.unit:GetLife() local life0=element.unit:GetLife0() text=text..string.format("\n (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%.1f/%.1f", j, tostring(element.modex), element.name, tostring(element.status), tostring(element.ai), tostring(element.unit:InAir()), life, life0) end end end -- Display text. self:I(self.lid..text) return text end --- Remove a flight group from a queue. -- @param #FLIGHTCONTROL self -- @param #table queue The queue from which the group will be removed. -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group that will be removed from queue. -- @param #string queuename Name of the queue. -- @return #boolean True, flight was in Queue and removed. False otherwise. -- @return #number Table index of removed queue element or nil. function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) queuename=queuename or "unknown" -- Loop over all flights in group. for i,_flight in pairs(queue) do local qflight=_flight --Ops.FlightGroup#FLIGHTGROUP -- Check for name. if qflight.groupname==flight.groupname then self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) table.remove(queue, i) if not flight.ai then flight:_UpdateMenu() end return true, i end end self:I(self.lid..string.format("Could NOT remove flight group %s from %s queue.", flight.groupname, queuename)) return false, nil end --- Add flight to inbound queue (flights that are traveling to the holding pattern). -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToInboundQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qinbound, flight.group) then self:E(self.lid..string.format("WARNING: Flight %s is already in INBOUND queue.", flight.groupname)) return false else self:T(self.lid..string.format("Adding flight %s to INBOUND queue.", flight.groupname)) end -- Add flight to table. table.insert(self.Qinbound, flight) -- Flight is not holding yet. Clear Tholding in any case. flight.Tholding=nil return true end --- Add flight to holding queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToHoldingQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qholding, flight.group) then return false end -- Add flight to table. table.insert(self.Qholding, flight) -- Flight is not holding any more. flight.Tholding=timer.getAbsTime() return true end --- Add flight to landing queue and set recovered to false for all elements of the flight and its section members. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToLandingQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qlanding, flight.group) then return false end -- Add flight to table. table.insert(self.Qlanding, flight) -- Flight is not holding any more. flight.Tholding=nil return true end --- Add flight to taxi inbound queue. These are flights that just landed and are taxiing to their parking position. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToTaxiInboundQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qtaxiinb, flight.group) then return false end -- Add flight to table. table.insert(self.Qtaxiinb, flight) return true end --- Add flight to parking queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToParkingQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qparking, flight.group) then return false end -- Add flight to table. table.insert(self.Qparking, flight) -- Set parking time stamp. This is also done in FLIGHTGROUP onafterFlightParking function. flight.Tparking=timer.getAbsTime() return true end --- Add flight to arrived queue. These are flights that have arrived at their parking position. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToArrivedQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qarrived, flight.group) then return false end -- Add flight to table. table.insert(self.Qarrived, flight) return true end --- Add flight to taxi out for takeoff queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToTaxiOutQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qtaxiout, flight.group) then return false end -- Add flight to table. table.insert(self.Qtaxiout, flight) return true end --- Add flight to taxi out for takeoff queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToReady4TakoffQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qreadyto, flight.group) then return false end -- Add flight to table. table.insert(self.Qreadyto, flight) return true end --- Add flight to takeoff queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #boolean If true, flight was added. False otherwise. function FLIGHTCONTROL:_AddFlightToTakeoffQueue(flight) -- Check if already in queue. if self:_InQueue(self.Qtakeoff, flight.group) then return false end -- Add flight to table. table.insert(self.Qtakeoff, flight) -- New time stamp for time waiting for takeoff. flight.Tparking=nil return true end --- Check if a group is in a queue. -- @param #FLIGHTCONTROL self -- @param #table queue The queue to check. -- @param Wrapper.Group#GROUP group The group to be checked. -- @return #boolean If true, group is in the queue. False otherwise. function FLIGHTCONTROL:_InQueue(queue, group) local name=group:GetName() for _,_flight in pairs(queue) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP if name==flight.groupname then return true end end return false end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Runway Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Initialize data of runways. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_InitRunwayData() self.runways=self.airbase:GetRunwayData() end --- Get the active runway based on current wind direction. -- @param #FLIGHTCONTROL self -- @return Wrapper.Airbase#AIRBASE.Runway Active runway. function FLIGHTCONTROL:GetActiveRunway() return self.airbase:GetActiveRunway() end --- Get the active runway based on current wind direction. -- @param #FLIGHTCONTROL self -- @return #string Runway text, e.g. "31L" or "09". function FLIGHTCONTROL:GetActiveRunwayText() local rwy="" local rwyL if self.atis then rwy, rwyL=self.atis:GetActiveRunway() if rwyL==true then rwy=rwy.."L" elseif rwyL==false then rwy=rwy.."R" end else rwy=self.airbase:GetActiveRunway().idx end return rwy end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Parking Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Init parking spots. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_InitParkingSpots() -- Parking spots of airbase. local parkingdata=self.airbase:GetParkingSpotsTable() -- Init parking spots table. self.parking={} -- 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 local _,TermID, dist, spot=coord:GetClosestParkingSpot(self.airbase) if dist<=10 then self:I(self.lid..string.format("Found client %s on parking spot %d", tostring(unit.name), TermID)) self.parking[TermID].Reserved=tostring(unit.name) end end end end self.Nparkingspots=0 for _,_spot in pairs(parkingdata) do local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot local parking=spot --#FLIGHTCONTROL.ParkingSpot --TODO: reserve client spots. --TODO: scan spot for objects. -- Mark position. local text=string.format("ID=%d, Terminal=%d, Free=%s, Reserved=%s, Dist=%.1f", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.Reserved), parking.DistToRwy) -- Add to table. self.parking[parking.TerminalID]=parking self.Nparkingspots=self.Nparkingspots+1 end -- Check clients. _clients() end --- Get free parking spots. -- @param #FLIGHTCONTROL self -- @param #number terminal Terminal type or nil. -- @return #number Number of free spots. Total if terminal=nil or of the requested terminal type. -- @return #table Table of free parking spots of data type #FLIGHCONTROL.ParkingSpot. function FLIGHTCONTROL:_GetFreeParkingSpots(terminal) local freespots={} local n=0 for _,_parking in pairs(self.parking) do local parking=_parking --#FLIGHTCONTROL.ParkingSpot if parking.Free and parking.TOAC==false and not parking.Reserved then if terminal==nil or terminal==parking.terminal then n=n+1 table.insert(freespots, parking) end end end return n,freespots end --- Update parking spots. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_UpdateParkingSpots() -- Parking spots of airbase. local parkingdata=self.airbase:GetParkingSpotsTable() for _,_parking in pairs(self.parking) do local parking=_parking --#FLIGHTCONTROL.ParkingSpot if parking.markerid then parking.Coordinate:RemoveMark(parking.markerid) parking.markerid=nil end end -- Loop over all spots. local message="Parking Spots:" for _,_spot in pairs(parkingdata) do local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot -- Parking. local parking=self.parking[spot.TerminalID] --#FLIGHTCONTROL.ParkingSpot -- Check if any known flight has reserved this spot. local reserved=self:IsParkingReserved(spot) -- Message text. --local text=string.format("ID=%03d, Terminal=%03d, Free=%s, TOAC=%s, reserved=%s", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.TOAC), tostring(reserved)) local text=string.format("ID=%03d, Terminal=%03d, Free=%s, TOAC=%s, reserved=%s", spot.TerminalID, spot.TerminalType, tostring(spot.Free), tostring(spot.TOAC), tostring(reserved)) -- Place marker on non-free spots. if parking.Free==false or parking.TOAC or reserved~=nil then parking.markerid=parking.Coordinate:MarkToAll(text) message=message.."\n"..text end end -- Debug message. self:I(self.lid..message) end --- Check if a parking spot is reserved by a flight group. -- @param #FLIGHTCONTROL self -- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot to check. -- @return #string Name of element or nil. function FLIGHTCONTROL:IsParkingReserved(spot) local park=self.parking[spot.TerminalID] --Wrapper.Airbase#AIRBASE.ParkingSpot if park.Reserved then return tostring(park.Reserved) end -- Init all elements as NOT parking anywhere. for _,_flight in pairs(self.flights) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP -- Loop over all elements. for _,_element in pairs(flight.elements) do local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element local parking=element.parking if parking and parking.TerminalID==spot.TerminalID then return element.name end end end return nil end --- Get closest parking spot. -- @param #FLIGHTCONTROL self -- @param Core.Point#COORDINATE coordinate Reference coordinate. -- @param #number terminaltype (Optional) Check only this terminal type. -- @param #boolean free (Optional) If true, check only free spots. -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Closest parking spot. function FLIGHTCONTROL:GetClosestParkingSpot(coordinate, terminaltype, free) local distmin=math.huge local spotmin=nil for TerminalID, Spot in pairs(self.parking) do local spot=Spot --Wrapper.Airbase#AIRBASE.ParkingSpot if (not free) or free==true and not (self:IsParkingReserved(spot) or spot.Reserved) then if terminaltype==nil or terminaltype==spot.TerminalType then -- Get distance from coordinate to spot. local dist=coordinate:Get2DDistance(spot.Coordinate) -- Check if distance is smaller. if dist0 then MESSAGE:New("Negative ghostrider, other flights are currently landing. Talk to you soon.", 5):ToAll() self:_AddFlightToReady4TakoffQueue(flight) elseif #self.Qtakeoff>0 then MESSAGE:New("Negative ghostrider, other flights are ahead of you. Talk to you soon.", 5):ToAll() self:_AddFlightToReady4TakoffQueue(flight) end else MESSAGE:New(string.format("Negative, you must request TAXI before you can request TAKEOFF!"), 5):ToAll() end end end --- Player wants to abort takeoff. -- @param #FLIGHTCONTROL self -- @param #string groupname Name of the flight group. function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) MESSAGE:New("Abort takeoff", 5):ToAll() local flight=_DATABASE:GetFlightGroup(groupname) if flight then if self:_InQueue(self.Qtakeoff, flight.group) then MESSAGE:New("Afirm, You are removed from takeoff queue", 5):ToAll() self:_RemoveFlightFromQueue(self.Qtakeoff, flight, "takeoff") --TODO: what now? taxi inbound? or just another later attempt to takeoff. else MESSAGE:New("Negative, You are NOT in the takeoff queue", 5):ToAll() end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Flight and Element Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new flight group. -- @param #FLIGHTCONTROL self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return Ops.FlightGroup#FLIGHTGROUP Flight group. function FLIGHTCONTROL:_CreateFlightGroup(group) -- Check if not already in flights if self:_InQueue(self.flights, group) then self:E(self.lid..string.format("WARNING: Flight group %s does already exist!", group:GetName())) return end -- Debug info. self:I(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) -- Get flightgroup from data base. local flight=_DATABASE:GetFlightGroup(group:GetName()) -- If it does not exist yet, create one. if not flight then flight=FLIGHTGROUP:New(group:GetName()) end --if flight.destination and flight.destination:GetName()==self.airbasename then if flight.homebase and flight.homebase:GetName()==self.airbasename then flight:SetFlightControl(self) end -- Add to known flights. --table.insert(self.flights, flight) return flight end --- Remove flight from all queues. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight The flight to be removed. function FLIGHTCONTROL:_RemoveFlight(flight) self:_RemoveFlightFromQueue(self.Qinbound, flight, "inbound") self:_RemoveFlightFromQueue(self.Qholding, flight, "holding") self:_RemoveFlightFromQueue(self.Qlanding, flight, "landing") self:_RemoveFlightFromQueue(self.Qtaxiinb, flight, "taxiINB") self:_RemoveFlightFromQueue(self.Qarrived, flight, "arrived") self:_RemoveFlightFromQueue(self.Qparking, flight, "parking") self:_RemoveFlightFromQueue(self.Qtaxiout, flight, "taxiOUT") self:_RemoveFlightFromQueue(self.Qreadyto, flight, "readyTO") self:_RemoveFlightFromQueue(self.Qtakeoff, flight, "takeoff") self:_RemoveFlightFromQueue(self.flights, flight, "flights") end --- Get flight from group. -- @param #FLIGHTCONTROL self -- @param Wrapper.Group#GROUP group Group that will be removed from queue. -- @param #table queue The queue from which the group will be removed. -- @return Ops.FlightGroup#FLIGHTGROUP Flight group or nil. -- @return #number Queue index or nil. function FLIGHTCONTROL:_GetFlightFromGroup(group) if group then -- Group name local name=group:GetName() -- Loop over all flight groups in queue for i,_flight in pairs(self.flights) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP if flight.groupname==name then return flight, i end end self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.", name)) end self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) return nil, nil end --- Get element of flight from its unit name. -- @param #FLIGHTCONTROL self -- @param #string unitname Name of the unit. -- @return #FLIGHTCONTROL.FlightElement Element of the flight or nil. -- @return #number Element index or nil. -- @return Ops.FlightGroup#FLIGHTGROUP The Flight group or nil. function FLIGHTCONTROL:_GetFlightElement(unitname) -- Get the unit. local unit=UNIT:FindByName(unitname) -- Check if unit exists. if unit then -- Get flight element from all flights. local flight=self:_GetFlightFromGroup(unit:GetGroup()) -- Check if fight exists. if flight then -- Loop over all elements in flight group. for i,_element in pairs(flight.elements) do local element=_element --#FLIGHTCONTROL.FlightElement if element.unit:GetName()==unitname then return element, i, flight end end self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.", unitname, flight.groupname)) end end return nil, nil, nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Check Sanity Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check status of all registered flights and do some sanity checks. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_CheckFlights() -- First remove all dead flights. for i=#self.flights,1,-1 do local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP if flight:IsDead() then self:I(self.lid..string.format("Removing DEAD flight %s", tostring(flight.groupname))) self:_RemoveFlight(flight) end end --TODO: check parking? -- NOTE: This is now done in FLIGHTGROUP! -- Check if parking flights started to taxi. for _,_flight in pairs(self.Qparking) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP for _,_element in pairs(flight.elements) do local element=_element --Ops.FlightGroup#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 or velocity of unit is >0, 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 or element.unit:GetVelocityMPS()>0 then if element.status==FLIGHTGROUP.ElementStatus.ENGINEON then self.flight: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 end --- Check status of all registered flights and do some sanity checks. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:_CheckParking() for TerminalID,_spot in pairs(self.parking) do local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot if spot.Reserved then if spot.MarkerID then spot.Coordinate:RemoveMark(spot.MarkerID) end spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s", tostring(spot.Reserved)), self:GetCoalition()) end -- First remove all dead flights. for i=1,#self.flights do local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP for _,_element in pairs(flight.elements) do local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element if element.parking and element.parking.TerminalID==TerminalID then if spot.MarkerID then spot.Coordinate:RemoveMark(spot.MarkerID) end spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s", tostring(element.name)), self:GetCoalition()) end end end end end --- Scan airbase zone for units, statics and scenery. -- @param #FLIGHTCONTROL self -- @return Core.Set#SET_UNIT Set of scanned units. -- @return Core.Set#SET_STATIC Set of scanned static objects. function FLIGHTCONTROL:_CheckAirbase() -- Airbase position. local coord=self:GetCoordinate() -- Scan radius = 5 NM. local RCCZ=UTILS.NMToMeters(1) self.scenery=self.scenery or {} local scanscenery=true if #self.scenery>0 then scanscenery=false end -- Debug info. self:T(self.lid..string.format("Scanning airbase zone. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ))) -- Scan units in carrier zone. local _,_,_,unitscan,staticscan,sceneryscan=coord:ScanObjects(RCCZ, true, true, scanscenery) local su=SET_UNIT:New():FilterActive(true) for _,_unit in pairs(unitscan) do local unit=_unit --Wrapper.Unit#UNIT if unit and unit:IsAlive() and unit:InAir()==false then su:AddUnit(unit) end end local setstatic=SET_STATIC:New() for _,static in pairs(staticscan) do local static=STATIC:Find(static) setstatic:AddStatic(static) static:GetCoordinate():MarkToAll("Static") end if scanscenery then for _,scen in pairs(sceneryscan) do --local static=SCENERY: local s=SCENERY:Register(scen:getName(), scen) --s:GetCoordinate():MarkToAll("Scenery") table.insert(self.scenery, s) end end -- Debug info. local text=string.format("Scan found: units=%d, statics=%d, scenery=%d", su:Count(), setstatic:Count(), #self.scenery) self:I(self.lid..text) return su,setstatic end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Tell AI to land at the airbase. Flight is added to the landing queue. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @param #table parking Free parking spots table. function FLIGHTCONTROL:_LandAI(flight, parking) -- Debug info. self:I(self.lid..string.format("Landing AI flight %s.", flight.groupname)) -- Add flight to landing queue. self:_AddFlightToLandingQueue(flight) -- Remove flight from waiting queue. self:_RemoveFlightFromQueue(self.Qholding, flight, "holding") -- Altitude above ground for a glide slope of 3 degrees.. local alpha=math.rad(3) local x1=UTILS.NMToMeters(10) local x2=UTILS.NMToMeters(5) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) local SpeedLand=140 local SpeedTo=180 local runway=self:GetActiveRunway() -- Waypoints. local wp={} -- Current pos. wp[#wp+1]=flight.group:GetCoordinate():WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") -- Approach point: 10 NN in direction of runway. local papp=self.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=self.airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), self.airbase, {}, "Landing") if self.Debug then papp:MarkToAll(string.format("Final Approach: d=%d m, h=%d m", x1, h1)) pland:MarkToAll(string.format("Landing: d=%d m, h=%d m", x2, h2)) end -- Give signal to land. flight.flaghold:Set(1) -- Get group template. local Template=flight.group:GetTemplate() -- Set route points. Template.route.points=wp for i,unit in pairs(Template.units) do local spot=parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot local element=flight:GetElementByName(unit.name) if element then --element.parking=spot unit.parking_landing=spot.TerminalID local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name)) self:I(self.lid..text) self.parking[spot.TerminalID].Reserved=element.name else env.info("FF error could not get element to assign parking!") end end -- Debug message. MESSAGE:New(string.format("Respawning group %s", flight.groupname)):ToAll() --Respawn the group. flight.group=flight.group:Respawn(Template, true) -- Route the group. --flight.group:Route(wp, 1) end --- Get holding point. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #FLIGHTCONTROL.HoldingPoint Holding point. function FLIGHTCONTROL:_GetHoldingpoint(flight) local holdingpoint={} --#FLIGHTCONTROL.HoldingPoint local runway=self:GetActiveRunway() local hdg=runway.heading+90 local dx=UTILS.NMToMeters(5) local dz=UTILS.NMToMeters(1) local angels=UTILS.FeetToMeters(math.random(6,10)*1000) holdingpoint.pos0=runway.position:Translate(dx, hdg):SetAltitude(angels) holdingpoint.pos1=holdingpoint.pos0:Translate(dz, runway.heading):SetAltitude(angels) return holdingpoint end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --TODO: Get landing waypoint to check whether flight is meant to land at this airbase. --- Get coordinate of the airbase. -- @param #FLIGHTCONTROL self -- @return Core.Point#COORDINATE Coordinate of the airbase. function FLIGHTCONTROL:GetCoordinate() return self.airbase:GetCoordinate() end --- Get coalition of the airbase. -- @param #FLIGHTCONTROL self -- @return #number Coalition ID. function FLIGHTCONTROL:GetCoalition() return self.airbase:GetCoalition() end --- Get onboard number of player or client. -- @param #FLIGHTCONTROL self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return #string Onboard number as string. function FLIGHTCONTROL:_GetOnboardNumberPlayer(group) return self:_GetOnboardNumbers(group, true) end --- Get onboard numbers of all units in a group. -- @param #FLIGHTCONTROL self -- @param Wrapper.Group#GROUP group Aircraft group. -- @param #boolean playeronly If true, return the onboard number for player or client skill units. -- @return #table Table of onboard numbers. function FLIGHTCONTROL:_GetOnboardNumbers(group, playeronly) -- Get group name. local groupname=group:GetName() -- Debug text. local text=string.format("Onboard numbers of group %s:", groupname) -- Units of template group. local units=group:GetTemplate().units -- Get numbers. local numbers={} for _,unit in pairs(units) do -- Onboard number and unit name. local n=tostring(unit.onboard_num) local name=unit.name local skill=unit.skill -- Debug text. text=text..string.format("\n- unit %s: onboard #=%s skill=%s", name, n, skill) if playeronly and skill=="Client" or skill=="Player" then -- There can be only one player in the group, so we skip everything else. return n end -- Table entry. numbers[name]=n end -- Debug info. self:T2(self.lid..text) return numbers end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------