--- **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 flight All flights table. -- @field #table Qwaiting Queue of aircraft waiting for landing permission. -- @field #table Qlanding Queue of aircraft currently on final approach. -- @field #table Qtakeoff Queue of aircraft about to takeoff. -- @field #table Qparking Queue of aircraft parking. -- @field #number activerwyno Number of active runway. -- @field #number atcfreq ATC radio frequency. -- @field Core.RadioQueue#RADIOQUEUE atcradio ATC radio queue. -- @extends Core.Fsm#FSM --- Be surprised! -- -- === -- -- ![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 = {}, Qwaiting = {}, Qlanding = {}, Qtakeoff = {}, Qparking = {}, activerwyno = 1, atcfreq = nil, atcradio = nil, atcradiounitname = 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. --- Parking spot data. -- @type FLIGHTCONTROL.ParkingSpot -- @field #number index Parking index. -- @field #number id Parking id. -- @field Core.Point#COORDINATE position Coordinate of the spot. -- @field #number terminal Terminal type. -- @field #boolean free If true, spot is free. -- @field #number drunway Distance to runway. -- @field #boolean reserved If true, reserved. -- @field #number markerid ID of the marker. --- 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.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add FARPS. -- TODO: Add helos. -- TODO: Task 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 self.airbase=AIRBASE:FindByName(airbasename) if not self.airbase then self:E(string.format("ERROR: Could not find airbase %s!", tostring(airbasename))) return nil end if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then self:E(string.format("ERROR: Airbase %s is not an AIRDROMOE! 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:GetDesc().category -- Set some string id for output to DCS.log file. self.lid=string.format("FLIGHTCONTROL %s | ", airbasename) -- Current map. self.theatre=env.mission.theatre -- 30 NM zone around the airbase. self.zoneAirbase=ZONE_RADIUS:New("FC", self:GetCoordinate():GetVec2(), UTILS.NMToMeters(30)) -- Init runways. self:_InitRunwayData() -- Init parking spots. self:_InitParkingSpots() -- 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) --self.dTstatus=0.1 end -- Add to data base. _DATABASE:AddFlightControl(self) return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- 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) self.atcradio:Start(1, 0.01) -- Init status updates. self:__Status(-1) end --- Update status. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStatus() -- Check zone for flights inbound. --self:_CheckInbound() --self:_UpdateParkingSpots() -- Check parking spots. self:_CheckParking() -- Check waiting and landing queue. self:_CheckQueues() -- Get runway. local runway=self:GetActiveRunway() -- Get free parking spots. local nfree=self:_GetFreeParkingSpots() -- Info text. local text=string.format("State %s - Active Runway %s - Free Parking %d", self:GetState(), runway.idx, nfree) self:I(self.lid..text) -- Next status update in ~30 seconds. self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Event Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event handler for event birth. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData function FLIGHTCONTROL:OnEventBirth(EventData) self:F3({EvendData=EventData}) self:T2(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) 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:T2(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:T2(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 --self:_PrintQueue(self.flights, "All flights") self:_PrintQueue(self.Qparking, "Parking") self:_PrintQueue(self.Qtakeoff, "Takeoff") self:_PrintQueue(self.Qwaiting, "Holding") self:_PrintQueue(self.Qlanding, "Landing") -- Number of groups landing. local nlanding=#self.Qlanding -- Number of groups taking off. local ntakeoff=#self.Qtakeoff -- Number of holding groups. local nholding=#self.Qwaiting -- Number of parking groups. local nparking=#self.Qparking local flight, isholding=self:_GetNextFight() if flight and ntakeoff==0 and nlanding==0 then if isholding then -- Message. local text=string.format("Flight %s, you are cleared to land.", flight.groupname) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() flight:FlightLanding() self:_LandAI(flight) else -- Message. local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() flight.group:StartUncontrolled() flight:_UpdateRoute(1) self:_RemoveFlightFromQueue(self.Qparking, flight, "parking") end 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. function FLIGHTCONTROL:_GetNextFight() local flightholding=self:_GetNextFightHolding() local flightparking=self:_GetNextFightParking() -- If no flight is waiting for takeoff return the holding flight or nil. if not flightparking then return flightholding, true end -- If no flight is waiting for landing return the takeoff flight or nil. if not flightholding then return flightparking, false 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 return flightholding, true end -- Return the flight which is waiting longer. if flightholding.Tholding