mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-20 22:03:25 +00:00
FG & FC
parking
This commit is contained in:
@@ -419,23 +419,17 @@ function FLIGHTCONTROL:_CheckQueues()
|
||||
local nparking=#self.Qparking
|
||||
|
||||
-- Get next flight in line: either holding or parking.
|
||||
local flight, isholding=self:_GetNextFlight()
|
||||
local flight, isholding, parking=self:_GetNextFlight()
|
||||
|
||||
|
||||
-- Check if somebody wants something.
|
||||
if flight and ntakeoff==0 and nlanding==0 then
|
||||
if flight and ntakeoff==0 and nlanding==0 then
|
||||
|
||||
if isholding then
|
||||
if isholding and parking then
|
||||
|
||||
--------------------
|
||||
-- Holding flight --
|
||||
--------------------
|
||||
|
||||
-- Get free parking spots.
|
||||
local n,parking=self:_GetFreeParkingSpots()
|
||||
|
||||
-- Get number of alive elements.
|
||||
local Ne=flight:GetNelements()
|
||||
|
||||
-- Message.
|
||||
local text=string.format("Flight %s, you are cleared to land.", flight.groupname)
|
||||
@@ -484,65 +478,60 @@ end
|
||||
-- @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()
|
||||
|
||||
|
||||
-- Free parking spots.
|
||||
local nP=self:_GetFreeParkingSpots()
|
||||
|
||||
-- Get number of alive elements of the holding flight.
|
||||
local nH=0
|
||||
if flightholding then
|
||||
nH=flightholding:GetNelements()
|
||||
-- If no flight is waiting for landing just return the takeoff flight or nil.
|
||||
if not flightholding then
|
||||
return flightparking, false, nil
|
||||
end
|
||||
|
||||
--env.info("FF")
|
||||
--self:I({nextholding=flightholding})
|
||||
--self:I({nextparking=flightparking})
|
||||
--self:I(string.format("GetNext: holding=%d - parking=%d", nH, nP))
|
||||
-- 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 nP>=nH then
|
||||
return flightholding, true
|
||||
if parking then
|
||||
return flightholding, true, parking
|
||||
else
|
||||
self:E(string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", nP, nH))
|
||||
return nil, nil
|
||||
self:E(string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", #parking, nH))
|
||||
return nil, nil, nil
|
||||
end
|
||||
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
|
||||
if nP>=nH then
|
||||
if parking then
|
||||
-- Enough parking ==> land
|
||||
return flightholding, true
|
||||
return flightholding, true, parking
|
||||
else
|
||||
-- Not enough parking ==> take off
|
||||
return flightparking, false
|
||||
return flightparking, false, nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Return the flight which is waiting longer.
|
||||
if flightholding.Tholding>flightparking.Tparking and nP>=nH then
|
||||
return flightholding, true
|
||||
if flightholding.Tholding>flightparking.Tparking and parking then
|
||||
return flightholding, true, parking
|
||||
else
|
||||
return flightparking, false
|
||||
return flightparking, false, nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
|
||||
@@ -941,7 +930,7 @@ function FLIGHTCONTROL:_UpdateParkingSpots()
|
||||
parking.Coordinate:RemoveMark(parking.markerid)
|
||||
end
|
||||
|
||||
local text=string.format("ID=%03d, Terminal=%03d, Free=%s, Reserved=%s, reserved4=%s, Dist=%.1f", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.TOAC), parking.reserved, parking.DistToRwy)
|
||||
local text=string.format("ID=%03d, Terminal=%03d, Free=%s, TOAC=%s, reserved=%s", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.TOAC), parking.reserved)
|
||||
message=message.."\n"..text
|
||||
if parking.Free==false or parking.TOAC or parking.reserved~="none" then
|
||||
parking.markerid=parking.Coordinate:MarkToAll(text)
|
||||
@@ -1459,7 +1448,7 @@ function FLIGHTCONTROL:_LandAI(flight, parking)
|
||||
spot.reserved=unit.name
|
||||
unit.parking_landing=spot.TerminalID
|
||||
local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name))
|
||||
env.info(text)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
MESSAGE:New("Respawning group"):ToAll()
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
-- @field Core.Set#SET_UNIT detectedunits Set of detected units.
|
||||
-- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group.
|
||||
-- @field Wrapper.Airbase#AIRBASE destination The destination base of the flight group.
|
||||
-- @field #string attribute Generalized attribute.
|
||||
-- @field #string actype Type name of the aircraft.
|
||||
-- @field #number speedmax Max speed in km/h.
|
||||
-- @field #number range Range of the aircraft in km.
|
||||
@@ -80,6 +81,7 @@ FLIGHTGROUP = {
|
||||
detectedunits = {},
|
||||
homebase = nil,
|
||||
destination = nil,
|
||||
attribute = nil,
|
||||
actype = nil,
|
||||
speedmax = nil,
|
||||
range = nil,
|
||||
@@ -123,6 +125,27 @@ FLIGHTGROUP.ElementStatus={
|
||||
DEAD="dead",
|
||||
}
|
||||
|
||||
--- Generalized attribute. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
|
||||
-- @type FLIGHTGROUP.Attribute
|
||||
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
|
||||
-- @field #string AWACS Airborne Early Warning and Control System.
|
||||
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
|
||||
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
|
||||
-- @field #string TANKER Airplane which can refuel other aircraft.
|
||||
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
|
||||
-- @field #string ATTACKHELO Attack helicopter.
|
||||
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
|
||||
FLIGHTGROUP.Attribute = {
|
||||
TRANSPORTPLANE="TransportPlane",
|
||||
AWACS="AWACS",
|
||||
FIGHTER="Fighter",
|
||||
BOMBER="Bomber",
|
||||
TANKER="Tanker",
|
||||
TRANSPORTHELO="TransportHelo",
|
||||
ATTACKHELO="AttackHelo",
|
||||
UAV="UAV",
|
||||
OTHER="Other",
|
||||
}
|
||||
|
||||
--- Flight group element.
|
||||
-- @type FLIGHTGROUP.Element
|
||||
@@ -1079,9 +1102,9 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element)
|
||||
else
|
||||
|
||||
-- Get parking spot.
|
||||
local spot, dist=self:GetParkingSpot(Element)
|
||||
local spot=self:GetParkingSpot(Element, 10)
|
||||
|
||||
if dist<10 then
|
||||
if spot then
|
||||
|
||||
Element.parking=spot
|
||||
|
||||
@@ -1185,7 +1208,19 @@ end
|
||||
-- @param #string To To state.
|
||||
-- @param #FLIGHTGROUP.Element Element The flight group element.
|
||||
function FLIGHTGROUP:onafterElementDead(From, Event, To, Element)
|
||||
self:T2(self.sid..string.format("Element dead %s.", Element.name))
|
||||
self:I(self.sid..string.format("Element dead %s.", Element.name))
|
||||
|
||||
-- Clear reserved parking spot.
|
||||
local spot=self:GetParkingSpot(Element, 10)
|
||||
if self.flightcontrol and spot then
|
||||
for _,p in pairs(self.flightcontrol.parking) do
|
||||
local parking=p --Ops.FlightControl#FLIGHTCONTROL.ParkingSpot
|
||||
if parking.TerminalID==spot.TerminalID then
|
||||
parking.reserved="none"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Set element status.
|
||||
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD)
|
||||
@@ -2030,6 +2065,7 @@ function FLIGHTGROUP:AddElementByName(unitname)
|
||||
element.category=element.unit:GetCategory()
|
||||
element.categoryname=element.unit:GetCategoryName()
|
||||
element.callsign=element.unit:GetCallsign()
|
||||
element.size=element.unit:GetObjectSize()
|
||||
|
||||
if element.skill=="Client" or element.skill=="Player" then
|
||||
element.ai=false
|
||||
@@ -3017,12 +3053,12 @@ function FLIGHTGROUP:GetCoalition()
|
||||
return self.group:GetCoalition()
|
||||
end
|
||||
|
||||
--- Returns the coalition side.
|
||||
--- Returns the parking spot of the element.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param #FLIGHTGROUP.Element element Element of the flight group.
|
||||
-- @return Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
-- @return #number Distance to spot in meters.
|
||||
function FLIGHTGROUP:GetParkingSpot(element)
|
||||
-- @param #number maxdist Distance threshold in meters. Default 5 m.
|
||||
-- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold.
|
||||
function FLIGHTGROUP:GetParkingSpot(element, maxdist)
|
||||
|
||||
local coord=element.unit:GetCoordinate()
|
||||
|
||||
@@ -3030,7 +3066,11 @@ function FLIGHTGROUP:GetParkingSpot(element)
|
||||
|
||||
local _,_,dist,spot=coord:GetClosestParkingSpot(ab)
|
||||
|
||||
return spot, dist
|
||||
if dist<=maxdist and not element.unit:InAir() then
|
||||
return spot
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get holding time.
|
||||
@@ -3077,6 +3117,204 @@ function FLIGHTGROUP:GetNelements(status)
|
||||
return n
|
||||
end
|
||||
|
||||
--- Seach unoccupied parking spots at the airbase for all flight elements.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
|
||||
-- @return #table Table of coordinates and terminal IDs of free parking spots.
|
||||
function FLIGHTGROUP:GetParking(airbase)
|
||||
|
||||
-- Init default
|
||||
local scanradius=50
|
||||
local scanunits=true
|
||||
local scanstatics=true
|
||||
local scanscenery=false
|
||||
local verysafe=true
|
||||
|
||||
-- Function calculating the overlap of two (square) objects.
|
||||
local function _overlap(l1,l2,dist)
|
||||
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
|
||||
local safe = (dist > safedist)
|
||||
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
|
||||
return safe
|
||||
end
|
||||
|
||||
-- Get airbase category.
|
||||
local airbasecategory=airbase:GetAirbaseCategory()
|
||||
|
||||
-- Get parking spot data table. This contains all free and "non-free" spots.
|
||||
local parkingdata
|
||||
|
||||
if self.flightcontrol then
|
||||
parkingdata=self.flightcontrol.parking
|
||||
else
|
||||
parkingdata=airbase:GetParkingSpotsTable()
|
||||
end
|
||||
parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
-- List of obstacles.
|
||||
local obstacles={}
|
||||
|
||||
-- Loop over all parking spots and get the currently present obstacles.
|
||||
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
|
||||
-- The alternative would be to perform the scan once but with a much larger radius and store all data.
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
|
||||
-- Scan a radius of 100 meters around the spot.
|
||||
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
|
||||
|
||||
-- Check all units.
|
||||
for _,_unit in pairs(_units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local _coord=unit:GetCoordinate()
|
||||
local _size=self:_GetObjectSize(unit:GetDCSObject())
|
||||
local _name=unit:GetName()
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
|
||||
end
|
||||
|
||||
-- Check all clients.
|
||||
--[[
|
||||
for _,_unit in pairs(_units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local _coord=unit:GetCoordinate()
|
||||
local _size=self:_GetObjectSize(unit:GetDCSObject())
|
||||
local _name=unit:GetName()
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="client"})
|
||||
end
|
||||
]]
|
||||
|
||||
-- Check all statics.
|
||||
for _,static in pairs(_statics) do
|
||||
local _vec3=static:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=static:getName()
|
||||
local _size=self:_GetObjectSize(static)
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
|
||||
end
|
||||
|
||||
-- Check all scenery.
|
||||
for _,scenery in pairs(_sceneries) do
|
||||
local _vec3=scenery:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=scenery:getTypeName()
|
||||
local _size=self:_GetObjectSize(scenery)
|
||||
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Parking data for all assets.
|
||||
local parking={}
|
||||
|
||||
-- Get terminal type.
|
||||
-- TODO: get terminal type
|
||||
--local terminaltype=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
|
||||
local terminaltype=AIRBASE.TerminalType.OpenMedOrBig
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
for i,_element in pairs(self.elements) do
|
||||
local element=_element --#FLIGHTGROUP.Element
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local _toac=parkingspot.TOAC
|
||||
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Safe parking using TO_AC from DCS result.
|
||||
if verysafe and _toac then
|
||||
free=false
|
||||
self:I(self.sid..string.format("Parking spot %d is occupied by other aircraft taking off or landing.", _termid))
|
||||
end
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
local safe=_overlap(element.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this element.
|
||||
table.insert(parking, parkingspot)
|
||||
|
||||
self:I(self.sid..string.format("Parking spot #%d is free for element %s!", _termid, element.name))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
table.insert(obstacles, {coord=_spot, size=element.size, name=element.name, type="element"})
|
||||
|
||||
gotit=true
|
||||
break
|
||||
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
self:I(self.sid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
|
||||
--if self.Debug then
|
||||
-- local coord=problem.coord --Core.Point#COORDINATE
|
||||
-- local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
|
||||
-- coord:MarkToAll(string.format(text))
|
||||
--end
|
||||
|
||||
end
|
||||
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one asset :(
|
||||
if not gotit then
|
||||
self:I(self.sid..string.format("WARNING: No free parking spot for element %s", element.name))
|
||||
return nil
|
||||
end
|
||||
|
||||
end -- loop over asset units
|
||||
|
||||
return parking
|
||||
end
|
||||
|
||||
--- Size of the bounding box of a DCS object derived from the DCS descriptor table. If boundinb box is nil, a size of zero is returned.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param DCS#Object DCSobject The DCS object for which the size is needed.
|
||||
-- @return #number Max size of object in meters (length (x) or width (z) components not including height (y)).
|
||||
-- @return #number Length (x component) of size.
|
||||
-- @return #number Height (y component) of size.
|
||||
-- @return #number Width (z component) of size.
|
||||
function FLIGHTGROUP:_GetObjectSize(DCSobject)
|
||||
local DCSdesc=DCSobject:getDesc()
|
||||
if DCSdesc.box then
|
||||
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) --length
|
||||
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height
|
||||
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) --width
|
||||
return math.max(x,z), x , y, z
|
||||
end
|
||||
return 0,0,0,0
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user