**AIRBASE**
- Runways are now retrieved from DCS API function
This commit is contained in:
Frank 2022-06-08 23:42:17 +02:00
parent a53595a055
commit 8926e06e44
7 changed files with 393 additions and 47 deletions

View File

@ -1037,7 +1037,7 @@ function DATABASE:_RegisterAirbases()
local airbaseUID=airbase:GetID(true)
-- Debug output.
local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal)
local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType) do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])

View File

@ -1939,6 +1939,7 @@ function WAREHOUSE:New(warehouse, alias)
self:SetMarker(true)
self:SetReportOff()
self:SetRunwayRepairtime()
self.allowSpawnOnClientSpots=false
-- Add warehouse to database.
_WAREHOUSEDB.Warehouses[self.uid]=self
@ -2584,6 +2585,14 @@ function WAREHOUSE:SetSafeParkingOff()
return self
end
--- Set wether client parking spots can be used for spawning.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetAllowSpawnOnClientParking()
self.allowSpawnOnClientSpots=true
return self
end
--- Set low fuel threshold. If one unit of an asset has less fuel than this number, the event AssetLowFuel will be fired.
-- @param #WAREHOUSE self
-- @param #number threshold Relative low fuel threshold, i.e. a number in [0,1]. Default 0.15 (15%).
@ -7878,14 +7887,16 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
-- 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
if not self.allowSpawnOnClientSpots then
local clients=_DATABASE.CLIENTS
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
end
end
end
return coords

View File

@ -317,9 +317,6 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS)
-- Wait at least 10 seconds after last radio message before calling the next status update.
self.dTmessage=10
-- Init runways.
self:_InitRunwayData()
-- Start State.
self:SetStartState("Stopped")
@ -801,7 +798,7 @@ function FLIGHTCONTROL:OnEventBirth(EventData)
-- Create player menu.
self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup)
end
end
-- Spawn parking guard.
if bornhere then
@ -1540,35 +1537,19 @@ 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()
local rwy=self.airbase:GetActiveRunway()
return rwy
end
--- Get the active runway based on current wind direction.
--- Get the name of the active runway.
-- @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
local rwy=self.airbase:GetRunwayName()
return rwy
end
@ -2408,8 +2389,7 @@ function FLIGHTCONTROL:_PlayerVectorInbound(groupname)
local dist=UTILS.MetersToNM(distance)
-- Message text.
local text=string.format("%s, fly heading %03d for %d nautical miles, hold at angels %d.",
callsign, self.alias, heading, dist, flight.stack.angels)
local text=string.format("%s, fly heading %03d for %d nautical miles, hold at angels %d.", callsign, heading, dist, flight.stack.angels)
-- Send message.
self:TextMessageToFlight(text, flight)
@ -2513,16 +2493,24 @@ function FLIGHTCONTROL:_PlayerHolding(groupname)
if stack then
-- Pilot calls inbound for landing.
local text=string.format("%s, %s, arrived at holding pattern", self.alias, callsign)
-- Radio message.
self:TransmissionPilot(text, flight)
-- Current coordinate.
local Coordinate=flight:GetCoordinate(nil, player.name)
-- Distance.
local dist=stack.pos0:Get2DDistance(Coordinate)
if dist<5000 then
local dmax=UTILS.NMToMeters(5)
if dist<dmax then
-- Message to flight
local text=string.format("%s, roger, you are added to the holding queue!", callsign)
local text=string.format("%s, roger, fly heading %d at angels %d.", callsign, stack.heading, stack.angels)
self:TextMessageToFlight(text, flight, 10, true)
-- Call holding event.
@ -2531,13 +2519,15 @@ function FLIGHTCONTROL:_PlayerHolding(groupname)
else
-- Message to flight
local text=string.format("Negative, you have to be within 5 km!")
local text=string.format("Negative, you have to be within %d NM of the arrival zone! You still %d NM away.", UTILS.MetersToNM(dmax), UTILS.MetersToNM(dist))
self:TextMessageToFlight(text, flight, 10, true)
end
else
--TODO: Error not holding stack.
-- Message to flight
local text=string.format("Negative, we have no holding stack for you!")
self:TextMessageToFlight(text, flight, 10, true)
end
else
@ -3419,9 +3409,12 @@ end
-- @param Ops.FlightGroup#FLIGHTGROUP Flight The flight.
-- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec.
function FLIGHTCONTROL:TransmissionTower(Text, Flight, Delay)
-- Spoken text.
local text=self:_GetTextForSpeech(Text)
-- Tower radio call.
self.msrsTower:PlayText(Text, Delay)
self.msrsTower:PlayText(text, Delay)
-- "Subtitle".
if Flight and not Flight.isAI then
@ -3442,9 +3435,12 @@ end
-- @param Ops.FlightGroup#FLIGHTGROUP Flight The flight.
-- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec.
function FLIGHTCONTROL:TransmissionPilot(Text, Flight, Delay)
-- Spoken text.
local text=self:_GetTextForSpeech(Text)
-- Pilot radio call.
self.msrsPilot:PlayText(Text, Delay)
self.msrsPilot:PlayText(text, Delay)
-- "Subtitle".
if Flight and not Flight.isAI then
@ -3564,6 +3560,45 @@ function FLIGHTCONTROL:RemoveParkingGuard(spot, delay)
end
--- Get callsign name of a given flight.
-- @param #FLIGHTCONTROL self
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
-- @return #string Callsign or "Ghostrider 1-1".
function FLIGHTCONTROL:_GetCallsignName(flight)
local callsign=flight:GetCallsignName()
local name=string.match(callsign, "%a+")
local number=string.match(callsign, "%d+")
end
--- Get text for text
-- @param #FLIGHTCONTROL self
-- @param #string text
-- @return #string Callsign or "Ghostrider 1-1".
function FLIGHTCONTROL:_GetTextForSpeech(text)
--- Function to space out text.
local function space(text)
local res=""
for i=1, #text do
local char=text:sub(i,i)
res=res..char.." "
end
return res
end
-- Space out numbers.
local t=text:gsub("(%d+)", space)
return t
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FLIGHTCONTROL self

