Files
MOOSE/Moose Development/Moose/Ops/FlightControl.lua
T
2019-09-17 22:09:56 +02:00

1215 lines
39 KiB
Lua

--- **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,
}
--- 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)
-- 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 true 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 %03d - Free Parking %d", self:GetState(), runway.direction, 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
-- Get next flight in holding queue.
local flight=self:_GetNextFightHolding()
if flight then
if nlanding==0 and ntakeoff==0 then
-- Message.
local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname)
MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll()
flight:FlightLanding()
self:_LandAI(flight)
end
else
-- Get next wairing flight.
local flight=self:_GetNextFightParking()
if flight and ntakeoff==0 and nlanding==0 then
-- 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 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()
-- Loop over all marshal flights.
for _,_flight in pairs(self.Qwaiting) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
-- TODO: Sort by fuel, holding time.
return flight
end
return 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:_GetNextFightParking()
-- Loop over all marshal flights.
for _,_flight in pairs(self.Qparking) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
-- TODO: Sort by fuel, holding time.
return flight
end
return nil
end
--- Print queue.
-- @param #FLIGHTCONTROL self
-- @param #table queue Queue to print.
-- @param #string name Queue name.
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
-- Loop over all flights in queue.
for i,_flight in ipairs(queue) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
-- Gather info.
local clock="0:0" --UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
local fuel=flight.group:GetFuelMin()*100
local ai=tostring(flight.ai)
local actype=tostring(flight.actype)
local holding=tostring(flight.holding)
local nunits=flight.nunits or 1
-- Main info.
text=text..string.format("\n[%d] %s (%s*%d): ai=%s, timestamp=%s, fuel=%d, inzone=%s, holding=%s",
i, flight.groupname, actype, nunits, ai, clock, fuel, tostring(flight.inzone), holding)
-- 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): ai=%s, status=%s,, airborne=%s life=%.1f/%.1f",
j, tostring(element.modex), element.name, tostring(element.ai) ,tostring(element.status), tostring(element.unit:InAir()), life, life0)
end
end
end
-- Display text.
self:T(self.lid..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)
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 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 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 in pattern.
flight.Tholding=timer.getAbsTime()
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()
-- Get spawn points on runway.
local runwaycoords=self.airbase:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
self:E(self.lid..string.format("Runway coords # = %d", #runwaycoords))
for i=1,#runwaycoords,2 do
-- Assuming each runway has two points.
local j=(i+1)/2
-- Coordinates of the two runway points.
local c1=runwaycoords[i] --Core.Point#COORDINATES
local c2=runwaycoords[i+1] --Core.Point#COORDINATES
-- Debug mark
c1:MarkToAll("Runway Point 1")
c2:MarkToAll("Runway Point 2")
-- Heading of runway.
local hdg=c1:HeadingTo(c2)
-- Debug info.
self:T(self.lid..string.format("Runway %d heading=%03d", j, hdg))
-- Runway table.
local runway={} --#FLIGHTCONTROL.Runway
runway.direction=hdg
runway.length=c1:Get2DDistance(c2)
runway.position=c1
self:I(self.lid..string.format("Adding runway #%d: heading=%03d, lendth=%d m", #self.runways+1, runway.direction, runway.length))
-- Add runway.
table.insert(self.runways, runway)
-- Inverse runway.
local runway={} --#FLIGHTCONTROL.Runway
local hdg=hdg-180
if hdg<0 then
hdg=hdg+360
end
runway.direction=hdg
runway.length=c1:Get2DDistance(c2)
runway.position=c2
self:I(self.lid..string.format("Adding runway #%d: heading=%03d, lendth=%d m", #self.runways+1, runway.direction, runway.length))
-- Add inverse runway.
table.insert(self.runways, runway)
end
end
--- Get the active runway based on current wind direction.
-- @param #FLIGHTCONTROL self
-- @return #FLIGHTCONTROL.Runway Active runway.
function FLIGHTCONTROL:_GetActiveRunway()
-- TODO: get runway.
local i=math.max(self.activerwyno, #self.runways)
return self.runways[i]
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Parking Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Init parking spots.
-- @param #FLIGHTCONTROL self
function FLIGHTCONTROL:_InitParkingSpots()
-- Parking spots of airbase.
local parkingdata=self.airbase:GetParkingSpotsTable()
self.parking={}
for _,_spot in pairs(parkingdata) do
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingData
local parking={} --#FLIGHTCONTROL.ParkingSpot
parking.position=spot.Coordinate
parking.drunway=spot.DistToRwy
parking.terminal=spot.TerminalType
parking.id=spot.TerminalID
parking.free=spot.Free
parking.reserved=spot.TOAC
-- Mark position.
local text=string.format("ID=%d, Terminal=%d, Free=%s, Reserved=%s, Dist=%.1f", parking.id, parking.terminal, tostring(parking.free), tostring(parking.reserved), parking.drunway)
parking.markerid=parking.position:MarkToAll(text)
-- Add to table.
table.insert(self.parking, parking)
end
end
--- Check parking spots.
-- @param #FLIGHTCONTROL self
function FLIGHTCONTROL:_CheckParking()
-- Init all elements as NOT parking anywhere.
for _,_flight in pairs(self.flights) do
-- Loop over all elements.
for _,_element in pairs(_flight.elements) do
local element=_element --#FLIGHTCONTROL.FlightElement
element.parking=false
end
end
-- Loop over all parking spots.
for i,_spot in pairs(self.parking) do
local spot=_spot --#FLIGHTCONTROL.ParkingSpot
-- Assume spot is free.
spot.free=true
-- Loop over all flights.
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
if element.unit and element.unit:IsAlive() then
-- Distance to parking spot.
local dist=element.unit:GetCoordinate():Get3DDistance(spot.position)
-- Element is parking on this spot
if dist<5 and not element.unit:InAir() then
element.parking=true
spot.free=false
end
else
self:E(self.lid..string.format("ERROR: Element %s is not alive any more!", element.name))
end
end
end
end
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 then
if terminal==nil or terminal==parking.terminal then
n=n+1
table.insert(freespots, parking)
end
end
end
return n,freespots
end
--- Init parking spots.
-- @param #FLIGHTCONTROL self
function FLIGHTCONTROL:_UpdateParkingSpots()
-- Parking spots of airbase.
local parkingdata=self.airbase:GetParkingSpotsTable()
local message="Parking Spots:"
for _,_parkingspot in pairs(self.parking) do
local parking=_parkingspot --#FLIGHTCONTROL.ParkingSpot
for _,_spot in pairs(parkingdata) do
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
if parking.id==spot.TerminalID then
parking.position=spot.Coordinate
parking.drunway=spot.DistToRwy
parking.terminal=spot.TerminalType
parking.id=spot.TerminalID
parking.free=spot.Free
parking.reserved=spot.TOAC
-- Mark position.
if parking.markerid then
parking.position:RemoveMark(parking.markerid)
end
local text=string.format("ID=%d, Terminal=%d, Free=%s, Reserved=%s, Dist=%.1f", parking.id, parking.terminal, tostring(parking.free), tostring(parking.reserved), parking.drunway)
message=message.."\n"..text
parking.markerid=parking.position:MarkToAll(text)
break
end
end
end
self:E(self.lid..message)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ATIS Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ATC Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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
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
flight:SetFlightControl(self)
end
--[[
-- New flight.
local flight={} --Ops.FlightGroup#FLIGHTGROUP
-- Flight group name
local groupname=group:GetName()
local human, playername=self:_IsHuman(group)
-- Queue table item.
flight.group=group
flight.groupname=group:GetName()
flight.nunits=#group:GetUnits()
flight.time=timer.getAbsTime()
flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate())
flight.flag=-100
flight.ai=not human
flight.actype=group:GetTypeName()
flight.onboardnumbers=self:_GetOnboardNumbers(group)
flight.inzone=flight.group:IsCompletelyInZone(self.zoneAirbase)
flight.holding=nil
-- Flight elements.
local text=string.format("Flight elements of group %s:", flight.groupname)
flight.elements={}
local units=group:GetUnits()
for i,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
local element={} --#FLIGHTCONTROL.FlightElement
element.unit=unit
element.unitname=unit:GetName()
element.onboard=flight.onboardnumbers[element.unitname]
element.sizemax=unit:GetObjectSize()
element.ai=not self:_IsHumanUnit(unit)
-- Debug text.
text=text..string.format("\n[%d] %s onboard #%s, AI=%s", i, element.unitname, tostring(element.onboard), tostring(element.ai))
-- Add to table.
table.insert(flight.elements, element)
end
self:T(self.lid..text)
-- Onboard.
if flight.ai then
local onboard=flight.onboardnumbers[flight.seclead]
flight.onboard=onboard
else
flight.onboard=self:_GetOnboardNumberPlayer(group)
end
]]
-- Add to known flights.
table.insert(self.flights, flight)
return flight
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
--- Get parking spot of flight element.
-- @param #FLIGHTCONTROL self
-- @param Ops.FlightGroup#FLIGHTGROUP.Element element Element of flight group.
-- @return #FLIGHTCONTROL.ParkingSpot Parking spot of flight element or nil.
function FLIGHTCONTROL:_GetElementParkingSpot(element)
if element then
-- Unit object.
local unit=element.unit
if unit then
local upos=unit:GetCoordinate()
for _,_parkingspot in pairs(self.parking) do
local parkingspot=_parkingspot --#FLIGHTCONTROL.ParkingSpot
-- Spot position.
local spos=parkingspot.position
-- 3D distance from unit to spot.
local dist=spos:Get3DDistance(upos)
-- Distance threshold 5 meters.
if dist<5 then
return parkingspot
end
end
end
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Routing Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Scan airbase zone and find new flights.
-- @param #FLIGHTCONTROL self
function FLIGHTCONTROL:_CheckInbound()
-- Airbase position.
local coord=self:GetCoordinate()
-- Scan radius = 20 NM.
local RCCZ=UTILS.NMToMeters(20)
-- Debug info.
self:T(self.lid..string.format("Scanning airbase zone. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ)))
-- Scan units in carrier zone.
local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false)
-- Make a table with all groups currently in the CCA zone.
local insideZone={}
for _,_unit in pairs(unitscan) do
local unit=_unit --Wrapper.Unit#UNIT
-- Necessary conditions to be met:
local aircraft=unit:IsAir()
local inzone=unit:IsInZone(self.zoneAirbase)
local friendly=self:GetCoalition()==unit:GetCoalition()
-- Check if this an aircraft and that it is inside the airbase zone and friendly.
if aircraft and inzone and friendly then
local group=unit:GetGroup()
local groupname=group:GetName()
-- Add group to table.
if insideZone[groupname]==nil then
insideZone[groupname]=group
end
end
end
-- Find new flights that are inside the airbase zone.
for groupname,_group in pairs(insideZone) do
local group=_group --Wrapper.Group#GROUP
self:_CreateFlightGroup(group)
end
for _,_flight in pairs(self.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
if flight and flight.destination and flight.destination:GetName()==self.airbasename then
-- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available.
if not (self:_InQueue(self.Qwaiting, flight.group) or self:_InQueue(self.Qlanding, flight.group) or flight:IsHolding()) then
self:_WaitAI(flight, 1, true)
end
end
end
end
--- Command AI flight to orbit.
-- @param #FLIGHTCONTROL self
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
-- @param #number stack Holding stack.
-- @param #boolean respawn If true respawn the group. Otherwise reset the mission task with new waypoints.
function FLIGHTCONTROL:_WaitAI(flight, stack, respawn)
-- Set flag to something other than -100 and <0
flight.flag=stack
-- Holding point.
local holding=self:_GetHoldingpoint(flight)
local altitude=holding.pos0.y
local angels=UTILS.MetersToFeet(altitude)/1000
flight:Hold(self.airbase, holding.pos0)
end
--- Tell AI to land at the airbase. Flight is added to the landing queue.
-- @param #FLIGHTCONTROL self
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
function FLIGHTCONTROL:_LandAI(flight)
-- Debug info.
self:I(self.lid..string.format("Landing AI flight %s.", flight.groupname))
-- Add flight to landing queue.
table.insert(self.Qlanding, flight)
-- Give signal to land.
flight.flaghold:Set(1)
-- Remove flight from waiting queue.
self:_RemoveFlightFromQueue(self.Qwaiting, flight, "holding")
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.direction+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.direction):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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------