Merge branch 'develop' into FF/Fox2

This commit is contained in:
Frank
2019-10-02 23:41:34 +02:00
2 changed files with 347 additions and 343 deletions
+153 -151
View File
@@ -1,5 +1,5 @@
--- **Ops** - (R2.5) - Automatic Terminal Information Service (ATIS).
--
--
-- ===
--
-- **Main Features:**
@@ -28,9 +28,9 @@
-- ===
--
-- ## Missions: Example missions will be added later.
--
--
-- ===
--
--
-- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel.
--
-- ===
@@ -63,7 +63,7 @@
-- @field #number subduration Duration how long subtitles are displayed in seconds.
-- @field #boolean metric If true, use metric units. If false, use imperial (default).
-- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hecto Pascal (=mili Bars) for metric units.
-- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system.
-- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system.
-- @field #number zuludiff Time difference local vs. zulu in hours.
-- @field #number magvar Magnetic declination/variation at the airport in degrees.
-- @extends Core.Fsm#FSM
@@ -75,108 +75,110 @@
-- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png)
--
-- # The ATIS Concept
--
--
-- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, i.e. airports and their immediate surroundings.
-- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots.
--
--
-- # DCS Limitations
--
--
-- Unfortunately, the DCS API only allow to get the temperature, pressure as well as wind direction and speed. Therefore, some other information such as cloud coverage, base and ceiling are not available
-- when dynamic weather is used.
--
--
-- # Scripting
--
--
-- The lua script to create an ATIS at an airport is pretty easy:
--
--
-- -- ATIS at Batumi Airport on 143.00 MHz AM.
-- atisBatumi=ATIS:New("Batumi", 143.00)
-- atisBatumi:Start()
--
--
-- The @{#ATIS.New}(*airbasename*, *frequency*) creates a new ATIS object. The parameter *airbasename* is the name of the airbase or airport. Note that this has to be spelled exactly as in the DCS mission editor.
-- The parameter *frequency* is the frequency the ATIS broadcasts in MHz.
--
-- Broadcasting is started via the @{#ATIS.Start}() function. The start can be delayed by useing @{#ATIS.__Start}(*delay*), where *delay* is the delay in seconds.
--
--
-- Broadcasting is started via the @{#ATIS.Start}() function. The start can be delayed by useing @{#ATIS.__Start}(*delay*), where *delay* is the delay in seconds.
--
-- ## Subtitles
--
-- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, i.e. airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an
--
-- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, i.e. airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an
-- additonal aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the
-- player has tuned in the correct ATIS frequency.
--
--
-- Radio transmissions via an airborne unit can be set via the @{#ATIS.SetRadioRelayUnitName}(*unitname*) function, where the parameter *unitname* is the name of the unit passed as string, e.g.
--
--
-- atisBatumi:SetRadioRelayUnitName("Radio Relay Batumi")
--
--
-- With a unit set in the mission editor with name "Radio Relay Batumi".
--
--
-- By default, subtitles are displayed for 10 seconds. This can be changed using @{#ATIS.SetSubtitleDuration}(*duration*) with *duration* being the duration in seconds.
--
--
-- ## Active Runway
--
-- By default, the currently active runway is determined automatically by analysing the wind direction. However, there are special cases, where this does not yield the correct result.
-- Also, there are airports with more than one runway facing in the same direction (usually denoted left and right). In this case, there is obviously no *unique* result depending on the wind vector.
--
--
-- By default, the currently active runway is determined automatically by analysing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission.
--
-- Note however, there are a few special cases, where automatic detection does not yield the correct or desired result.
-- For example, there are airports with more than one runway facing in the same direction (usually denoted left and right). In this case, there is obviously no *unique* result depending on the wind vector.
--
-- If the automatic runway detection fails, the active runway can be specified manually in the script via the @{#ATIS.SetActiveRunway}(*runway*) function.
-- The parameter *runway* is a string which can be used to specify the runway heading and, if applicable, whether the left or right runway is in use.
--
--
-- For example, setting runway 21L would be
--
--
-- atisNellis:SetActiveRunway("21L")
--
--
-- The script will examine the string and search for the characters "L" (left) and "R" (right).
--
--
-- If only left or right should be set and the direction determined by the wind vector, the runway heading can be left out, e.g.
--
--
-- atisAbuDhabi:SetActiveRunway("L")
--
--
-- The first two digits of the runway are determined by converting the *true* runway heading into its magnetic heading. The magnetic declination (or variation) is assumed to be constant on the given map.
-- The magnatic declinatin can also be specified for the specific airport using the @{#ATIS.SetMagneticDeclination}(*magvar*).
--
--
-- ## Tower Frequencies
--
--
-- The tower frequency (or frequencies) can also be included in the ATIS information. However, there is no way to get these automatically. Therefore, it is necessary to manually specify them in the script via the
-- @{#ATIS.SetTowerFrequencies}(*frequencies*) function. The parameter *frequencies* can be a plain number if only one frequency is necessary or it can be a table of frequencies.
--
--
-- ## Unit System
--
--
-- By default, information is given in imperial units, i.e. wind speed in knots, pressure in inches of mercury, visibility in Nautical miles, etc.
--
--
-- If you prefer metric units, you can enable this via the @{#ATIS.SetMetricUnits}() function,
--
--
-- atisBatumi:SetMetricUnits()
--
--
-- With this, wind speed is given in meters per second, pressure in hecto Pascal (mbar), visibility in kilometers etc.
--
--
-- # Sound Files
--
-- More than 180 individual sound files have been created using a text-to-speech program. All ATIS information is given with en-US accent.
--
--
-- More than 180 individual sound files have been created using a text-to-speech program. All ATIS information is given with en-US accent.
--
-- Check out the pinned messages in the Moose discord #ops-atis channel.
--
--
-- To include the files, open the mission (.miz) file with, e.g., 7-zip. Then just drag-n-drop the file into the miz.
--
--
-- ![Banner Image](..\Presentations\ATIS\ATIS_SoundFolder.png)
--
--
-- **Note** that the default folder name is *ATIS Soundfiles/*. If you want to change it, you can use the @{#ATIS.SetSoundfilesPath}(*path*), where *path* is the path of the directory. This must end with a slash "/"!
--
--
-- # Examples
--
--
-- ## Caucasus: Batumi
--
--
-- -- ATIS Batumi Airport on 143.00 MHz AM.
-- atisBatumi=ATIS:New(AIRBASE.Caucasus.Batumi, 143.00)
-- atisBatumi:SetRadioRelayUnitName("Radio Relay Batumi")
-- atisBatumi:Start()
--
--
-- ## Nevada: Nellis AFB
--
--
-- -- ATIS Nellis AFB on 270.100 MHz AM.
-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis_AFB, 270.100)
-- atisNellis:SetRadioRelayUnitName("Radio Relay Nellis")
-- atisNellis:SetActiveRunway("21L")
-- atisNellis:SetTowerFrequencies({327.000, 132.550})
-- atisNellis:Start()
--
--
-- ## Persian Gulf: Abu Dhabi International Airport
--
-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_International_Airport, 125.1)
@@ -184,7 +186,7 @@
-- atisAbuDhabi:SetMetricUnits()
-- atisAbuDhabi:SetActiveRunway("L")
-- atisAbuDhabi:Start()
--
--
--
-- @field #ATIS
ATIS = {
@@ -271,24 +273,24 @@ function ATIS:New(airbasename, frequency, modulation)
-- Inherit everything from WAREHOUSE class.
local self=BASE:Inherit(self, FSM:New()) -- #ATIS
self.airbasename=airbasename
self.airbase=AIRBASE:FindByName(airbasename)
if self.airbase==nil then
self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(airbasename))
end
-- Default freq and modulation.
self.frequency=frequency or 143.00
self.modulation=modulation or 0
-- Get map.
self.theatre=env.mission.theatre
-- Set some string id for output to DCS.log file.
self.lid=string.format("ATIS %s | ", self.airbasename)
-- Defaults:
self:SetSoundfilesPath()
self:SetSubtitleDuration()
@@ -302,7 +304,7 @@ function ATIS:New(airbasename, frequency, modulation)
self:AddTransition("*", "Status", "*") -- Update status.
self:AddTransition("*", "Broadcast", "*") -- Broadcast ATIS message.
self:AddTransition("*", "CheckQueue", "*") -- Check if radio queue is empty.
------------------------
--- Pseudo Functions ---
------------------------
@@ -367,7 +369,7 @@ end
function ATIS:SetTowerFrequencies(freqs)
if type(freqs)=="table" then
-- nothing to do
else
else
freqs={freqs}
end
self.towerfrequency=freqs
@@ -410,7 +412,7 @@ function ATIS:SetImperialUnits()
end
--- Set pressure unit to millimeters of mercury (mmHg).
-- Default is inHg for imperial and hPa (=mBar) for metric units.
-- Default is inHg for imperial and hPa (=mBar) for metric units.
-- @param #ATIS self
-- @return #ATIS self
function ATIS:SetPressureMillimetersMercury()
@@ -427,14 +429,14 @@ function ATIS:SetTemperatureFahrenheit()
end
--- Set magnetic declination/variation at the airport.
--
--
-- Default is per map:
--
--
-- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944
-- * Persian Gulf +2 (East), year ~ 2011
--
--
-- @param #ATIS self
-- @param #number magvar Magnetic variation in degrees.
-- @return #ATIS self
@@ -445,12 +447,12 @@ end
--- Set time local difference with respect to Zulu time.
-- Default is per map:
--
--
-- * Caucasus +4
-- * Nevada -7
-- * Normandy +1
-- * Persian Gulf +4
--
--
-- @param #ATIS self
-- @param #number delta Time difference in hours.
-- @return #ATIS self
@@ -469,16 +471,16 @@ function ATIS:onafterStart(From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation))
-- Start radio queue.
self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation)
-- Send coordinate is airbase coord.
self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate())
-- Set relay unit if we have one.
self.radioqueue:SetSenderUnitName(self.relayunitname)
-- Init numbers.
self.radioqueue:SetDigit(0, "N-0.ogg", 0.55, self.soundpath)
self.radioqueue:SetDigit(1, "N-1.ogg", 0.40, self.soundpath)
@@ -490,7 +492,7 @@ function ATIS:onafterStart(From, Event, To)
self.radioqueue:SetDigit(7, "N-7.ogg", 0.42, self.soundpath)
self.radioqueue:SetDigit(8, "N-8.ogg", 0.37, self.soundpath)
self.radioqueue:SetDigit(9, "N-9.ogg", 0.38, self.soundpath)
-- Start radio queue.
self.radioqueue:Start(1, 0.1)
@@ -505,11 +507,11 @@ function ATIS:onafterStatus(From, Event, To)
-- Get FSM state.
local fsmstate=self:GetState()
-- Info text.
local text=string.format("State %s", fsmstate)
self:I(self.lid..text)
self:__Status(30)
end
@@ -527,7 +529,7 @@ function ATIS:onafterCheckQueue(From, Event, To)
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
end
-- Check back in 5 seconds.
self:__CheckQueue(5)
end
@@ -538,29 +540,29 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Get current coordinate.
local coord=self.airbase:GetCoordinate()
-- Get elevation.
local height=coord:GetLandHeight()+10
----------------
--- Pressure ---
----------------
-- Pressure in hPa.
local qfe=coord:GetPressure(height)
local qnh=coord:GetPressure(0)
-- Convert to inHg.
if self.PmmHg then
qfe=UTILS.hPa2mmHg(qfe)
qnh=UTILS.hPa2mmHg(qnh)
else
else
if not self.metric then
qfe=UTILS.hPa2inHg(qfe)
qnh=UTILS.hPa2inHg(qnh)
end
end
local QFE=UTILS.Split(string.format("%.2f", qfe), ".")
local QNH=UTILS.Split(string.format("%.2f", qnh), ".")
@@ -573,18 +575,18 @@ function ATIS:onafterBroadcast(From, Event, To)
QNH=UTILS.Split(string.format("%.1f", qnh), ".")
end
end
--------------
--- Runway ---
--------------
-- Get runway based on wind direction.
local runway=self.airbase:GetActiveRunway(self.magvar).idx
-- Left or right in case there are two runways with the same heading.
local rleft=false
local rright=false
-- Check if user explicitly specified a runway.
if self.activerunway then
local runwayno=self.activerunway:gsub("%D+", "")
@@ -594,27 +596,27 @@ function ATIS:onafterBroadcast(From, Event, To)
rleft=self.activerunway:lower():find("l")
rright=self.activerunway:lower():find("r")
end
------------
--- Wind ---
------------
-- Get wind direction and speed in m/s.
local windFrom, windSpeed=coord:GetWind(height)
local WINDFROM=string.format("%03d", windFrom)
local WINDSPEED=string.format("%d", UTILS.MpsToKnots(windSpeed))
if self.metric then
WINDSPEED=string.format("%d", windSpeed)
end
------------
--- Time ---
------------
local time=timer.getAbsTime()
-- Conversion to Zulu time.
if self.zuludiff then
-- User specified.
@@ -630,43 +632,43 @@ function ATIS:onafterBroadcast(From, Event, To)
time=time-1*60*60 -- Calais UTC+1 hour
end
end
local clock=UTILS.SecondsToClock(time)
local zulu=UTILS.Split(clock, ":")
local ZULU=string.format("%s%s", zulu[1], zulu[2])
-- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc.
local NATO=ATIS.Alphabet[tonumber(zulu[1])+1]
-- Debug.
self:T3({nato=NATO})
-------------------
--- Temperature ---
-------------------
-- Temperature in °C.
-- Temperature in C.
local temperature=coord:GetTemperature(height)
local TEMPERATURE=string.format("%d", temperature)
if self.TDegF then
TEMPERATURE=string.format("%d", UTILS.CelciusToFarenheit(temperature))
end
---------------
--- Weather ---
---------------
-- Get mission weather info. Most of this is static.
local clouds, visibility, turbulence, fog, dust, static=self:GetMissionWeather()
-- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level.
if fog and fog.thickness<height then
fog=nil
end
-- Dust only up to 1500 ft = 457 m ASL.
if dust and height>UTILS.FeetToMeters(1500) then
dust=nil
@@ -675,63 +677,63 @@ function ATIS:onafterBroadcast(From, Event, To)
------------------
--- Visibility ---
------------------
-- Get min visibility.
local visibilitymin=visibility
if fog then
if fog.visibility<visibilitymin then
visibilitymin=fog.visibility
end
end
end
if dust then
if dust<visibilitymin then
visibilitymin=dust
end
end
-- Visibility in NM.
local VISIBILITY=string.format("%d", UTILS.Round(UTILS.MetersToNM(visibilitymin)))
-- Visibility in km.
if self.metric then
VISIBILITY=string.format("%d", UTILS.Round(visibilitymin/1000))
end
--------------
--- Clouds ---
--------------
local cloudbase=clouds.base
local cloudceil=clouds.base+clouds.thickness
local clouddens=clouds.density
-- Precepitation: 0=None, 1=Rain, 2=Thunderstorm.
local precepitation=tonumber(clouds.iprecptns)
local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase))
local CLOUDCEIL=string.format("%d", UTILS.MetersToFeet(cloudceil))
if self.metric then
CLOUDBASE=string.format("%d", cloudbase)
CLOUDCEIL=string.format("%d", cloudceil)
end
-- Cloud base/ceiling in thousands and hundrets of ft/meters.
local CLOUDBASE1000, CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase))
local CLOUDCEIL1000, CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil))
if self.metric then
CLOUDBASE1000, CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase)
CLOUDCEIL1000, CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil)
end
-- No cloud info for dynamic weather.
local CLOUDSogg="CloudsNotAvailable.ogg"
local CLOUDSsub="Cloud coverage information not available"
local CLOUDSdur=2.40
-- Only valid for static weather.
if static then
if clouddens>=9 then
@@ -763,31 +765,31 @@ function ATIS:onafterBroadcast(From, Event, To)
CLOUDSdur=1.00
end
end
--------------------
--- Transmission ---
--------------------
local subduration=self.subduration
local subtitle=""
--Airbase name
subtitle=string.format("%s", self.airbasename)
if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then
subtitle=subtitle.." Airport"
end
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, subduration)
-- Information tag
subtitle=string.format("Information %s", NATO)
self.radioqueue:NewTransmission("Information.ogg", 0.85, self.soundpath, nil, 0.5, subtitle, subduration)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
-- Zulu Time
subtitle=string.format("%s Zulu Time", ZULU)
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self.radioqueue:NewTransmission("TimeZulu.ogg", 0.89, self.soundpath, nil, 0.2, subtitle, subduration)
-- Visibility
if self.metric then
subtitle=string.format("Visibility %s km", VISIBILITY)
@@ -799,9 +801,9 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.metric then
self.radioqueue:NewTransmission("Kilometers.ogg", 0.78, self.soundpath, nil, 0.2)
else
self.radioqueue:NewTransmission("NauticalMiles.ogg", 1.05, self.soundpath, nil, 0.2)
self.radioqueue:NewTransmission("NauticalMiles.ogg", 1.05, self.soundpath, nil, 0.2)
end
-- Cloud base
self.radioqueue:NewTransmission(CLOUDSogg, CLOUDSdur, self.soundpath, nil, 1.0, CLOUDSsub, subduration)
if CLOUDBASE and static then
@@ -815,7 +817,7 @@ function ATIS:onafterBroadcast(From, Event, To)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self.radioqueue:NewTransmission("Thousand.ogg", 0.55, self.soundpath, nil, 0.1)
end
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self.radioqueue:NewTransmission("Hundred.ogg", 0.47, self.soundpath, nil, 0.1)
@@ -825,7 +827,7 @@ function ATIS:onafterBroadcast(From, Event, To)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self.radioqueue:NewTransmission("Thousand.ogg", 0.55, self.soundpath, nil, 0.1)
end
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self.radioqueue:NewTransmission("Hundred.ogg", 0.47, self.soundpath, nil, 0.1)
@@ -836,7 +838,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:NewTransmission("Feet.ogg", 0.45, self.soundpath, nil, 0.1)
end
end
-- Weather phenomena
local wp=false
local wpsub=""
@@ -853,16 +855,16 @@ function ATIS:onafterBroadcast(From, Event, To)
if fog then
if wp then
wpsub=wpsub..","
end
end
wpsub=wpsub.." fog"
wp=true
wp=true
end
if dust then
if wp then
wpsub=wpsub..","
end
end
wpsub=wpsub.." dust"
wp=true
wp=true
end
-- Actual output
if wp then
@@ -876,14 +878,14 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:NewTransmission("Fog.ogg", 0.81, self.soundpath, nil, 0.5)
end
if dust then
self.radioqueue:NewTransmission("Dust.ogg", 0.81, self.soundpath, nil, 0.5)
end
end
self.radioqueue:NewTransmission("Dust.ogg", 0.81, self.soundpath, nil, 0.5)
end
end
-- Altimeter QNH/QFE.
if self.PmmHg then
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
else
else
if self.metric then
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
else
@@ -908,7 +910,7 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:NewTransmission("InchesOfMercury.ogg", 1.16, self.soundpath, nil, 0.1)
end
end
-- Temperature
if self.TDegF then
subtitle=string.format("Temperature %s °F", TEMPERATURE)
@@ -922,7 +924,7 @@ function ATIS:onafterBroadcast(From, Event, To)
else
self.radioqueue:NewTransmission("DegreesCelsius.ogg", 1.28, self.soundpath, nil, 0.2)
end
-- Wind
if self.metric then
subtitle=string.format("Wind from %s at %s m/s", WINDFROM, WINDSPEED)
@@ -944,7 +946,7 @@ function ATIS:onafterBroadcast(From, Event, To)
if turbulence>0 then
self.radioqueue:NewTransmission("Gusting.ogg", 0.55, self.soundpath, nil, 0.2)
end
-- Active runway.
local subtitle=string.format("Active runway %s", runway)
if rleft then
@@ -959,7 +961,7 @@ function ATIS:onafterBroadcast(From, Event, To)
elseif rright then
self.radioqueue:NewTransmission("Right.ogg", 0.43, self.soundpath, nil, 0.2)
end
-- Tower frequency.
if self.towerfrequency then
local freqs=""
@@ -972,14 +974,14 @@ function ATIS:onafterBroadcast(From, Event, To)
self.radioqueue:NewTransmission("TowerFrequency.ogg", 1.19, self.soundpath, nil, 1.0, string.format("Tower frequency %s", freqs), subduration)
for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq)
f=UTILS.Split(f, ".")
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
self.radioqueue:NewTransmission("Decimal.ogg", 0.58, self.soundpath, nil, 0.2)
self.radioqueue:Number2Transmission(f[2])
self.radioqueue:NewTransmission("MegaHertz.ogg", 0.86, self.soundpath, nil, 0.2)
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -990,7 +992,7 @@ end
-- @param #ATIS self
-- @return #table Clouds table which has entries "thickness", "density", "base", "iprecptns".
-- @return #number Visibility distance in meters.
-- @return #number Ground turbulence in m/s.
-- @return #number Ground turbulence in m/s.
-- @return #table Fog table, which has entries "thickness", "visibility" or nil if fog is disabled in the mission.
-- @return #number Dust density or nil if dust is disabled in the mission.
-- @return #boolean static If true, static weather is used. If false, dynamic weather is used.
@@ -1010,13 +1012,13 @@ function ATIS:GetMissionWeather()
}, -- end of ["clouds"]
]]
local clouds=weather.clouds
-- 0=static, 1=dynamic
local static=weather.atmosphere_type==0
-- Visibilty distance in meters.
local visibility=weather.visibility.distance
-- Ground turbulence.
local turbulence=weather.groundTurbulence
@@ -1063,12 +1065,12 @@ end
function ATIS:_GetThousandsAndHundreds(n)
local N=UTILS.Round(n/1000, 1)
local S=UTILS.Split(string.format("%.1f", N), ".")
local t=S[1]
local h=S[2]
return t, h
end
+194 -192
View File
@@ -1,13 +1,13 @@
--- **Wrapper** -- AIRBASE is a wrapper class to handle the DCS Airbase objects.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
--
--
-- ### Contributions: **funkyfranky**
--
--
-- ===
--
--
-- @module Wrapper.Airbase
-- @image Wrapper_Airbase.JPG
@@ -19,40 +19,40 @@
-- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle the DCS Airbase objects:
--
--
-- * Support all DCS Airbase APIs.
-- * Enhance with Airbase specific APIs not in the DCS Airbase API set.
--
--
-- ## AIRBASE reference methods
--
--
-- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object.
-- This is done at the beginning of the mission (when the mission starts).
--
--
-- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference
-- using the DCS Airbase or the DCS AirbaseName.
--
-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object.
--
-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object.
-- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution.
-- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file.
--
--
-- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance:
--
--
-- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object.
-- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name.
--
--
-- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil).
--
--
-- ## DCS Airbase APIs
--
--
-- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method.
-- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call,
-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSWrapper.Airbase#Airbase.getName}()
-- is implemented in the AIRBASE class as @{#AIRBASE.GetName}().
--
--
-- @field #AIRBASE AIRBASE
AIRBASE = {
ClassName="AIRBASE",
CategoryName = {
CategoryName = {
[Airbase.Category.AIRDROME] = "Airdrome",
[Airbase.Category.HELIPAD] = "Helipad",
[Airbase.Category.SHIP] = "Ship",
@@ -61,9 +61,9 @@ AIRBASE = {
}
--- Enumeration to identify the airbases in the Caucasus region.
--
--
-- These are all airbases of Caucasus:
--
--
-- * AIRBASE.Caucasus.Gelendzhik
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
-- * AIRBASE.Caucasus.Sukhumi_Babushara
@@ -85,7 +85,7 @@ AIRBASE = {
-- * AIRBASE.Caucasus.Nalchik
-- * AIRBASE.Caucasus.Mozdok
-- * AIRBASE.Caucasus.Beslan
--
--
-- @field Caucasus
AIRBASE.Caucasus = {
["Gelendzhik"] = "Gelendzhik",
@@ -112,7 +112,7 @@ AIRBASE.Caucasus = {
}
--- These are all airbases of Nevada:
--
--
-- * AIRBASE.Nevada.Creech_AFB
-- * AIRBASE.Nevada.Groom_Lake_AFB
-- * AIRBASE.Nevada.McCarran_International_Airport
@@ -131,7 +131,7 @@ AIRBASE.Caucasus = {
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
-- * AIRBASE.Nevada.Tonopah_Airport
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
-- @field Nevada
-- @field Nevada
AIRBASE.Nevada = {
["Creech_AFB"] = "Creech AFB",
["Groom_Lake_AFB"] = "Groom Lake AFB",
@@ -154,7 +154,7 @@ AIRBASE.Nevada = {
}
--- These are all airbases of Normandy:
--
--
-- * AIRBASE.Normandy.Saint_Pierre_du_Mont
-- * AIRBASE.Normandy.Lignerolles
-- * AIRBASE.Normandy.Cretteville
@@ -264,7 +264,7 @@ AIRBASE.PersianGulf = {
["Bandar_Abbas_Intl"] = "Bandar Abbas Intl",
["Bandar_Lengeh"] = "Bandar Lengeh",
["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield",
["Dubai_Intl"] = "Dubai Intl",
["Dubai_Intl"] = "Dubai Intl",
["Fujairah_Intl"] = "Fujairah Intl",
["Havadarya"] = "Havadarya",
["Jiroft_Airport"] = "Jiroft Airport",
@@ -284,7 +284,7 @@ AIRBASE.PersianGulf = {
["Tunb_Island_AFB"] = "Tunb Island AFB",
["Tunb_Kochak"] = "Tunb Kochak",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
-- @type AIRBASE.ParkingSpot
-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot.
@@ -294,11 +294,11 @@ AIRBASE.PersianGulf = {
-- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment.
-- @field #number TerminalID0 Unknown what this means. If you know, please tell us!
-- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID.
--- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking
--
--
-- Supported types are:
--
--
-- * AIRBASE.TerminalType.Runway = 16: Valid spawn points on runway.
-- * AIRBASE.TerminalType.HelicopterOnly = 40: Special spots for Helicopers.
-- * AIRBASE.TerminalType.Shelter = 68: Hardened Air Shelter. Currently only on Caucaus map.
@@ -307,7 +307,7 @@ AIRBASE.PersianGulf = {
-- * AIRBASE.TerminalType.OpenMedOrBig = 176: Combines OpenMed and OpenBig spots.
-- * AIRBASE.TerminalType.HelicopterUnsable = 216: Combines HelicopterOnly, OpenMed and OpenBig.
-- * AIRBASE.TerminalType.FighterAircraft = 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
--
--
-- @type AIRBASE.TerminalType
-- @field #number Runway 16: Valid spawn points on runway.
-- @field #number HelicopterOnly 40: Special spots for Helicopers.
@@ -331,13 +331,13 @@ AIRBASE.TerminalType = {
--- Runway data.
-- @type AIRBASE.Runway
-- @field #number heading Heading of the runway in degrees.
-- @field #string idx Runway ID: heading 070° ==> idx="07".
-- @field #string idx Runway ID: heading 070 ==> idx="07".
-- @field #number length Length of runway in meters.
-- @field Core.Point#COORDINATE position Position of runway start.
-- @field Core.Point#COORDINATE endpoint End point of runway.
-- Registration.
--- Create a new AIRBASE from DCSAirbase.
-- @param #AIRBASE self
-- @param #string AirbaseName The name of the airbase.
@@ -368,7 +368,7 @@ end
-- @param #string AirbaseName The Airbase Name.
-- @return #AIRBASE self
function AIRBASE:FindByName( AirbaseName )
local AirbaseFound = _DATABASE:FindAirbase( AirbaseName )
return AirbaseFound
end
@@ -378,18 +378,18 @@ end
-- @param #number id Airbase ID.
-- @return #AIRBASE self
function AIRBASE:FindByID(id)
for name,_airbase in pairs(_DATABASE.AIRBASES) do
local airbase=_airbase --#AIRBASE
local aid=tonumber(airbase:GetID())
if aid==id then
return airbase
end
end
return nil
end
@@ -398,11 +398,11 @@ end
-- @return DCS#Airbase DCS airbase object.
function AIRBASE:GetDCSObject()
local DCSAirbase = Airbase.getByName( self.AirbaseName )
if DCSAirbase then
return DCSAirbase
end
return nil
end
@@ -418,7 +418,7 @@ end
-- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP
-- @return #table Table containing all airbase objects of the current map.
function AIRBASE.GetAllAirbases(coalition, category)
local airbases={}
for _,_airbase in pairs(_DATABASE.AIRBASES) do
local airbase=_airbase --#AIRBASE
@@ -428,28 +428,28 @@ function AIRBASE.GetAllAirbases(coalition, category)
end
end
end
return airbases
end
--- Returns a table of parking data for a given airbase. If the optional parameter *available* is true only available parking will be returned, otherwise all parking at the base is returned. Term types have the following enumerated values:
--
--
-- * 16 : Valid spawn points on runway
-- * 40 : Helicopter only spawn
-- * 40 : Helicopter only spawn
-- * 68 : Hardened Air Shelter
-- * 72 : Open/Shelter air airplane only
-- * 104: Open air spawn
--
--
-- Note that only Caucuses will return 68 as it is the only map currently with hardened air shelters.
-- 104 are also generally larger, but does not guarantee a large aircraft like the B-52 or a C-130 are capable of spawning there.
--
--
-- Table entries:
--
--
-- * Term_index is the id for the parking
-- * vTerminal pos is its vec3 position in the world
-- * fDistToRW is the distance to the take-off position for the active runway from the parking.
--
--
-- @param #AIRBASE self
-- @param #boolean available If true, only available parking spots will be returned.
-- @return #table Table with parking data. See https://wiki.hoggitworld.com/view/DCS_func_getParking
@@ -458,13 +458,13 @@ function AIRBASE:GetParkingData(available)
-- Get DCS airbase object.
local DCSAirbase=self:GetDCSObject()
-- Get parking data.
local parkingdata=nil
if DCSAirbase then
parkingdata=DCSAirbase:getParking(available)
end
self:T2({parkingdata=parkingdata})
return parkingdata
end
@@ -477,27 +477,27 @@ function AIRBASE:GetParkingSpotsNumber(termtype)
-- Get free parking spots data.
local parkingdata=self:GetParkingData(false)
local nspots=0
for _,parkingspot in pairs(parkingdata) do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then
nspots=nspots+1
end
end
return nspots
end
--- Get number of free parking spots at an airbase.
-- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype Terminal type.
-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships.
-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships.
-- @return #number Number of free parking spots at this airbase.
function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
-- Get free parking spots data.
local parkingdata=self:GetParkingData(true)
local nfree=0
for _,parkingspot in pairs(parkingdata) do
-- Spots on runway are not counted unless explicitly requested.
@@ -507,7 +507,7 @@ function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
end
end
end
return nfree
end
@@ -520,7 +520,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
-- Get free parking spots data.
local parkingdata=self:GetParkingData(true)
-- Put coordinates of free spots into table.
local spots={}
for _,parkingspot in pairs(parkingdata) do
@@ -531,7 +531,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
end
end
end
return spots
end
@@ -543,23 +543,23 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype)
-- Get all parking spots data.
local parkingdata=self:GetParkingData(false)
-- Put coordinates of free spots into table.
local spots={}
for _,parkingspot in ipairs(parkingdata) do
-- Coordinates on runway are not returned unless explicitly requested.
if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then
-- Get coordinate from Vec3 terminal position.
local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos)
-- Add to table.
table.insert(spots, _coord)
end
end
return spots
end
@@ -570,11 +570,11 @@ end
-- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
function AIRBASE:GetParkingSpotsTable(termtype)
-- Get parking data of all spots (free or occupied)
-- Get parking data of all spots (free or occupied)
local parkingdata=self:GetParkingData(false)
-- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true)
-- Function to ckeck if any parking spot is free.
local function _isfree(_tocheck)
for _,_spot in pairs(parkingfree) do
@@ -584,7 +584,7 @@ function AIRBASE:GetParkingSpotsTable(termtype)
end
return false
end
-- Put coordinates of parking spots into table.
local spots={}
for _,_spot in pairs(parkingdata) do
@@ -595,22 +595,22 @@ function AIRBASE:GetParkingSpotsTable(termtype)
table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW})
end
end
self:T2({ spots = spots } )
return spots
end
--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase.
-- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype Terminal type.
-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships.
-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships.
-- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
-- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true)
-- Put coordinates of free spots into table.
local freespots={}
for _,_spot in pairs(parkingfree) do
@@ -621,7 +621,7 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
end
end
end
return freespots
end
@@ -634,10 +634,10 @@ function AIRBASE:GetParkingSpotData(TerminalID)
-- Get parking data.
local parkingdata=self:GetParkingSpotsTable()
-- Debug output.
self:T2({parkingdata=parkingdata})
for _,_spot in pairs(parkingdata) do
local spot=_spot --#AIRBASE.ParkingSpot
self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType})
@@ -645,7 +645,7 @@ function AIRBASE:GetParkingSpotData(TerminalID)
return spot
end
end
self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID))
return nil
end
@@ -667,18 +667,18 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
-- Get airbase name.
local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype)))
for _,_spot in pairs(parkingdata) do
-- Mark text.
local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m",
_spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
-- Create mark on the F10 map.
if mark then
_spot.Coordinate:MarkToAll(_text)
end
-- Info to DCS.log file.
local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m",
airbasename, _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
@@ -696,7 +696,7 @@ end
-- @param #boolean scanstatics (Optional) Scan for statics as obstacles. Default true.
-- @param #boolean scanscenery (Optional) Scan for scenery as obstacles. Default false. Can cause problems with e.g. shelters.
-- @param #boolean verysafe (Optional) If true, wait until an aircraft has taken off until the parking spot is considered to be free. Defaul false.
-- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group.
-- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group.
-- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function.
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata)
@@ -714,8 +714,8 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
end
if verysafe==nil then
verysafe=false
end
end
-- Function calculating the overlap of two (square) objects.
local function _overlap(object1, object2, dist)
local pos1=object1 --Wrapper.Positionable#POSITIONABLE
@@ -723,107 +723,107 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
local r1=pos1:GetBoundingRadius()
local r2=pos2:GetBoundingRadius()
if r1 and r2 then
local safedist=(r1+r2)*1.1
local safedist=(r1+r2)*1.1
local safe = (dist > safedist)
self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe)))
return safe
else
return true
end
end
end
-- Get airport name.
local airport=self:GetName()
-- Get parking spot data table. This contains free and "non-free" spots.
-- Note that there are three major issues with the DCS getParking() function:
-- 1. A spot is considered as NOT free until an aircraft that is present has finally taken off. This might be a bit long especiall at smaller airports.
-- 2. A "free" spot does not take the aircraft size into accound. So if two big aircraft are spawned on spots next to each other, they might overlap and get destroyed.
-- 3. The routine return a free spot, if there a static objects placed on the spot.
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
-- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft=group:GetUnit(1)
local _aircraftsize, ax,ay,az=aircraft:GetObjectSize()
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
local _nspots=nspots or group:GetSize()
-- Debug info.
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
-- Table of valid spots.
local validspots={}
local nvalid=0
-- Test other stuff if no parking spot is available.
local _test=false
if _test then
return validspots
end
-- Mark all found obstacles on F10 map for debugging.
local markobstacles=false
-- Loop over all known parking spots
for _,parkingspot in pairs(parkingdata) do
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
self:T2({_termid=_termid})
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
-- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off.
if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then
-- DCS getParking() routine returned that spot is not free.
self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC)))
else
-- Scan a radius of 50 meters around the spot.
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
-- Loop over objects within scan radius.
local occupied=false
-- Check all units.
-- Check all units.
for _,unit in pairs(_units) do
local _coord=unit:GetCoordinate()
local _dist=_coord:Get2DDistance(_spot)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft, unit, _dist)
if markobstacles then
local l,x,y,z=unit:GetObjectSize()
_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:GetName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end
if scanunits and not _safe then
occupied=true
end
end
end
-- Check all statics.
for _,static in pairs(_statics) do
local _static=STATIC:Find(static)
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_static,_dist)
if markobstacles then
local l,x,y,z=_static:GetObjectSize()
_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end
if scanstatics and not _safe then
occupied=true
end
end
end
-- Check all scenery.
for _,scenery in pairs(_sceneries) do
local _scenery=SCENERY:Register(scenery:getTypeName(), scenery)
@@ -831,17 +831,17 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_scenery,_dist)
if markobstacles then
local l,x,y,z=scenery:GetObjectSize(scenery)
_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe)))
end
if scanscenery and not _safe then
occupied=true
end
end
end
-- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot.
for _,_takenspot in pairs(validspots) do
local _dist=_takenspot.Coordinate:Get2DDistance(_spot)
@@ -850,7 +850,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
occupied=true
end
end
--_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied)))
if occupied then
self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid))
@@ -862,16 +862,16 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
nvalid=nvalid+1
self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots))
end
end -- loop over units
-- We found enough spots.
if nvalid>=_nspots then
return validspots
end
end -- check terminal type
end
end
-- Retrun spots we found, even if there were not enough.
return validspots
end
@@ -880,54 +880,54 @@ end
-- @param #AIRBASE self
-- @param Wrapper.Group#GROUP group Group to be checked.
-- @param #number radius Radius around the spawn point to be checked. Default is 50 m.
-- @param #boolean despawn If true, the group is destroyed.
-- @param #boolean despawn If true, the group is destroyed.
-- @return #boolean True if group is within radius around spawn points on runway.
function AIRBASE:CheckOnRunWay(group, radius, despawn)
-- Default radius.
radius=radius or 50
-- We only check at real airbases (not FARPS or ships).
if self:GetDesc().category~=Airbase.Category.AIRDROME then
return false
end
if group and group:IsAlive() then
-- Debug.
self:T(string.format("%s, checking if group %s is on runway?",self:GetName(), group:GetName()))
-- Get coordinates on runway.
local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
-- Mark runway spawn points.
--[[
for _i,_coord in pairs(runwaypoints) do
_coord:MarkToAll(string.format("runway %d",_i))
end
]]
-- Get units of group.
local units=group:GetUnits()
-- Loop over units.
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
-- Check if unit is alive and not in air.
if unit and unit:IsAlive() and not unit:InAir() then
self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(), unit:GetName()))
-- Loop over runway spawn points.
for _i,_coord in pairs(runwaypoints) do
-- Distance between unit and spawn pos.
local dist=unit:GetCoordinate():Get2DDistance(_coord)
-- Mark unit spawn points for debugging.
--unit:GetCoordinate():MarkToAll(string.format("unit %s distance to rwy %d = %d",unit:GetName(),_i, dist))
-- Check if unit is withing radius.
if dist<radius then
self:E(string.format("%s, unit %s of group %s was spawned on runway #%d. Distance %.1f < radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn)))
@@ -940,16 +940,16 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn)
self:T(string.format("%s, unit %s of group %s was NOT spawned on runway #%d. Distance %.1f > radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn)))
--unit:FlareGreen()
end
end
else
self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName()))
end
self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName()))
end
end
else
self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName()))
end
return false
end
@@ -980,15 +980,15 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
return true
end
end
-- Init no match.
local match=false
-- Standar case.
-- Standar case.
if Term_Type==termtype then
match=true
end
-- Artificial cases. Combination of terminal types.
if termtype==AIRBASE.TerminalType.OpenMedOrBig then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then
@@ -1003,114 +1003,119 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
match=true
end
end
return match
end
--- Get runways data. Only for airdromes!
-- @param #AIRBASE self
-- @param #number magvar (Optional) Magnetic variation in degrees.
-- @param #boolean mark (Optional) Place markers with runway data on F10 map.
-- @return #table Runway data.
function AIRBASE:GetRunwayData(magvar)
function AIRBASE:GetRunwayData(magvar, mark)
-- Runway table.
local runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
return {}
end
-- Get spawn points on runway.
local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
-- Magnetic declination.
magvar=magvar or UTILS.GetMagneticDeclination()
local N=#runwaycoords
local dN=2
local ex=false
local name=self:GetName()
if name==AIRBASE.Nevada.Jean_Airport or
name==AIRBASE.Nevada.Creech_AFB or
if name==AIRBASE.Nevada.Jean_Airport or
name==AIRBASE.Nevada.Creech_AFB or
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport then
N=#runwaycoords/2
dN=1
ex=true
end
for i=1,N,dN do
local j=i+1
if ex then
--j=N+i
j=#runwaycoords-i+1
end
-- Coordinates of the two runway points.
local c1=runwaycoords[i] --Core.Point#COORDINATES
local c2=runwaycoords[j] --Core.Point#COORDINATES
-- Heading of runway.
local hdg=c1:HeadingTo(c2)
-- Runway ID: heading=070° ==> idx="07"
-- Runway ID: heading=070 ==> idx="07"
local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0))
-- Runway table.
local runway={} --#AIRBASE.Runway
runway.heading=hdg
runway.idx=idx
runway.length=c1:Get2DDistance(c2)
runway.length=c1:Get2DDistance(c2)
runway.position=c1
runway.endpoint=c2
-- Debug info.
self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length))
-- Debug mark
runway.position:MarkToAll(string.format("Runway %s Heading=%03d", runway.idx, runway.heading))
if mark then
runway.position:MarkToAll(string.format("Runway %s: true heading=%03d, length=%d m", runway.idx, runway.heading, runway.length))
end
-- Add runway.
table.insert(runways, runway)
end
-- Get inverse runways
local inverse={}
for _,_runway in pairs(runways) do
local r=_runway --#AIRBASE.Runway
local runway={} --#AIRBASE.Runway
local runway={} --#AIRBASE.Runway
runway.heading=r.heading-180
if runway.heading<0 then
runway.heading=runway.heading+360
end
end
runway.idx=string.format("%02d", math.max(0, UTILS.Round((runway.heading-magvar)/10, 0)))
runway.length=r.length
runway.position=r.endpoint
runway.endpoint=r.position
-- Debug info.
self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length))
-- Debug mark
runway.position:MarkToAll(string.format("Runway %s Heading=%03d", runway.idx, runway.heading))
if mark then
runway.position:MarkToAll(string.format("Runway %s: true heading=%03d, length=%d m", runway.idx, runway.heading, runway.length))
end
-- Add runway.
table.insert(inverse, runway)
table.insert(inverse, runway)
end
-- Add inverse runway.
for _,runway in pairs(inverse) do
-- Add runway.
table.insert(runways, runway)
table.insert(runways, runway)
end
return runways
end
@@ -1138,48 +1143,45 @@ function AIRBASE:GetActiveRunway(magvar)
-- Get wind vector.
local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3()
local norm=UTILS.VecNorm(Vwind)
-- Active runway number.
local iact=1
-- Check if wind is blowing (norm>0).
if norm>0 then
-- Normalize wind (not necessary).
Vwind.x=Vwind.x/norm
Vwind.y=0
Vwind.z=Vwind.z/norm
-- Loop over runways.
local dotmin=nil
for i,_runway in pairs(runways) do
local runway=_runway --#AIRBASE.Runway
-- Angle in rad.
local alpha=math.rad(runway.heading)
-- Runway vector.
local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)}
-- 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))
--env.info(string.format("runway=%03d dot=%.3f", runway.heading, dot))
-- New min?
if dotmin==nil or dot<dotmin then
dotmin=dot
iact=i
end
end
else
self:E("WARNING: Norm of wind is zero! Cannot determine active runway based on wind direction.")
end
return runways[iact]
end