View File

@ -393,7 +393,7 @@ function FLIGHTGROUP:SetFlightControl(flightcontrol)
-- Check if there is already a FC.
if self.flightcontrol then
if self.flightcontrol.airbasename==flightcontrol.airbasename then
if self.flightcontrol:IsControlling(self) then
-- Flight control is already controlling this flight!
return
else
@ -3680,6 +3680,13 @@ function FLIGHTGROUP:_SetElementParkingAt(Element, Spot)
-- Debug info.
self:T(self.lid..string.format("Element %s is parking on spot %d", Element.name, Spot.TerminalID))
-- Get flightcontrol.
local fc=_DATABASE:GetFlightControl(Spot.AirbaseName)
if fc and not self.flightcontrol then
self:SetFlightControl(fc)
end
if self.flightcontrol then

View File

@ -11684,6 +11684,15 @@ end
-- @return #string Callsign name, e.g. Uzi-1
function OPSGROUP:GetCallsignName()
local element=self:GetElementAlive()
if element then
env.info("FF callsign "..tostring(element.callsign))
return element.callsign
end
--[[
local numberSquad=self.callsign.NumberSquad or self.callsignDefault.NumberSquad
local numberGroup=self.callsign.NumberGroup or self.callsignDefault.NumberGroup
@ -11698,6 +11707,8 @@ function OPSGROUP:GetCallsignName()
else
end
]]
return callsign
end

View File

@ -1153,7 +1153,7 @@ function UTILS.VecHdg(a)
end
--- Calculate "heading" of a 2D vector in the X-Y plane.
-- @param DCS#Vec2 a Vector in "D with x, y components.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @return #number Heading in degrees in [0,360).
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y, a.x))

View File

