From f5eb77cbf5de67dc00345eb9eeb7a4524b661cda Mon Sep 17 00:00:00 2001 From: funkyfranky <> Date: Mon, 25 Jun 2018 22:38:47 +0200 Subject: [PATCH] RAT 2.3.0 RAT: - Added getparking function wrappers to determin free parking spots. - Terminal type can be specified. - respawndelay is used as delay for despawn as well. - commute has new option for star shape routes. DATABASE: - Added neutral coalition support. COORDINATE: - added new search world function - added new get closest parking spot function SPAWN: - updated SpawnAtAirbase function to use getparking wrapper function. DCS: - updated country.id list AIRBASE: - Added Persion Gulf map airports - Added wrapper function for new DCS API getparking() function. --- Moose Development/Moose/Core/Database.lua | 17 +- Moose Development/Moose/Core/Point.lua | 135 +++++- Moose Development/Moose/Core/Spawn.lua | 210 +++++++-- Moose Development/Moose/DCS.lua | 55 +++ Moose Development/Moose/Functional/RAT.lua | 488 +++++++++++++++----- Moose Development/Moose/Wrapper/Airbase.lua | 283 +++++++++++- 6 files changed, 1013 insertions(+), 175 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 82e195f82..3b0fb0337 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -66,6 +66,7 @@ local _DATABASECoalition = { [1] = "Red", [2] = "Blue", + [3] = "Neutral", } local _DATABASECategory = @@ -116,7 +117,7 @@ function DATABASE:New() --- @param #DATABASE self local function CheckPlayers( self ) - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )} for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --self:E( { "CoalitionData:", CoalitionData } ) for UnitId, UnitData in pairs( CoalitionData ) do @@ -741,7 +742,7 @@ end -- @return #DATABASE self function DATABASE:_RegisterPlayers() - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for UnitId, UnitData in pairs( CoalitionData ) do self:T3( { "UnitData:", UnitData } ) @@ -765,7 +766,7 @@ end -- @return #DATABASE self function DATABASE:_RegisterGroupsAndUnits() - local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ) } + local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSGroupId, DCSGroup in pairs( CoalitionData ) do @@ -1176,11 +1177,19 @@ function DATABASE:_RegisterTemplates() self.UNITS = {} --Build routines.db.units and self.Navpoints for CoalitionName, coa_data in pairs(env.mission.coalition) do + self:T({CoalitionName=CoalitionName}) - if (CoalitionName == 'red' or CoalitionName == 'blue') and type(coa_data) == 'table' then + if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then --self.Units[coa_name] = {} local CoalitionSide = coalition.side[string.upper(CoalitionName)] + if CoalitionName=="red" then + CoalitionSide=coalition.side.NEUTRAL + elseif CoalitionName=="blue" then + CoalitionSide=coalition.side.BLUE + else + CoalitionSide=coalition.side.NEUTRAL + end -- build nav points DB self.Navpoints[CoalitionName] = {} diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b98159460..4cae6049b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -292,8 +292,76 @@ do -- COORDINATE return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z end - + --- Returns if the 2 coordinates are at the same 2D position. + -- @param #COORDINATE self + -- @param #number radius Scan radius in meters. + -- @return True if units were found. + -- @return True if statics were found. + -- @return True if scenery objects were found. + -- @return Unit objects found. + -- @return Static objects found. + -- @return Scenery objects found. + function COORDINATE:ScanObjects(radius) + env.info(string.format("FF Scanning in radius %.1f m.", radius)) + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = self:GetVec3(), + radius = radius, + } + } + + -- Found stuff. + local Units = {} + local Statics = {} + local Scenery = {} + local gotstatics=false + local gotunits=false + local gotscenery=false + + local function EvaluateZone( ZoneObject ) + + if ZoneObject then + + -- Get category of scanned object. + local ObjectCategory = ZoneObject:getCategory() + + -- Check for unit or static objects + if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) then + + table.insert(Units, ZoneObject) + gotunits=true + + elseif (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then + + table.insert(Statics, ZoneObject) + gotstatics=true + + elseif ObjectCategory == Object.Category.SCENERY then + + table.insert(Scenery, ZoneObject) + gotscenery=true + + end + + end + + return true + end + + -- Search the world. + world.searchObjects({Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY}, SphereSearch, EvaluateZone) + + for _,unit in pairs(Units) do + env.info(string.format("FF found unit %s", unit:getName())) + end + for _,static in pairs(Statics) do + env.info(string.format("FF found unit %s", static:getName())) + end + + return gotunits, gotstatics, gotscenery, Units, Statics, Scenery + end --- Calculate the distance from a reference @{#COORDINATE}. -- @param #COORDINATE self -- @param #COORDINATE PointVec2Reference The reference @{#COORDINATE}. @@ -946,6 +1014,71 @@ do -- COORDINATE return RoutePoint end + --- Gets the nearest parking spot. + -- @param #COORDINATE self + -- @param #boolean free (Optional) Only look for free parking spots. By default the closest parking spot is returned regardless of whether it is free or not. + -- @param Wrapper.Airbase#AIRBASE airbase (Optional) Search only parking spots at that airbase. + -- @param Wrapper.Airbase#Terminaltype terminaltype Type of the terminal. + -- @return Core.Point#COORDINATE Coordinate of the nearest parking spot. + -- @return #number Distance to closest parking spot. + function COORDINATE:GetClosestParkingSpot(free, airbase, terminaltype) + + local airbases={} + if airbase then + table.insert(airbases,airbase) + else + airbases=AIRBASE:GetAllAirbases() + end + + local _closest=nil --Core.Point#COORDINATE + local _distmin=nil + for _,_airbase in pairs(airbases) do + + local mybase=_airbase --Wrapper.Airbase#AIRBASE + local parkingdata=mybase:GetParkingSpotsTable(terminaltype) + + for _,_spot in pairs(parkingdata) do + + -- Get coordinate if it matches the requirements. + local _coord=nil --Core.Point#COORDINATE + if (free and _spot.Free) or free==nil then + if (terminaltype and _spot.TerminalType==terminaltype) or terminaltype==nil then + _coord=_spot.Coordinate + end + end + + -- Compare distance to closest one found so far. + if _coord then + local _dist=self:Get2DDistance(_coord) + if _distmin==nil then + _closest=_coord + _distmin=_dist + else + local _dist=self:Get2DDistance(_coord) + if _dist<_distmin then + _distmin=_dist + _closest=_coord + end + end + + end + + end + end + + return _closest, _distmin + end + + --- Gets the nearest free parking spot. + -- @param #COORDINATE self + -- @param Wrapper.Airbase#AIRBASE airbase (Optional) Search only parking spots at that airbase. + -- @param Wrapper.Airbase#Terminaltype terminaltype Type of the terminal. + -- @return #COORDINATE Coordinate of the nearest free parking spot. + -- @return #number Distance to closest free parking spot. + function COORDINATE:GetClosestFreeParkingSpot(airbase, terminaltype) + return self:GetClosestParkingSpot(true, airbase, terminaltype) + end + --- Gets the nearest coordinate to a road. -- @param #COORDINATE self -- @return #COORDINATE Coordinate of the nearest road. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index b07ea4421..5db251ae5 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1218,6 +1218,7 @@ end -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot. -- @param #number TakeoffAltitude (optional) The altitude above the ground. +-- @param #number TerminalType (optional) The terminal type the aircraft should be spawned at. -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage @@ -1237,33 +1238,40 @@ end -- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) -- -function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2 +function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType ) -- R2.2, R2.4 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude } ) - local PointVec3 = SpawnAirbase:GetPointVec3() + -- Get position of airbase. + local PointVec3 = SpawnAirbase:GetCoordinate() self:T2(PointVec3) + -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then + -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate if SpawnTemplate then + -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] - -- These are only for ships. + -- These are only for ships and FARPS. SpawnPoint.linkUnit = nil SpawnPoint.helipadId = nil SpawnPoint.airdromeId = nil + -- Get airbase ID and category. local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetDesc().category self:F( { AirbaseCategory = AirbaseCategory } ) + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID @@ -1274,57 +1282,177 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2 SpawnPoint.airdromeId = AirbaseID end - SpawnPoint.alt = 0 - - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + -- Check if we spawn on ground. + local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) + self:E({spawnonground=spawnonground, takeoff=Takeoff, toair=Takeoff==SPAWN.Takeoff.Air}) + + -- Check where we actually spawn if we spawn on ground. + local spawnonship=false + local spawnonfarp=false + local spawnonrunway=false + local spawnonairport=false + if spawnonground then + + -- Spawning at a ship + spawnonship=SpawnAirbase:GetCategory()==1 -- Catetory 1 are ships. + + -- Spawning at a FARP. Catetory 4 are airbases so we need to check that type is FARP as well. + spawnonfarp=SpawnAirbase:GetCategory()==4 and SpawnAirbase:GetTypeName()=="FARP" + + -- Spawning at an airport. + spawnonairport=SpawnAirbase:GetCategory()==4 and SpawnAirbase:GetTypeName()~="FARP" + + -- Spawning on the runway. + spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + end + + + -- Array with parking spots coordinates. + local parkingspots={} + local parkingindex={} + local spots + + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. + if spawnonground then + + -- Number of free parking spots. + local nfree=0 + + -- Set terminal type. + local termtype=TerminalType + if spawnonrunway then + termtype=AIRBASE.TerminalType.Runway + end + + -- Number of free parking spots at the airbase. + nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, spawnonship or spawnonfarp or spawnonrunway) + spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, spawnonship or spawnonfarp or spawnonrunway) + + -- Get parking data. + local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) + + self:E(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + for _,_spot in pairs(parkingdata) do + self:E(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) + end + self:E(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, #SpawnTemplate.units)) + + -- Put parking spots in table. These spots are only used if + if nfree >= #SpawnTemplate.units or (spawnonrunway and nfree>0) then + + for i=1,#SpawnTemplate.units do + table.insert(parkingspots, spots[i].Coordinate) + table.insert(parkingindex, spots[i].TerminalID) + end + + else + self:E(string.format("Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + -- Not enough parking spots at the airport ==> Spawn in air. + spawnonground=false + spawnonship=false + spawnonfarp=false + spawnonrunway=false + + -- Set waypoint type/action to turning point. + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point + + -- Adjust altitude to be 500-1000 m above the airbase. + PointVec3.y=PointVec3:GetLandHeight()+math.random(200,1200) + + Takeoff=GROUP.Takeoff.Air + end + + end + -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - - -- These cause a lot of confusion. + self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - - UnitTemplate.parking = nil - UnitTemplate.parking_id = nil - UnitTemplate.alt = 0 - + + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x local SY = UnitTemplate.y - local BX = SpawnPoint.x - local BY = SpawnPoint.y - local TX = PointVec3.x + ( SX - BX ) - local TY = PointVec3.z + ( SY - BY ) - - UnitTemplate.x = TX - UnitTemplate.y = TY - - if Takeoff == GROUP.Takeoff.Air then - UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 ) - --else - -- UnitTemplate.alt = PointVec3.y + 10 - end - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - end - - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z - - if Takeoff == GROUP.Takeoff.Air then - SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 ) - --else - -- SpawnPoint.alt = PointVec3.y + 10 - end + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = PointVec3.x + (SX-BX) + local TY = PointVec3.z + (SY-BY) + + if spawnonground then + + -- Ships and FARPS seem to have a build in queue. + if spawnonship or spawnonfarp or spawnonrunway then + + self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + -- Spawn on ship. We take only the position of the ship. + SpawnTemplate.units[UnitID].x = PointVec3.x --TX + SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + + else + + self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- Get coordinates of parking spot. + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y + + end + + else + + self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + + end + + -- Parking spot id. + UnitTemplate.parking = nil + UnitTemplate.parking_id = nil + if parkingindex[UnitID] then + UnitTemplate.parking = parkingindex[UnitID] + end + + + -- Place marker at spawn position. + --if self.Debug then + local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x, SpawnTemplate.units[UnitID].alt, SpawnTemplate.units[UnitID].y) + unitspawn:MarkToAll(string.format("%s Spawnplace unit #%d, terminal %s", self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) + --end + + self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) + self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) + + self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + end + + -- Set gereral spawnpoint position. + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z + SpawnPoint.alt = PointVec3.y + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) - -- When spawned in the air, we need to generate a Takeoff Event - + -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 ) @@ -1801,7 +1929,7 @@ end --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) + self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) local Index = tonumber( IndexString ) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 19bb8e47c..253632052 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -155,6 +155,7 @@ do -- country -- @field UK -- @field FRANCE -- @field GERMANY + -- @field AGGRESSORS -- @field CANADA -- @field SPAIN -- @field THE_NETHERLANDS @@ -167,6 +168,60 @@ do -- country -- @field ABKHAZIA -- @field SOUTH_OSETIA -- @field ITALY + -- @field AUSTRALIA + -- @field SWITZERLAND + -- @field AUSTRIA + -- @field BELARUS + -- @field BULGARIA + -- @field CHEZH_REPUBLIC + -- @field CHINA + -- @field CROATIA + -- @field EGYPT + -- @field FINLAND + -- @field GREECE + -- @field HUNGARY + -- @field INDIA + -- @field IRAN + -- @field IRAQ + -- @field JAPAN + -- @field KAZAKHSTAN + -- @field NORTH_KOREA + -- @field PAKISTAN + -- @field POLAND + -- @field ROMANIA + -- @field SAUDI_ARABIA + -- @field SERBIA + -- @field SLOVAKIA + -- @field SOUTH_KOREA + -- @field SWEDEN + -- @field SYRIA + -- @field YEMEN + -- @field VIETNAM + -- @field VENEZUELA + -- @field TUNISIA + -- @field THAILAND + -- @field SUDAN + -- @field PHILIPPINES + -- @field MOROCCO + -- @field MEXICO + -- @field MALAYSIA + -- @field LIBYA + -- @field JORDAN + -- @field INDONESIA + -- @field HONDURAS + -- @field ETHIOPIA + -- @field CHILE + -- @field BRAZIL + -- @field BAHRAIN + -- @field THIRDREICH + -- @field YUGOSLAVIA + -- @field USSR + -- @field ITALIAN_SOCIAL_REPUBLIC + -- @field ALGERIA + -- @field KUWAIT + -- @field QATAR + -- @field OMAN + -- @field UNITED_ARAB_EMIRATES country = {} -- #country diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 53b4d67f6..7653e9261 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -70,6 +70,7 @@ -- @field #number coalition Coalition of spawn group template. -- @field #number country Country of spawn group template. -- @field #string category Category of aircarft: "plane" or "heli". +-- @field #number groupsize Number of aircraft in group. -- @field #string friendly Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. -- @field #table ctable Table with the valid coalitons from choice self.friendly. -- @field #table aircraft Table which holds the basic aircraft properties (speed, range, ...). @@ -146,6 +147,9 @@ -- @field #boolean checkontop Aircraft are checked if they were accidentally spawned on top of another unit. Default is true. -- @field #number rbug_maxretry Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. Default is 3. -- @field #boolean useparkingdb Parking spots are added to data base once an aircraft has used it. These spots can later be used by other aircraft. Default is true. +-- @field #number termtype Type of terminal to be used when spawning at an airbase. +-- @field #boolean starshape If true, aircraft travel A-->B-->A-->C-->A-->D... for commute. +-- @field #string homebase Home base for commute and return zone. Aircraft will always return to this base but otherwise travel in a star shaped way. -- @extends Core.Spawn#SPAWN --- Implements an easy to use way to randomly fill your map with AI aircraft. @@ -302,6 +306,7 @@ RAT={ coalition = nil, -- Coalition of spawn group template. country = nil, -- Country of the group template. category = nil, -- Category of aircarft: "plane" or "heli". + groupsize=nil, -- Number of aircraft in the group. friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral. ctable = {}, -- Table with the valid coalitons from choice self.friendly. aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...). @@ -353,7 +358,7 @@ RAT={ respawn_after_takeoff=false, -- Aircraft will be respawned directly after takeoff. respawn_after_crash=true, -- Aircraft will be respawned after a crash. respawn_inair=true, -- Aircraft are spawned in air if there is no free parking spot on the ground. - respawn_delay=nil, -- Delay in seconds until repawn happens after landing. + respawn_delay=0, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. waypointdescriptions={}, -- Array with descriptions for waypoint markers. waypointstatus={}, -- Array with status info on waypoints. @@ -377,8 +382,11 @@ RAT={ onboardnum0=1, -- (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is one. rbug_maxretry=3, -- Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. checkonrunway=true, -- Check whether aircraft have been spawned on the runway. - checkontop=true, -- Check whether aircraft have been spawned on top of another unit. - useparkingdb=true, -- Put known parking spots into a data base. + checkontop=false, -- Check whether aircraft have been spawned on top of another unit. + useparkingdb=false, -- Put known parking spots into a data base. + termtype=nil, -- Terminal type. + starshape=false, -- If true, aircraft travel A-->B-->A-->C-->A-->D... for commute. + homebase=nil, -- Home base for commute and return zone. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -501,7 +509,7 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.2.2", + version = "2.3.0", print = true, } @@ -565,7 +573,7 @@ function RAT:New(groupname, alias) end -- Welcome message. - self:F(RAT.id.."Creating new RAT object from template: "..groupname) + self:F(RAT.id..string.format("Creating new RAT object from template: %s.", groupname)) -- Set alias. alias=alias or groupname @@ -578,13 +586,16 @@ function RAT:New(groupname, alias) -- Check the group actually exists. if DCSgroup==nil then - self:E(RAT.id.."ERROR: Group with name "..groupname.." does not exist in the mission editor!") + self:E(RAT.id..string.format("ERROR: Group with name %s does not exist in the mission editor!", groupname)) return nil end -- Store template group. self.templategroup=GROUP:FindByName(groupname) + -- Get number of aircraft in group. + self.groupsize=self.templategroup:GetSize() + -- Set own coalition. self.coalition=DCSgroup:getCoalition() @@ -690,6 +701,7 @@ function RAT:Spawn(naircraft) end text=text..string.format("Min dist to destination: %4.1f\n", self.mindist) text=text..string.format("Max dist to destination: %4.1f\n", self.maxdist) + text=text..string.format("Terminal type: %s\n", tostring(self.termtype)) text=text..string.format("Takeoff type: %i\n", self.takeoff) text=text..string.format("Landing type: %i\n", self.landing) text=text..string.format("Commute: %s\n", tostring(self.commute)) @@ -814,12 +826,12 @@ function RAT:_CheckConsistency() -- Only zones but not takeoff air == > Enable takeoff air. if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then self.takeoff=RAT.wp.air - self:E(RAT.id.."ERROR: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start!") + self:E(RAT.id..string.format("ERROR: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!", self.alias)) end -- No airport and no zone specified. if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then self.random_departure=true - local text="No airports or zones found given in SetDeparture(). Enabling random departure airports!" + local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!", self.alias) self:E(RAT.id.."ERROR: "..text) MESSAGE:New(text, 30):ToAll() end @@ -951,6 +963,14 @@ function RAT:SetCountry(id) self.country=id end +--- Set the terminal type the aircraft use when spawning at an airbase. Cf. https://wiki.hoggitworld.com/view/DCS_func_getParking +-- @param #RAT self +-- @param #number termtype Type of terminal. Use enumerator AIRBASE.TerminalType.XXX or check https://wiki.hoggitworld.com/view/DCS_func_getParking for valid numbers. +function RAT:SetTerminalType(termtype) + self:F2(termtype) + self.termtype=termtype +end + --- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air. -- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines. -- @param #RAT self @@ -1177,10 +1197,16 @@ end --- Aircraft will commute between their departure and destination airports or zones. -- @param #RAT self -function RAT:Commute() +-- @param #boolean starshape If true, keep homebase, i.e. travel A-->B-->A-->C-->A-->D... instead of A-->B-->A-->B-->A... +function RAT:Commute(starshape) self:F2() self.commute=true self.continuejourney=false + if starshape then + self.starshape=starshape + else + self.starshape=false + end end --- Set the delay before first group is spawned. @@ -1203,12 +1229,12 @@ end --- Make aircraft respawn the moment they land rather than at engine shut down. -- @param #RAT self --- @param #number delay (Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. Minimum is 0.5 seconds. +-- @param #number delay (Optional) Delay in seconds until respawn happens after landing. Default is 180 seconds. Minimum is 1.0 seconds. function RAT:RespawnAfterLanding(delay) self:F2(delay) delay = delay or 180 self.respawn_at_landing=true - delay=math.max(0.5, delay) + delay=math.max(1.0, delay) self.respawn_delay=delay end @@ -1217,7 +1243,7 @@ end -- @param #number delay Delay in seconds until respawn happens. Default is 1 second. Minimum is 1 second. function RAT:SetRespawnDelay(delay) self:F2(delay) - delay = delay or 1 + delay = delay or 1.0 delay=math.max(1.0, delay) self.respawn_delay=delay end @@ -1786,9 +1812,9 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live -- Find parking spot in RAT parking DB. Category 4 should be airports and farps. Ships would be caterory 1. local _spawnpos=_lastpos - if self.useparkingdb and (takeoff==RAT.wp.cold or takeoff==RAT.wp.hot) and departure:GetCategory()==4 and _spawnpos==nil then - _spawnpos=self:_FindParkingSpot(departure) - end +-- if self.useparkingdb and (takeoff==RAT.wp.cold or takeoff==RAT.wp.hot) and departure:GetCategory()==4 and _spawnpos==nil then +-- _spawnpos=self:_FindParkingSpot(departure) +-- end -- Set (another) livery. local livery @@ -1805,7 +1831,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live end -- Modify the spawn template to follow the flight plan. - self:_ModifySpawnTemplate(waypoints, livery, _spawnpos) + self:_ModifySpawnTemplate(waypoints, livery, _spawnpos, departure, takeoff) -- Actually spawn the group. local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP @@ -1889,7 +1915,6 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live self.ratcraft[self.SpawnIndex].nrespawn=nrespawn -- If we start at a parking position, we memorize the parking spot position for future use (DCS bug). - -- TODO: Check for ships and FARPS. if self.useparkingdb and (takeoff==RAT.wp.cold or takeoff==RAT.wp.hot) and departure:GetCategory()==4 then self:_AddParkingSpot(departure, group) end @@ -1930,11 +1955,13 @@ end --- Respawn a group. -- @param #RAT self --- @param Wrapper.Group#GROUP group Group to be repawned. -function RAT:_Respawn(group) +-- @param #number index Spawn index. +-- @param Core.Point#COORDINATE lastpos Last known position of the group. +-- @param #number delay Delay before respawn +function RAT:_Respawn(index, lastpos, delay) -- Get the spawn index from group - local index=self:GetSpawnIndexFromGroup(group) + --local index=self:GetSpawnIndexFromGroup(group) -- Get departure and destination from previous journey. local departure=self.ratcraft[index].departure @@ -1943,7 +1970,7 @@ function RAT:_Respawn(group) local landing=self.ratcraft[index].landing local livery=self.ratcraft[index].livery local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] - local lastpos=group:GetCoordinate() + --local lastpos=group:GetCoordinate() local _departure=nil local _destination=nil @@ -2004,8 +2031,22 @@ function RAT:_Respawn(group) elseif self.commute then -- We commute between departure and destination. - _departure=destination:GetName() - _destination=departure:GetName() + + if self.starshape==true then + if destination:GetName()==self.homebase then + -- We are at our home base ==> destination is again randomly selected. + _departure=self.homebase + _destination=nil -- destination will be set anew + else + -- We are not a our home base ==> we fly back to our home base. + _departure=destination:GetName() + _destination=self.homebase + end + else + -- Simply switch departure and destination. + _departure=destination:GetName() + _destination=departure:GetName() + end -- Use the same livery for next aircraft. _livery=livery @@ -2067,6 +2108,16 @@ function RAT:_Respawn(group) -- Debug self:T2({departure=_departure, destination=_destination, takeoff=_takeoff, landing=_landing, livery=_livery, lastwp=_lastwp}) + + -- We should give it at least 3 sec since this seems to be the time until free parking spots after despawn are available again (Sirri Island test). + local respawndelay + if delay then + respawndelay=delay + elseif self.respawn_delay then + respawndelay=self.respawn_delay+3 -- despawn happens after self.respawndelay. We add another 3 sec for free parking. + else + respawndelay=3 + end -- Spawn new group. local arg={} @@ -2078,7 +2129,8 @@ function RAT:_Respawn(group) arg.livery=_livery arg.lastwp=_lastwp arg.lastpos=_lastpos - SCHEDULER:New(nil, self._SpawnWithRouteTimer, {arg}, self.respawn_delay or 1) + self:T(RAT.id..string.format("%s delayed respawn in %.1f seconds.", self.alias, respawndelay)) + SCHEDULER:New(nil, self._SpawnWithRouteTimer, {arg}, respawndelay) end @@ -2166,6 +2218,9 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) else departure=self:_PickDeparture(takeoff) + if self.commute and self.starshape==true and self.homebase==nil then + self.homebase=departure:GetName() + end end -- Return nil if no departure could be found. @@ -3031,14 +3086,18 @@ function RAT:Status(message, forID) -- Current time. local Tnow=timer.getTime() + + -- Alive counter. + local nalive=0 - -- Loop over all ratcraft. + -- Loop over all ratcraft. for spawnindex,ratcraft in ipairs(self.ratcraft) do -- Get group. local group=ratcraft.group --Wrapper.Group#GROUP if group and group:IsAlive() then + nalive=nalive+1 -- Gather some information. local prefix=self:_GetPrefixFromGroup(group) @@ -3143,7 +3202,7 @@ function RAT:Status(message, forID) text=text..string.format("\nTime on ground = %6.0f seconds\n", Tg) text=text..string.format("Position change = %8.1f m since %3.0f seconds.", Dg, dTlast) end - self:T2(RAT.id..text) + self:T(RAT.id..text) if message then MESSAGE:New(text, 20):ToAll() end @@ -3158,23 +3217,29 @@ function RAT:Status(message, forID) self:T(RAT.id..text) self:_Despawn(group) end + -- Despawn group if life is < 10% and distance travelled < 100 m. if life<10 and Dtravel<100 then local text=string.format("Damaged group %s is despawned. Life = %3.0f", self.alias, life) self:T(RAT.id..text) self:_Despawn(group) end + end -- Despawn groups after they have reached their destination zones. if ratcraft.despawnme then + local text=string.format("Flight %s will be despawned NOW!", self.alias) self:T(RAT.id..text) -- Despawn old group. - if (not self.norespawn) and (not self.respawn_after_takeoff) then - self:_Respawn(group) + if (not self.norespawn) and (not self.respawn_after_takeoff) then + local idx=self:GetSpawnIndexFromGroup(group) + local coord=group:GetCoordinate() + self:_Respawn(idx, coord, 0) end - self:_Despawn(group) + self:_Despawn(group, 0) + end else @@ -3185,11 +3250,10 @@ function RAT:Status(message, forID) end - if (message and not forID) then - local text=string.format("Alive groups of %s: %d", self.alias, self.alive) - self:T(RAT.id..text) - MESSAGE:New(text, 20):ToAll() - end + -- Alive groups. + local text=string.format("Alive groups of %s: %d, nalive=%d/%d", self.alias, self.alive, nalive, self.ngroups) + self:T(RAT.id..text) + MESSAGE:New(text, 20):ToAllIf(message and not forID) end @@ -3236,7 +3300,7 @@ function RAT:_SetStatus(group, status) local text=string.format("Flight %s: %s.", group:GetName(), status) self:T(RAT.id..text) - if (not (no1 or no2 or no3)) then + if not (no1 or no2 or no3) then MESSAGE:New(text, 10):ToAllIf(self.reportstatus) end @@ -3245,6 +3309,30 @@ function RAT:_SetStatus(group, status) end end +--- Get status of group. +-- @param #RAT self +-- @param Wrapper.Group#GROUP group Group. +-- @return #string status Status of group. +function RAT:GetStatus(group) + + if group and group:IsAlive() then + + -- Get index from groupname. + local index=self:GetSpawnIndexFromGroup(group) + + if self.ratcraft[index] then + + -- Set new status. + return self.ratcraft[index].status + + end + + end + + return "nonexistant" +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function is executed when a unit is spawned. @@ -3344,7 +3432,7 @@ function RAT:_OnBirth(EventData) -- Check if any unit of the group was spawned on top of another unit in the MOOSE data base. local ontop=false if self.checkontop then - ontop=self:_CheckOnTop(SpawnGroup) + ontop=self:_CheckOnTop(SpawnGroup, 2) end if ontop then @@ -3429,12 +3517,11 @@ function RAT:_OnTakeoff(EventData) self:_SetStatus(SpawnGroup, status) if self.respawn_after_takeoff then - text="Event: Group "..SpawnGroup:GetName().." will be respawned." + text="Event: Group "..SpawnGroup:GetName().." will be respawned after takeoff." self:T(RAT.id..text) -- Respawn group. We respawn with no parameters from the old flight. self:_SpawnWithRoute(nil, nil, nil, nil, nil, nil, nil, nil) - --self:_Respawn(SpawnGroup) end end @@ -3479,7 +3566,9 @@ function RAT:_OnLand(EventData) self:T(RAT.id..text) -- Respawn group. - self:_Respawn(SpawnGroup) + local idx=self:GetSpawnIndexFromGroup(SpawnGroup) + local coord=SpawnGroup:GetCoordinate() + self:_Respawn(idx, coord) end end @@ -3492,6 +3581,7 @@ end --- Function is executed when a unit shuts down its engines. -- @param #RAT self +-- @param Core.Event#EVENTDATA EventData function RAT:_OnEngineShutdown(EventData) self:F3(EventData) self:T3(RAT.id.."Captured event EngineShutdown!") @@ -3503,35 +3593,42 @@ function RAT:_OnEngineShutdown(EventData) -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - if EventPrefix then - - -- Check that the template name actually belongs to this object. - if EventPrefix == self.alias then - - -- Despawn group only if it on the ground. - if not SpawnGroup:InAir() then - - local text="Event: Group "..SpawnGroup:GetName().." shut down its engines." - self:T(RAT.id..text) + -- Check that the template name actually belongs to this object. + if EventPrefix and EventPrefix == self.alias then + -- Despawn group only if it on the ground. + if not SpawnGroup:InAir() then + + -- Current status. + local currentstate=self:GetStatus(SpawnGroup) + + local text=string.format("Event: Unit %s of group %s shut down its engines. Current state %s.", EventData.IniUnitName, SpawnGroup:GetName(), currentstate) + self:T(RAT.id..text) + + -- Check that this is not the second unit of the group so that we dont trigger re- and despawns twice. + if currentstate~=RAT.status.EventEngineShutdown and currentstate~="Dead" then + -- Set status. local status=RAT.status.EventEngineShutdown self:_SetStatus(SpawnGroup, status) if not self.respawn_at_landing and not self.norespawn then - text="Event: Group "..SpawnGroup:GetName().." will be respawned." + text=string.format("Event: Group %s will be respawned. Current state %s => new state %s.", SpawnGroup:GetName(), currentstate, status) self:T(RAT.id..text) -- Respawn group. - self:_Respawn(SpawnGroup) + local idx=self:GetSpawnIndexFromGroup(SpawnGroup) + local coord=SpawnGroup:GetCoordinate() + self:_Respawn(idx, coord) end - + -- Despawn group. text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." self:T(RAT.id..text) - self:_Despawn(SpawnGroup) + self:_Despawn(SpawnGroup) end + end end @@ -3547,22 +3644,20 @@ function RAT:_OnHit(EventData) self:F3(EventData) self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s", self.alias, tostring(EventData.IniUnitName), tostring(EventData.TgtUnitName))) - local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP + local SpawnGroup = EventData.TgtGroup --Wrapper.Group#GROUP if SpawnGroup then -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - if EventPrefix then - - -- Check that the template name actually belongs to this object. - if EventPrefix == self.alias then + -- Check that the template name actually belongs to this object. + if EventPrefix and EventPrefix == self.alias then + -- Debug info. + self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.", SpawnGroup:GetName(), tostring(EventData.TgtUnitName))) - -- Debug info. - self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.", SpawnGroup:GetName(), EventData.IniUnitName)) - - end + local text=string.format("%s, unit %s was hit!", self.alias, EventData.TgtUnitName) + MESSAGE:New(text, 10):ToAllIf(self.reportstatus or self.Debug) end end end @@ -3658,36 +3753,33 @@ function RAT:_OnCrash(EventData) -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) - if EventPrefix then + -- Check that the template name actually belongs to this object. + if EventPrefix and EventPrefix == self.alias then - -- Check that the template name actually belongs to this object. - if EventPrefix == self.alias then - - -- Update number of alive units in the group. - local _i=self:GetSpawnIndexFromGroup(SpawnGroup) - self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 - local _n=self.ratcraft[_i].nunits - local _n0=SpawnGroup:GetInitialSize() - - -- Debug info. - local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.", SpawnGroup:GetName(), EventData.IniUnitName, _n, _n0) + -- Update number of alive units in the group. + local _i=self:GetSpawnIndexFromGroup(SpawnGroup) + self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 + local _n=self.ratcraft[_i].nunits + local _n0=SpawnGroup:GetInitialSize() + + -- Debug info. + local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.", SpawnGroup:GetName(), EventData.IniUnitName, _n, _n0) + self:T(RAT.id..text) + + -- Set status. + local status=RAT.status.EventCrash + self:_SetStatus(SpawnGroup, status) + + -- Respawn group if all units are dead. + if _n==0 and self.respawn_after_crash and not self.norespawn then + local text=string.format("No units left of group %s. Group will be respawned now.", SpawnGroup:GetName()) self:T(RAT.id..text) - - -- Set status. - local status=RAT.status.EventCrash - self:_SetStatus(SpawnGroup, status) - - -- Respawn group if all units are dead. - if _n==0 and self.respawn_after_crash and not self.norespawn then - local text=string.format("No units left of group %s. Group will be respawned now.", SpawnGroup:GetName()) - self:T(RAT.id..text) - -- Respawn group. - self:_Respawn(SpawnGroup) - end - - --TODO: Maybe spawn some people at the crash site and send a distress call. - -- And define them as cargo which can be rescued. + -- Respawn group. + local idx=self:GetSpawnIndexFromGroup(SpawnGroup) + local coord=SpawnGroup:GetCoordinate() + self:_Respawn(idx, coord) end + end else @@ -3701,7 +3793,8 @@ end -- Index of ratcraft array is taken from spawned group name. -- @param #RAT self -- @param Wrapper.Group#GROUP group Group to be despawned. -function RAT:_Despawn(group) +-- @param #number delay Delay in seconds before the despawn happens. +function RAT:_Despawn(group, delay) if group ~= nil then @@ -3741,8 +3834,23 @@ function RAT:_Despawn(group) --table.remove(self.ratcraft, index) - -- This will destroy the DCS group and create a single DEAD event. - self:_Destroy(group) + -- We should give it at least 3 sec since this seems to be the time until free parking spots after despawn are available again (Sirri Island test). + local despawndelay=0 + if delay then + -- Explicitly requested delay time. + despawndelay=delay + elseif self.respawn_delay then + -- Despawn afer respawn_delay. Actual respawn happens in +3 seconds to allow for free parking. + despawndelay=self.respawn_delay + end + + -- This will destroy the DCS group and create a single DEAD event. + --if despawndelay>0.5 then + self:T(RAT.id..string.format("%s delayed despawn in %.1f seconds.", self.alias, despawndelay)) + SCHEDULER:New(nil, self._Destroy, {self, group}, despawndelay) + --else + --self:_Destroy(group) + --end -- Remove submenu for this group. if self.f10menu and self.SubMenuName ~= nil then @@ -3764,11 +3872,6 @@ function RAT:_Destroy(group) local DCSGroup = group:GetDCSObject() -- DCS#Group if DCSGroup and DCSGroup:isExist() then - - --local DCSUnit = DCSGroup:getUnit(1) -- DCS#Unit - --if DCSUnit then - -- self:_CreateEventDead(timer.getTime(), DCSUnit) - --end -- Cread one single Dead event and delete units from database. local triggerdead=true @@ -4421,11 +4524,11 @@ end --- Find aircraft that have accidentally been spawned on top of each other. -- @param #RAT self -- @param Wrapper.Group#GROUP group Units of this group will be checked. +-- @param #number distmin Allowed distance in meters between units. Units with a distance smaller than this number are considered to be on top of each other. -- @return #boolean True if group was destroyed because it was on top of another unit. False if otherwise. -function RAT:_CheckOnTop(group) +function RAT:_CheckOnTop(group, distmin) - -- Minimum allowed distance between two units - local distmin=5 + distmin=distmin or 2 for i,uniti in pairs(group:GetUnits()) do local uniti=uniti --Wrapper.Unit#UNIT @@ -4448,15 +4551,13 @@ function RAT:_CheckOnTop(group) if DCSuniti and DCSuniti:isExist() and DCSunitj and DCSunitj:isExist() then -- Distance between units. - local _dist=uniti:GetCoordinate():Get2DDistance(unitj:GetCoordinate()) + local _dist=uniti:GetCoordinate():Get3DDistance(unitj:GetCoordinate()) -- Check for min distance. if _dist < distmin then if not uniti:InAir() and not unitj:InAir() then - --uniti:Destroy() - --self:_CreateEventDead(timer.getTime(), uniti) - --unitj:Destroy() - --self:_CreateEventDead(timer.getTime(), unitj) + -- Trigger immidiate destuction of unit. + self:T(RAT.id..string.format("Unit %s is on top of unit %s. Distance %.2f m.", namei, namej,_dist)) return true end end @@ -4807,7 +4908,6 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Modifies the template of the group to be spawned. -- In particular, the waypoints of the group's flight plan are copied into the spawn template. -- This allows to spawn at airports and also land at other airports, i.e. circumventing the DCS "landing bug". @@ -4815,22 +4915,52 @@ end -- @param #table waypoints The waypoints of the AI flight plan. -- @param #string livery (Optional) Livery of the aircraft. All members of a flight will get the same livery. -- @param Core.Point#COORDINATE spawnplace (Optional) Place where spawning should happen. If not present, first waypoint is taken. -function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) +-- @param Wrapper.Airbase#AIRBASE departure Departure airbase or zone. +-- @param #number takeoff Takeoff type. +function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, takeoff) self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace}) -- The 3D vector of the first waypoint, i.e. where we actually spawn the template group. - local PointVec3 = {x=waypoints[1].x, y=waypoints[1].alt, z=waypoints[1].y} + local PointVec3 = COORDINATE:New(waypoints[1].x, waypoints[1].alt, waypoints[1].y) if spawnplace then - PointVec3 = spawnplace:GetVec3() - self:T({spawnplace=PointVec3}) + PointVec3 = COORDINATE:NewFromCoordinate(spawnplace) end + -- Check if we spawn on ground. + local spawnonground=takeoff==RAT.wp.cold or takeoff==RAT.wp.hot or takeoff==RAT.wp.runway + + -- Check where we actually spawn if we spawn on ground. + local spawnonship=false + local spawnonfarp=false + local spawnonrunway=false + local spawnonairport=false + if spawnonground then + + -- Spawning at a ship + spawnonship=departure:GetCategory()==1 -- Catetory 1 are ships. + + -- Spawning at a FARP. Catetory 4 are airbases so we need to check that type is FARP as well. + spawnonfarp=departure:GetCategory()==4 and departure:GetTypeName()=="FARP" + + -- Spawning at an airport. + spawnonairport=departure:GetCategory()==4 and departure:GetTypeName()~="FARP" + + -- Spawning on the runway. + spawnonrunway=takeoff==RAT.wp.runway + end + + local automatic=false + if automatic and spawnonground then + PointVec3=PointVec3:GetClosestParkingSpot(true, departure) + end + -- Heading from first to seconds waypoints to align units in case of air start. local course = self:_Course(waypoints[1], waypoints[2]) local heading = self:_Heading(course) if self:_GetSpawnIndex(self.SpawnIndex+1) then + -- Get template from group. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate if SpawnTemplate then @@ -4841,27 +4971,139 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) -- This is used in the SPAWN:SpawnWithIndex() function. Some values are overwritten there! self.SpawnUnControlled=true end + + -- Array with parking spots coordinates. + local parkingspots={} + local parkingindex={} + local spots + + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. + if spawnonground then + -- Number of free parking spots. + local nfree=0 + + -- Set terminal type. + local termtype=self.termtype + if spawnonrunway then + termtype=AIRBASE.TerminalType.Runway + end + + -- Number of free parking spots at the airbase. + nfree=departure:GetFreeParkingSpotsNumber(termtype, spawnonship or spawnonfarp or spawnonrunway) + spots=departure:GetFreeParkingSpotsTable(termtype, spawnonship or spawnonfarp or spawnonrunway) + + -- Get parking data. + local parkingdata=departure:GetParkingSpotsTable(termtype) + + self:E(RAT.id..string.format("Parking at %s, terminal type %s:", departure:GetName(), tostring(termtype))) + for _,_spot in pairs(parkingdata) do + self:E(RAT.id..string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + departure:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) + end + self:E(RAT.id..string.format("%s at %s: free parking spots = %d - number of units = %d", self.alias, departure:GetName(), nfree, #SpawnTemplate.units)) + + + -- Put parking spots in table. These spots are only used if + if nfree >= #SpawnTemplate.units or (spawnonrunway and nfree>0) then + + for i=1,#SpawnTemplate.units do + table.insert(parkingspots, spots[i].Coordinate) + table.insert(parkingindex, spots[i].TerminalID) + end + + + if spawnonrunway then + --PointVec3=spots[1] + end + + else + self:E(RAT.id..string.format("RAT group %s has no parking spots at %s ==> air start!", self.alias, departure:GetName())) + + -- Not enough parking spots at the airport ==> Spawn in air. + spawnonground=false + spawnonship=false + spawnonfarp=false + spawnonrunway=false + + -- Set waypoint type/action to turning point. + waypoints[1].type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + waypoints[1].action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point + + -- Adjust altitude to be 500-1000 m above the airbase. + PointVec3.y=PointVec3:GetLandHeight()+math.random(500,1000) + + end + + end + -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) - -- Tranlate position. + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] + + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y local TX = PointVec3.x + (SX-BX) local TY = PointVec3.z + (SY-BY) - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY - SpawnTemplate.units[UnitID].alt = PointVec3.y + + if spawnonground then + + -- Shíps and FARPS seem to have a build in queue. + if spawnonship or spawnonfarp or spawnonrunway or automatic then + + self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName())) + + -- Spawn on ship. We take only the position of the ship. + SpawnTemplate.units[UnitID].x = PointVec3.x --TX + SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + + else + + self:T(RAT.id..string.format("RAT group %s spawning at airbase %s on parking spot id %d", self.alias, departure:GetName(), parkingindex[UnitID])) + + -- Get coordinates of parking spot. + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y + + end + + else - if self.Debug then - local unitspawn=COORDINATE:New(TX,PointVec3.y,TY) - unitspawn:MarkToAll(string.format("Spawnplace unit #%d", UnitID)) + self:T(RAT.id..string.format("RAT group %s spawning in air at %s.", self.alias, departure:GetName())) + + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + end + + -- Place marker at spawn position. + if self.Debug then + local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x, SpawnTemplate.units[UnitID].alt, SpawnTemplate.units[UnitID].y) + unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d", self.alias, UnitID)) + end + + -- Parking spot id. + UnitTemplate.parking = nil + UnitTemplate.parking_id = nil + if parkingindex[UnitID] and not automatic then + UnitTemplate.parking = parkingindex[UnitID] + end + + self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking = %s",self.alias, UnitID, tostring(UnitTemplate.parking))) + self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias, UnitID, tostring(UnitTemplate.parking_id))) + + + -- Set initial heading. SpawnTemplate.units[UnitID].heading = heading SpawnTemplate.units[UnitID].psi = -heading @@ -4889,15 +5131,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) SpawnTemplate.CountryID=self.country end - -- Parking spot. - UnitTemplate.parking = nil - UnitTemplate.parking_id = self.parking_id - --self:T(RAT.id.."Parking ID "..tostring(self.parking_id)) - - -- Initial altitude - UnitTemplate.alt=PointVec3.y - - self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) end -- Copy waypoints into spawntemplate. By this we avoid the nasty DCS "landing bug" :) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index d31779479..ae5b21368 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -106,7 +106,7 @@ AIRBASE.Caucasus = { ["Mozdok"] = "Mozdok", ["Beslan"] = "Beslan", } - +-- --- @field Nevada -- -- These are all airbases of Nevada: @@ -150,7 +150,7 @@ AIRBASE.Nevada = { ["Tonopah_Airport"] = "Tonopah Airport", ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", } - +-- --- @field Normandy -- -- These are all airbases of Normandy: @@ -219,6 +219,56 @@ AIRBASE.Normandy = { ["Tangmere"] = "Tangmere", ["Ford"] = "Ford", } +-- +--- @field PersianGulf +-- +-- These are all airbases of the Persion Gulf Map: +-- +-- * AIRBASE.PersianGulf.Fujairah_Intl +-- * AIRBASE.PersianGulf.Qeshm_Island +-- * AIRBASE.PersianGulf.Sir_Abu_Nuayr +-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport +-- * AIRBASE.PersianGulf.Bandar_Abbas_Intl +-- * AIRBASE.PersianGulf.Bandar_Lengeh +-- * AIRBASE.PersianGulf.Tunb_Island_AFB +-- * AIRBASE.PersianGulf.Havadarya +-- * AIRBASE.PersianGulf.Lar_Airbase +-- * AIRBASE.PersianGulf.Sirri_Island +-- * AIRBASE.PersianGulf.Tunb_Kochak +-- * AIRBASE.PersianGulf.Al_Dhafra_AB +-- * AIRBASE.PersianGulf.Dubai_Intl +-- * AIRBASE.PersianGulf.Al_Maktoum_Intl +-- * AIRBASE.PersianGulf.Khasab +-- * AIRBASE.PersianGulf.Al_Minhad_AB + -- * AIRBASE.PersianGulf.Sharjah_Intl +AIRBASE.PersianGulf = { + ["Fujairah_Intl"] = "Fujairah Intl", + ["Qeshm_Island"] = "Qeshm Island", + ["Sir_Abu_Nuayr"] = "Sir Abu Nuayr", + ["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport", + ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", + ["Bandar_Lengeh"] = "Bandar Lengeh", + ["Tunb_Island_AFB"] = "Tunb Island AFB", + ["Havadarya"] = "Havadarya", + ["Lar_Airbase"] = "Lar Airbase", + ["Sirri_Island"] = "Sirri Island", + ["Tunb_Kochak"] = "Tunb Kochak", + ["Al_Dhafra_AB"] = "Al Dhafra AB", + ["Dubai_Intl"] = "Dubai Intl", + ["Al_Maktoum_Intl"] = "Al Maktoum Intl", + ["Khasab"] = "Khasab", + ["Al_Minhad_AB"] = "Al Minhad AB", + ["Sharjah_Intl"] = "Sharjah Intl", + } +-- +--- @field Terminal Types of parking spots. +AIRBASE.TerminalType = { + Runway=16, + Helicopter=40, + HardenedShelter=68, + OpenOrShelter=72, + OpenAir=104, +} -- Registration. @@ -257,6 +307,9 @@ function AIRBASE:FindByName( 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 ) @@ -274,5 +327,231 @@ function AIRBASE:GetZone() return self.AirbaseZone end +--- Get all airbases of the current map. This includes ships and FARPS. +-- @param #AIRBASE self +-- @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 self: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) + + -- Get DCS airbase object. + local DCSAirbase=self:GetDCSObject() + + -- Get parking data. + local parkingdata=nil + if DCSAirbase then + parkingdata=DCSAirbase:getParking(available) + end + + return parkingdata +end + +--- Get number parking spots at an airbase. Optionally, for a specific terminal type. Spots on runway are exculded if not explicitly requested by terminal type +-- @param #AIRBASE self +-- @param #number termtype Terminal type. +-- @return #number Number of free parking spots at this airbase. +function AIRBASE:GetParkingSpotsNumber(termtype) + + -- Get free parking spots data. + local parkingdata=self:GetParkingData(false) + + local nfree=0 + for _,parkingspot in pairs(parkingdata) do + -- Spots on runway are not counted unless explicitly requested. + if (termtype~=nil and parkingspot.Term_Type==termtype) or (termtype==nil and parkingspot.Term_Type~=AIRBASE.TerminalType.Runway) then + nfree=nfree+1 + end + end + + return nfree +end + +--- Get number of free parking spots at an airbase. +-- @param #AIRBASE self +-- @param #number 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 (termtype~=nil and parkingspot.Term_Type==termtype) or (termtype==nil and parkingspot.Term_Type~=AIRBASE.TerminalType.Runway) 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 #number 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 (termtype and parkingspot.Term_Type==termtype) or (termtype==nil and parkingspot.Term_Type~=AIRBASE.TerminalType.Runway) 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 #number 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 (termtype and parkingspot.Term_Type==termtype) or (termtype==nil and parkingspot.Term_Type~=AIRBASE.TerminalType.Runway) then + local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) + local gotunits,gotstatics,gotscenery,_,statics,_=_coord:ScanObjects(.5) + env.info(string.format("FF scan: terminal index %03d, type = %03d, gotunits=%s, gotstatics=%s, gotscenery=%s", parkingspot.Term_Index+1, parkingspot.Term_Type, tostring(gotunits), tostring(gotstatics), tostring(gotscenery))) + if gotstatics then + for _,static in pairs(statics) do + env.info(string.format("FF Static name= %s", tostring(static:getName()))) + end + end + 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 #number 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 free spots into table. + local freespots={} + for _,_spot in pairs(parkingdata) do + if (termtype and _spot.Term_Type==termtype) or (termtype==nil and _spot.Term_Type~=AIRBASE.TerminalType.Runway) then + local _free=_isfree(_spot) + local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) + table.insert(freespots, {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 + + return freespots +end + +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @param #number 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 (termtype and _spot.Term_Type==termtype) or (termtype==nil and _spot.Term_Type~=AIRBASE.TerminalType.Runway) 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 #number termtype Terminal type for which marks should be placed. +function AIRBASE:MarkParkingSpots(termtype) + + local parkingdata=self:GetParkingSpotsTable(termtype) + + 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("%s, Term Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %.1f", + airbasename, _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) + + -- Create mark on the F10 map. + _spot.Coordinate:MarkToAll(_text) + + -- Info to DCS.log file. + self:E(_text) + end +end \ No newline at end of file