parking
This commit is contained in:
Frank
2019-11-19 23:08:02 +01:00
parent 8c91c753ee
commit f79ec32c2b
2 changed files with 273 additions and 46 deletions
+27 -38
View File
@@ -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()
+246 -8
View File
@@ -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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------