@ -28,6 +28,7 @@
-- @field #number activerwyno Active runway number (forced).
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
-- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning.
-- @field #table runways Runways of airdromes.
-- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle the DCS Airbase objects:
@ -551,11 +552,17 @@ AIRBASE.SpotStatus = {
--- Runway data.
-- @type AIRBASE.Runway
-- @field #number heading Heading of the runway in degrees.
-- @field #string name Runway name.
-- @field #string idx Runway ID: heading 070° ==> idx="07".
-- @field #number heading True heading of the runway in degrees.
-- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway.
-- @field #number length Length of runway in meters.
-- @field #number width Width of runway in meters.
-- @field Core.Zone#ZONE_POLYGON zone Runway zone.
-- @field Core.Point#COORDINATE center Center of the runway.
-- @field Core.Point#COORDINATE position Position of runway start.
-- @field Core.Point#COORDINATE endpoint End point of runway.
-- @field #boolean isLeft If `true`, this is the left of two parallel runways. If `false`, this is the right of two runways. If `nil`, no parallel runway exists.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Registration
@ -602,6 +609,9 @@ function AIRBASE:Register(AirbaseName)
else
self:E("ERROR: Unknown airbase category!")
end
-- Init Runways.
self:_InitRunways()
-- Init parking spots.
self:_InitParkingSpots()
@ -819,6 +829,42 @@ function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist)
return self
end
--- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS.
-- Note that this DOES NOT remove the airbase from the list. It just makes it unresponsive and silent to any radio calls to it.
-- @param #AIRBASE self
-- @param #boolean Silent If `true`, enable silent mode. If `false` or `nil`, disable silent mode.
-- @return #AIRBASE self
function AIRBASE:SetRadioSilentMode(Silent)
-- Get DCS airbase object.
local airbase=self:GetDCSObject()
-- Set mode.
if airbase then
airbase:setRadioSilentMode(Silent)
end
return self
end
--- Check whether or not the airbase has been silenced.
-- @param #AIRBASE self
-- @return #boolean If `true`, silent mode is enabled.
function AIRBASE:GetRadioSilentMode()
-- Is silent?
local silent=nil
-- Get DCS airbase object.
local airbase=self:GetDCSObject()
-- Set mode.
if airbase then
silent=airbase:getRadioSilentMode()
end
return silent
end
--- Get category of airbase.
-- @param #AIRBASE self
@ -1478,6 +1524,221 @@ end
-- Runway
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get runways.
-- @param #AIRBASE self
-- @return #table Runway data.
function AIRBASE:GetRunways()
return self.runways or {}
end
--- Init runways.
-- @param #AIRBASE self
-- @param #boolean IncludeInverse If `true` or `nil`, include inverse runways.
-- @return #table Runway data.
function AIRBASE:_InitRunways(IncludeInverse)
-- Default is true.
if IncludeInverse==nil then
IncludeInverse=true
end
-- Runway table.
local Runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self.runways={}
return {}
end
--- Function to create a runway data table.
local function _createRunway(name, course, width, length, center)
-- Bearing in rad.
local bearing=-1*course
-- Heading in degrees.
local heading=math.deg(bearing)
-- Data table.
local runway={} --#AIRBASE.Runway
runway.name=string.format("%02d", tonumber(name))
runway.magheading=tonumber(runway.name)*10
runway.heading=heading
runway.width=width or 0
runway.length=length or 0
runway.center=COORDINATE:NewFromVec3(center)
-- Ensure heading is [0,360]
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
-- For example at Nellis, DCS reports two runways, i.e. 03 and 21, BUT the "course" of both is -0.700 rad = 40 deg!
-- As a workaround, I check the difference between the "magnetic" heading derived from the name and the true heading.
-- If this is too large then very likely the "inverse" heading is the one we are looking for.
if math.abs(runway.heading-runway.magheading)>60 then
self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f", runway.name, runway.heading, runway.magheading))
runway.heading=runway.heading-180
end
-- Ensure heading is [0,360]
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
-- Start and endpoint of runway.
runway.position=runway.center:Translate(-runway.length/2, runway.heading)
runway.endpoint=runway.center:Translate( runway.length/2, runway.heading)
local init=runway.center:GetVec3()
local width = runway.width/2
local L2=runway.length/2
local offset1 = {x = init.x + (math.cos(bearing + math.pi) * L2), y = init.z + (math.sin(bearing + math.pi) * L2)}
local offset2 = {x = init.x - (math.cos(bearing + math.pi) * L2), y = init.z - (math.sin(bearing + math.pi) * L2)}
local points={}
points[1] = {x = offset1.x + (math.cos(bearing + (math.pi/2)) * width), y = offset1.y + (math.sin(bearing + (math.pi/2)) * width)}
points[2] = {x = offset1.x + (math.cos(bearing - (math.pi/2)) * width), y = offset1.y + (math.sin(bearing - (math.pi/2)) * width)}
points[3] = {x = offset2.x + (math.cos(bearing - (math.pi/2)) * width), y = offset2.y + (math.sin(bearing - (math.pi/2)) * width)}
points[4] = {x = offset2.x + (math.cos(bearing + (math.pi/2)) * width), y = offset2.y + (math.sin(bearing + (math.pi/2)) * width)}
-- Runway zone.
runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s", self.AirbaseName, runway.name), points)
return runway
end
-- Get DCS object.
local airbase=self:GetDCSObject()
if airbase then
-- Get DCS runways.
local runways=airbase:getRunways()
-- Debug info.
self:T2(runways)
if runways then
-- Loop over runways.
for _,rwy in pairs(runways) do
-- Debug info.
self:T(rwy)
-- Get runway data.
local runway=_createRunway(rwy.Name, rwy.course, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway
-- Add to table.
table.insert(Runways, runway)
-- Include "inverse" runway.
if IncludeInverse then
-- Create "inverse".
local idx=tonumber(runway.name)
local name2=tostring(idx-18)
if idx<18 then
name2=tostring(idx+18)
end
-- Create "inverse" runway.
local runway=_createRunway(name2, rwy.course-math.pi, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway
-- Add inverse to table.
table.insert(Runways, runway)
end
end
end
end
-- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis.
local rpairs={}
for i,_ri in pairs(Runways) do
local ri=_ri --#AIRBASE.Runway
for j,_rj in pairs(Runways) do
local rj=_rj --#AIRBASE.Runway
if i<j then
if ri.name==rj.name then
rpairs[i]=j
end
end
end
end
local function isLeft(a, b, c)
--return ((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0
return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0
end
--[[
local a={x=1, y=0, z=0}
local A={x=0, y=0, z=0}
local b={x=0, y=0, z=1}
local c={x=0, y=0, z=-1}
local bl=isLeft(A, a, b)
local cl=isLeft(A, a, c)
env.info(string.format("b left=%s, c left=%s", tostring(bl), tostring(cl)))
]]
for i,j in pairs(rpairs) do
local ri=Runways[i] --#AIRBASE.Runway
local rj=Runways[j] --#AIRBASE.Runway
-- Draw arrow.
--ri.center:ArrowToAll(rj.center)
local c0=ri.center
-- Vector in the direction of the runway.
local a=UTILS.VecTranslate(c0, 1000, ri.heading)
-- Vector from runway i to runway j.
local b=UTILS.VecSubstract(rj.center, ri.center)
b=UTILS.VecAdd(ri.center, b)
--[[
local ca=COORDINATE:NewFromVec3(a)
local cb=COORDINATE:NewFromVec3(b)
c0:ArrowToAll(ca, nil , {0,1,0})
c0:ArrowToAll(cb, nil , {0,0,1})
]]
-- Check if rj is left of ri.
local left=isLeft(c0, a, b)
--env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left)))
if left then
ri.isLeft=false
rj.isLeft=true
else
ri.isLeft=true
rj.isLeft=false
end
--break
end
-- Set runways.
self.runways=Runways
return Runways
end
--- Get runways data. Only for airdromes!
-- @param #AIRBASE self
-- @param #number magvar (Optional) Magnetic variation in degrees.
@ -1659,7 +1920,10 @@ end
function AIRBASE:GetActiveRunway(magvar)
-- Get runways data (initialize if necessary).
local runways=self:GetRunwayData(magvar)
--local runways=self:GetRunwayData(magvar)
-- Get runway data.
local runways=self:GetRunways()
-- Return user forced active runway if it was set.
if self.activerwyno then
@ -1695,9 +1959,6 @@ function AIRBASE:GetActiveRunway(magvar)
-- Dot product: parallel component of the two vectors.
local dot=UTILS.VecDot(Vwind, Vrunway)
-- Debug.
--env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot))
-- New min?
if dotmin==nil or dot<dotmin then
dotmin=dot
@ -1712,6 +1973,27 @@ function AIRBASE:GetActiveRunway(magvar)
return runways[iact]
end
--- Get name of the runway, e.g. "31L".
-- @param #AIRBASE self
-- @param #AIRBASE.Runway Runway The runway. Default is the active runway.
-- @return #AIRBASE.Runway Active runway data table.
function AIRBASE:GetRunwayName(Runway)
Runway=Runway or self:GetActiveRunway()
local name="Unknown"
if Runway then
name=Runway.name
if Runway.isLeft==true then
name=name.."L"
elseif Runway.isLeft==false then
name=name.."R"
end
end
return name
end
--- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway.
-- @param #AIRBASE self
-- @param Wrapper.Group#GROUP group Group to be checked.