diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 564cfa8dc..80887d4d2 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -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.thicknessUTILS.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=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 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 6da64bf10..19fa452bd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -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 %.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