--- **Wrapper** -- AIRBASE is a wrapper class to handle the DCS Airbase objects. -- -- === -- -- ### Author: **FlightControl** -- -- ### Contributions: **funkyfranky** -- -- === -- -- @module Wrapper.Airbase -- @image Wrapper_Airbase.JPG --- @type AIRBASE -- @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. -- 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 = { [Airbase.Category.AIRDROME] = "Airdrome", [Airbase.Category.HELIPAD] = "Helipad", [Airbase.Category.SHIP] = "Ship", }, } --- 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 -- * AIRBASE.Caucasus.Gudauta -- * AIRBASE.Caucasus.Batumi -- * AIRBASE.Caucasus.Senaki_Kolkhi -- * AIRBASE.Caucasus.Kobuleti -- * AIRBASE.Caucasus.Kutaisi -- * AIRBASE.Caucasus.Tbilisi_Lochini -- * AIRBASE.Caucasus.Soganlug -- * AIRBASE.Caucasus.Vaziani -- * AIRBASE.Caucasus.Anapa_Vityazevo -- * AIRBASE.Caucasus.Krasnodar_Center -- * AIRBASE.Caucasus.Novorossiysk -- * AIRBASE.Caucasus.Krymsk -- * AIRBASE.Caucasus.Maykop_Khanskaya -- * AIRBASE.Caucasus.Sochi_Adler -- * AIRBASE.Caucasus.Mineralnye_Vody -- * AIRBASE.Caucasus.Nalchik -- * AIRBASE.Caucasus.Mozdok -- * AIRBASE.Caucasus.Beslan -- -- @field Caucasus AIRBASE.Caucasus = { ["Gelendzhik"] = "Gelendzhik", ["Krasnodar_Pashkovsky"] = "Krasnodar-Pashkovsky", ["Sukhumi_Babushara"] = "Sukhumi-Babushara", ["Gudauta"] = "Gudauta", ["Batumi"] = "Batumi", ["Senaki_Kolkhi"] = "Senaki-Kolkhi", ["Kobuleti"] = "Kobuleti", ["Kutaisi"] = "Kutaisi", ["Tbilisi_Lochini"] = "Tbilisi-Lochini", ["Soganlug"] = "Soganlug", ["Vaziani"] = "Vaziani", ["Anapa_Vityazevo"] = "Anapa-Vityazevo", ["Krasnodar_Center"] = "Krasnodar-Center", ["Novorossiysk"] = "Novorossiysk", ["Krymsk"] = "Krymsk", ["Maykop_Khanskaya"] = "Maykop-Khanskaya", ["Sochi_Adler"] = "Sochi-Adler", ["Mineralnye_Vody"] = "Mineralnye Vody", ["Nalchik"] = "Nalchik", ["Mozdok"] = "Mozdok", ["Beslan"] = "Beslan", } --- These are all airbases of Nevada: -- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB -- * AIRBASE.Nevada.McCarran_International_Airport -- * AIRBASE.Nevada.Nellis_AFB -- * AIRBASE.Nevada.Beatty_Airport -- * AIRBASE.Nevada.Boulder_City_Airport -- * AIRBASE.Nevada.Echo_Bay -- * AIRBASE.Nevada.Henderson_Executive_Airport -- * AIRBASE.Nevada.Jean_Airport -- * AIRBASE.Nevada.Laughlin_Airport -- * AIRBASE.Nevada.Lincoln_County -- * AIRBASE.Nevada.Mellan_Airstrip -- * AIRBASE.Nevada.Mesquite -- * AIRBASE.Nevada.Mina_Airport_3Q0 -- * AIRBASE.Nevada.North_Las_Vegas -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield -- @field Nevada AIRBASE.Nevada = { ["Creech_AFB"] = "Creech AFB", ["Groom_Lake_AFB"] = "Groom Lake AFB", ["McCarran_International_Airport"] = "McCarran International Airport", ["Nellis_AFB"] = "Nellis AFB", ["Beatty_Airport"] = "Beatty Airport", ["Boulder_City_Airport"] = "Boulder City Airport", ["Echo_Bay"] = "Echo Bay", ["Henderson_Executive_Airport"] = "Henderson Executive Airport", ["Jean_Airport"] = "Jean Airport", ["Laughlin_Airport"] = "Laughlin Airport", ["Lincoln_County"] = "Lincoln County", ["Mellan_Airstrip"] = "Mellan Airstrip", ["Mesquite"] = "Mesquite", ["Mina_Airport_3Q0"] = "Mina Airport 3Q0", ["North_Las_Vegas"] = "North Las Vegas", ["Pahute_Mesa_Airstrip"] = "Pahute Mesa Airstrip", ["Tonopah_Airport"] = "Tonopah Airport", ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", } --- These are all airbases of Normandy: -- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles -- * AIRBASE.Normandy.Cretteville -- * AIRBASE.Normandy.Maupertus -- * AIRBASE.Normandy.Brucheville -- * AIRBASE.Normandy.Meautis -- * AIRBASE.Normandy.Cricqueville_en_Bessin -- * AIRBASE.Normandy.Lessay -- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer -- * AIRBASE.Normandy.Biniville -- * AIRBASE.Normandy.Cardonville -- * AIRBASE.Normandy.Deux_Jumeaux -- * AIRBASE.Normandy.Chippelle -- * AIRBASE.Normandy.Beuzeville -- * AIRBASE.Normandy.Azeville -- * AIRBASE.Normandy.Picauville -- * AIRBASE.Normandy.Le_Molay -- * AIRBASE.Normandy.Longues_sur_Mer -- * AIRBASE.Normandy.Carpiquet -- * AIRBASE.Normandy.Bazenville -- * AIRBASE.Normandy.Sainte_Croix_sur_Mer -- * AIRBASE.Normandy.Beny_sur_Mer -- * AIRBASE.Normandy.Rucqueville -- * AIRBASE.Normandy.Sommervieu -- * AIRBASE.Normandy.Lantheuil -- * AIRBASE.Normandy.Evreux -- * AIRBASE.Normandy.Chailey -- * AIRBASE.Normandy.Needs_Oar_Point -- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Ford -- @field Normandy AIRBASE.Normandy = { ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", ["Lignerolles"] = "Lignerolles", ["Cretteville"] = "Cretteville", ["Maupertus"] = "Maupertus", ["Brucheville"] = "Brucheville", ["Meautis"] = "Meautis", ["Cricqueville_en_Bessin"] = "Cricqueville-en-Bessin", ["Lessay"] = "Lessay", ["Sainte_Laurent_sur_Mer"] = "Sainte-Laurent-sur-Mer", ["Biniville"] = "Biniville", ["Cardonville"] = "Cardonville", ["Deux_Jumeaux"] = "Deux Jumeaux", ["Chippelle"] = "Chippelle", ["Beuzeville"] = "Beuzeville", ["Azeville"] = "Azeville", ["Picauville"] = "Picauville", ["Le_Molay"] = "Le Molay", ["Longues_sur_Mer"] = "Longues-sur-Mer", ["Carpiquet"] = "Carpiquet", ["Bazenville"] = "Bazenville", ["Sainte_Croix_sur_Mer"] = "Sainte-Croix-sur-Mer", ["Beny_sur_Mer"] = "Beny-sur-Mer", ["Rucqueville"] = "Rucqueville", ["Sommervieu"] = "Sommervieu", ["Lantheuil"] = "Lantheuil", ["Evreux"] = "Evreux", ["Chailey"] = "Chailey", ["Needs_Oar_Point"] = "Needs Oar Point", ["Funtington"] = "Funtington", ["Tangmere"] = "Tangmere", ["Ford"] = "Ford", } --- These are all airbases of the Persion Gulf Map: -- -- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport -- * AIRBASE.PersianGulf.Al-Bateen_Airport -- * AIRBASE.PersianGulf.Al_Ain_International_Airport -- * AIRBASE.PersianGulf.Al_Dhafra_AB -- * AIRBASE.PersianGulf.Al_Maktoum_Intl -- * AIRBASE.PersianGulf.Al_Minhad_AB -- * AIRBASE.PersianGulf.Bandar_e_Jask_airfield -- * AIRBASE.PersianGulf.Bandar_Abbas_Intl -- * AIRBASE.PersianGulf.Bandar_Lengeh -- * AIRBASE.PersianGulf.Dubai_Intl -- * AIRBASE.PersianGulf.Fujairah_Intl -- * AIRBASE.PersianGulf.Havadarya -- * AIRBASE.PersianGulf.Jiroft_Airport -- * AIRBASE.PersianGulf.Kerman_Airport -- * AIRBASE.PersianGulf.Khasab -- * AIRBASE.PersianGulf.Kish_International_Airport -- * AIRBASE.PersianGulf.Lar_Airbase -- * AIRBASE.PersianGulf.Lavan_Island_Airport -- * AIRBASE.PersianGulf.Liwa_Airbase -- * AIRBASE.PersianGulf.Qeshm_Island -- * AIRBASE.PersianGulf.Ras_Al_Khaimah_International_Airport -- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport -- * AIRBASE.PersianGulf.Sharjah_Intl -- * AIRBASE.PersianGulf.Shiraz_International_Airport -- * AIRBASE.PersianGulf.Sir_Abu_Nuayr -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak -- @field PersianGulf AIRBASE.PersianGulf = { ["Abu_Dhabi_International_Airport"] = "Abu Dhabi International Airport", ["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport", ["Al_Ain_International_Airport"] = "Al Ain International Airport", ["Al_Bateen_Airport"] = "Al-Bateen Airport", ["Al_Dhafra_AB"] = "Al Dhafra AB", ["Al_Maktoum_Intl"] = "Al Maktoum Intl", ["Al_Minhad_AB"] = "Al Minhad AB", ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Lengeh"] = "Bandar Lengeh", ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield", ["Dubai_Intl"] = "Dubai Intl", ["Fujairah_Intl"] = "Fujairah Intl", ["Havadarya"] = "Havadarya", ["Jiroft_Airport"] = "Jiroft Airport", ["Kerman_Airport"] = "Kerman Airport", ["Khasab"] = "Khasab", ["Kish_International_Airport"] = "Kish International Airport", ["Lar_Airbase"] = "Lar Airbase", ["Lavan_Island_Airport"] = "Lavan Island Airport", ["Liwa_Airbase"] = "Liwa Airbase", ["Qeshm_Island"] = "Qeshm Island", ["Ras_Al_Khaimah_International_Airport"] = "Ras Al Khaimah International Airport", ["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel Airport", ["Sharjah_Intl"] = "Sharjah Intl", ["Shiraz_International_Airport"] = "Shiraz International Airport", ["Sir_Abu_Nuayr"] = "Sir Abu Nuayr", ["Sirri_Island"] = "Sirri Island", ["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. -- @field #number TerminalID Terminal ID of the spot. Generally, this is not the same number as displayed in the mission editor. -- @field #AIRBASE.TerminalType TerminalType Type of the spot, i.e. for which type of aircraft it can be used. -- @field #boolean TOAC Takeoff or landing aircarft. I.e. this stop is occupied currently by an aircraft until it took of or until it landed. -- @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. -- * AIRBASE.TerminalType.OpenMed = 72: Open/Shelter air airplane only. -- * AIRBASE.TerminalType.OpenBig = 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. -- * 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. -- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map. -- @field #number OpenMed 72: Open/Shelter air airplane only. -- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. -- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. -- @field #number HelicopterUnsable 216: Combines HelicopterOnly, OpenMed and OpenBig. -- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. AIRBASE.TerminalType = { Runway=16, HelicopterOnly=40, Shelter=68, OpenMed=72, OpenBig=104, OpenMedOrBig=176, HelicopterUsable=216, FighterAircraft=244, } -- Registration. --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. -- @return Wrapper.Airbase#AIRBASE function AIRBASE:Register( AirbaseName ) local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) self.AirbaseName = AirbaseName self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, self:GetVec2(), 2500 ) return self end -- Reference methods. --- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. -- @param #AIRBASE self -- @param DCS#Airbase DCSAirbase An existing DCS Airbase object reference. -- @return Wrapper.Airbase#AIRBASE self function AIRBASE:Find( DCSAirbase ) local AirbaseName = DCSAirbase:getName() local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end --- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. -- @param #AIRBASE self -- @param #string AirbaseName The Airbase Name. -- @return #AIRBASE self function AIRBASE:FindByName( AirbaseName ) local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end --- Get the DCS object of an airbase -- @param #AIRBASE self -- @return DCS#Airbase DCS airbase object. function AIRBASE:GetDCSObject() local DCSAirbase = Airbase.getByName( self.AirbaseName ) if DCSAirbase then return DCSAirbase end return nil end --- Get the airbase zone. -- @param #AIRBASE self -- @return Core.Zone#ZONE_RADIUS The zone radius of the airbase. function AIRBASE:GetZone() return self.AirbaseZone end --- Get all airbases of the current map. This includes ships and FARPS. -- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. -- @return #table Table containing all airbase objects of the current map. function AIRBASE.GetAllAirbases(coalition) local airbases={} for _,airbase in pairs(_DATABASE.AIRBASES) do if (coalition~=nil and airbase:GetCoalition()==coalition) or coalition==nil then table.insert(airbases, airbase) 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 -- * 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 function AIRBASE:GetParkingData(available) self:F2(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 --- Get number of parking spots at an airbase. Optionally, a specific terminal type can be requested. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type of which the number of spots is counted. Default all spots but spawn points on runway. -- @return #number Number of parking spots at this airbase. 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. -- @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. if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then if (allowTOAC and allowTOAC==true) or parkingspot.TO_AC==false then nfree=nfree+1 end end end return nfree end --- Get the coordinates 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. -- @return #table Table of coordinates of the free parking spots. 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 -- Coordinates on runway are not returned unless explicitly requested. if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then if (allowTOAC and allowTOAC==true) or parkingspot.TO_AC==false then table.insert(spots, COORDINATE:NewFromVec3(parkingspot.vTerminalPos)) end end end return spots end --- Get the coordinates of all parking spots at an airbase. Optionally only those of a specific terminal type. Spots on runways are excluded if not explicitly requested by terminal type. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype (Optional) Terminal type. Default all. -- @return #table Table of coordinates of parking spots. 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 pairs(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 --- 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. -- @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) 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 if _spot.Term_Index==_tocheck.Term_Index then return true end end return false end -- Put coordinates of parking spots into table. local spots={} for _,_spot in pairs(parkingdata) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then self:I({_spot=_spot}) local _free=_isfree(_spot) local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) 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:I({ 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. -- @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 if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) end end end return freespots end --- Place markers of parking spots on the F10 map. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed. -- @param #boolean mark If false, do not place markers but only give output to DCS.log file. Default true. function AIRBASE:MarkParkingSpots(termtype, mark) -- Default is true. if mark==nil then mark=true end -- Get parking data from getParking() wrapper function. local parkingdata=self:GetParkingSpotsTable(termtype) -- 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) self:E(_text) end end --- Seach unoccupied parking spots at the airbase for a specific group of aircraft. The routine also optionally checks for other unit, static and scenery options in a certain radius around the parking spot. -- The dimension of the spawned aircraft and of the potential obstacle are taken into account. Note that the routine can only return so many spots that are free. -- @param #AIRBASE self -- @param Wrapper.Group#GROUP group Aircraft group for which the parking spots are requested. -- @param #AIRBASE.TerminalType terminaltype (Optional) Only search spots at a specific terminal type. Default is all types execpt on runway. -- @param #number scanradius (Optional) Radius in meters around parking spot to scan for obstacles. Default 50 m. -- @param #boolean scanunits (Optional) Scan for units as obstacles. Default true. -- @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 #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) -- Init default scanradius=scanradius or 50 if scanunits==nil then scanunits=true end if scanstatics==nil then scanstatics=true end if scanscenery==nil then scanscenery=false end if verysafe==nil then verysafe=false end -- Function calculating the overlap of two (square) objects. local function _overlap(object1, object2, dist) local pos1=object1 --Wrapper.Positionable#POSITIONABLE local pos2=object2 --Wrapper.Positionable#POSITIONABLE local r1=pos1:GetBoundingRadius() local r2=pos2:GetBoundingRadius() if r1 and r2 then 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 -- 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:I({_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. for _,unit in pairs(_units) do local _coord=unit:GetCoordinate() 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 -- 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 _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 -- Check all scenery. for _,scenery in pairs(_sceneries) do local _scenery=SCENERY:Register(scenery:getTypeName(), scenery) local _vec3=scenery:getPoint() 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 -- 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) local _safe=_overlap(aircraft, aircraft, _dist) if not _safe then 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)) else self:I(string.format("%s: Parking spot id %d free.", airport, _termid)) if nvalid<_nspots then table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) end 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 -- Retrun spots we found, even if there were not enough. return validspots end --- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway. -- @param #AIRBASE self -- @param Wrapper.Group#GROUP group Group to be checked. -- @param #number radius Radius around the spawn point to be checked. Default is 50 m. -- @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 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 --- Get category of airbase. -- @param #WAREHOUSE self -- @return #number Category of airbase from GetDesc().category. function AIRBASE:GetAirbaseCategory() return self:GetDesc().category end --- Helper function to check for the correct terminal type including "artificial" ones. -- @param #number Term_Type Termial type from getParking routine. -- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator. -- @return #boolean True if terminal types match. function AIRBASE._CheckTerminalType(Term_Type, termtype) -- Nill check for Term_Type. if Term_Type==nil then return false end -- If no terminal type is requested, we return true. BUT runways are excluded unless explicitly requested. if termtype==nil then if Term_Type==AIRBASE.TerminalType.Runway then return false else return true end end -- Init no match. local match=false -- 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 match=true end elseif termtype==AIRBASE.TerminalType.HelicopterUsable then if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then match=true end elseif termtype==AIRBASE.TerminalType.FighterAircraft then if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then match=true end end return match end