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..e007998ce 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -292,8 +292,108 @@ 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 (Optional) Scan radius in meters. Default 100 m. + -- @param #boolean scanunits (Optional) If true scan for units. Default true. + -- @param #boolean scanstatics (Optional) If true scan for static objects. Default true. + -- @param #boolean scanscenery (Optional) If true scan for scenery objects. Default false. + -- @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, scanunits, scanstatics, scanscenery) + self:F(string.format("Scanning in radius %.1f m.", radius)) + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = self:GetVec3(), + radius = radius, + } + } + + -- Defaults + radius=radius or 100 + if scanunits==nil then + scanunits=true + end + if scanstatics==nil then + scanstatics=true + end + if scanscenery==nil then + scanscenery=false + end + + --{Object.Category.UNIT, Object.Category.STATIC, Object.Category.SCENERY} + local scanobjects={} + if scanunits then + table.insert(scanobjects, Object.Category.UNIT) + end + if scanstatics then + table.insert(scanobjects, Object.Category.STATIC) + end + if scanscenery then + table.insert(scanobjects, Object.Category.SCENERY) + end + + -- 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 + if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist()) 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(scanobjects, SphereSearch, EvaluateZone) + + for _,unit in pairs(Units) do + self:T(string.format("Scan found unit %s", unit:getName())) + end + for _,static in pairs(Statics) do + self:T(string.format("Scan found static %s", static:getName())) + end + for _,scenery in pairs(Scenery) do + self:T(string.format("Scan found scenery %s", scenery:getTypeName())) + 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 +1046,84 @@ do -- COORDINATE return RoutePoint end + --- Gets the nearest parking spot. + -- @param #COORDINATE self + -- @param Wrapper.Airbase#AIRBASE airbase (Optional) Search only parking spots at this airbase. + -- @param Wrapper.Airbase#Terminaltype terminaltype (Optional) Type of the terminal. Default any execpt valid spawn points on runway. + -- @param #boolean free (Optional) If true, returns the closest free spot. If false, returns the closest occupied spot. If nil, returns the closest spot regardless of free or occupied. + -- @return Core.Point#COORDINATE Coordinate of the nearest parking spot. + -- @return #number Terminal ID. + -- @return #number Distance to closest parking spot in meters. + function COORDINATE:GetClosestParkingSpot(airbase, terminaltype, free) + + -- Get airbase table. + local airbases={} + if airbase then + table.insert(airbases,airbase) + else + airbases=AIRBASE.GetAllAirbases() + end + + -- Init. + local _closest=nil --Core.Point#COORDINATE + local _termID=nil + local _distmin=nil + + -- Loop over all airbases. + for _,_airbase in pairs(airbases) do + + local mybase=_airbase --Wrapper.Airbase#AIRBASE + local parkingdata=mybase:GetParkingSpotsTable(terminaltype) + + for _,_spot in pairs(parkingdata) do + + -- Check for parameters. + if (free==true and _spot.Free==true) or (free==false and _spot.Free==false) or free==nil then + + local _coord=_spot.Coordinate --Core.Point#COORDINATE + + local _dist=self:Get2DDistance(_coord) + if _distmin==nil then + _closest=_coord + _distmin=_dist + _termID=_spot.TerminalID + else + if _dist<_distmin then + _distmin=_dist + _closest=_coord + _termID=_spot.TerminalID + end + end + + end + end + end + + return _closest, _termID, _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 (Optional) Type of the terminal. + -- @return #COORDINATE Coordinate of the nearest free parking spot. + -- @return #number Terminal ID. + -- @return #number Distance to closest free parking spot in meters. + function COORDINATE:GetClosestFreeParkingSpot(airbase, terminaltype) + return self:GetClosestParkingSpot(airbase, terminaltype, true) + end + + --- Gets the nearest occupied parking spot. + -- @param #COORDINATE self + -- @param Wrapper.Airbase#AIRBASE airbase (Optional) Search only parking spots at that airbase. + -- @param Wrapper.Airbase#Terminaltype terminaltype (Optional) Type of the terminal. + -- @return #COORDINATE Coordinate of the nearest occupied parking spot. + -- @return #number Terminal ID. + -- @return #number Distance to closest occupied parking spot in meters. + function COORDINATE:GetClosestOccupiedParkingSpot(airbase, terminaltype) + return self:GetClosestParkingSpot(airbase, terminaltype, false) + 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..67d1cf753 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1218,8 +1218,9 @@ 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. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. +-- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. +-- @return Wrapper.Group#GROUP that was spawned or nil when nothing was spawned. -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) @@ -1236,34 +1237,56 @@ end -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air ) -- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) +-- +-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- -function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2 - self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude } ) +function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn ) -- R2.2, R2.4 + self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) - 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 + -- By default, groups are spawned in air if no parking spot is available. + if EmergencyAirSpawn==nil then + EmergencyAirSpawn=true + end + 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 } ) + + -- Template group, unit and its attributes. + local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) + local TemplateUnit=TemplateGroup:GetUnit(1) + local ishelo=TemplateUnit:HasAttribute("Helicopters") + local isbomber=TemplateUnit:HasAttribute("Bombers") + local istransport=TemplateUnit:HasAttribute("Transports") + local isfighter=TemplateUnit:HasAttribute("Battleplanes") + -- 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,72 +1297,253 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2 SpawnPoint.airdromeId = AirbaseID end - SpawnPoint.alt = 0 - - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + -- Set waypoint type/action. + 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:T({spawnonground=spawnonground, TOtype=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 + if AirbaseCategory == Airbase.Category.SHIP then + spawnonship=true + elseif AirbaseCategory == Airbase.Category.HELIPAD then + spawnonfarp=true + elseif AirbaseCategory == Airbase.Category.AIRDROME then + spawnonairport=true + end + 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 + + -- Scan options. Might make that input somehow. + local scanradius=50 + local scanunits=true + local scanstatics=true + local scanscenery=false + local verysafe=false + + -- Number of free parking spots at the airbase. + if spawnonship or spawnonfarp or spawnonrunway then + -- These places work procedural and have some kind of build in queue ==> Less effort. + self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, spawnonship or spawnonfarp or spawnonrunway) + spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, spawnonship or spawnonfarp or spawnonrunway) + else + if ishelo then + if termtype==nil then + -- Helo is spawned. Try exclusive helo spots first. + self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + if nfree<#SpawnTemplate.units then + -- Not enough helo ports. Let's try also other terminal types. + self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- No terminal type specified. We try all spots except shelters. + self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- Fixed wing aircraft is spawned. + if termtype==nil then + --TODO: Add some default cases for transport, bombers etc. if no explicit terminal type is provided. + --TODO: We don't want Bombers to spawn in shelters. But I don't know a good attribute for just fighers. + --TODO: Some attributes are "Helicopters", "Bombers", "Transports", "Battleplanes". Need to check it out. + if isbomber or istransport then + -- First we fill the potentially bigger spots. + self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + if nfree<#SpawnTemplate.units then + -- Now we try the smaller ones. + self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + self:T(string.format("Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- Terminal type explicitly given. + self:T(string.format("Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring(termtype))) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + end + end + + -- Get parking data. + local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) + self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + for _,_spot in pairs(parkingdata) do + self:T2(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:T(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 spawing at an airbase. + 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 + if EmergencyAirSpawn and not self.SpawnUnControlled then + self:E(string.format("WARNING: 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.x=PointVec3.x+math.random(-500,500) + PointVec3.z=PointVec3.z+math.random(-500,500) + if ishelo then + PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + else + -- Randomize position so that multiple AC wont be spawned on top even in air. + PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + end + + Takeoff=GROUP.Takeoff.Air + else + self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + return nil + end + 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 + + -- Debug output. + 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 ) end end - - return GroupSpawned + + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. + if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + end + + return GroupSpawned end end return nil end - - --- Will spawn a group from a Vec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -1801,7 +2005,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..081106680 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -20,7 +20,7 @@ -- * When a unit is removed a new unit with a different flight plan is respawned. -- * Aircraft can report their status during the route. -- * All of the above can be customized by the user if necessary. --- * All current (Caucasus, Nevada, Normandy) and future maps are supported. +-- * All current (Caucasus, Nevada, Normandy, Persian Gulf) and future maps are supported. -- -- The RAT class creates an entry in the F10 radio menu which allows to -- @@ -62,7 +62,7 @@ -- @type RAT -- @field #string ClassName Name of the Class. -- @field #boolean Debug Turn debug messages on or off. --- @field Core.Group#GROUP templategroup Group serving as template for the RAT aircraft. +-- @field Wrapper.Group#GROUP templategroup Group serving as template for the RAT aircraft. -- @field #string alias Alias for spawned group. -- @field #boolean spawninitialized If RAT:Spawn() was already called this RAT object is set to true to prevent users to call it again. -- @field #number spawndelay Delay time in seconds before first spawning happens. @@ -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, ...). @@ -110,6 +111,8 @@ -- @field #number FLminuser Minimum flight level set by user. -- @field #number FLmaxuser Maximum flight level set by user. -- @field #boolean commute Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation. +-- @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. -- @field #boolean continuejourney Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. -- @field #number ngroups Number of groups to be spawned in total. -- @field #number alive Number of groups which are alive. @@ -128,7 +131,6 @@ -- @field #string livery Livery of the aircraft set by user. -- @field #string skill Skill of AI. -- @field #boolean ATCswitch Enable/disable ATC if set to true/false. --- @field #string parking_id String with a special parking ID for the aircraft. -- @field #boolean radio If true/false disables radio messages from the RAT groups. -- @field #number frequency Radio frequency used by the RAT groups. -- @field #string modulation Ratio modulation. Either "FM" or "AM". @@ -143,9 +145,14 @@ -- @field #string onboardnum Sets the onboard number prefix. Same as setting "TAIL #" in the mission editor. -- @field #number onboardnum0 (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 1. -- @field #boolean checkonrunway Aircraft are checked if they were accidentally spawned on the runway. Default is true. +-- @field #number onrunwayradius Distance (in meters) from a runway spawn point until a unit is considered to have accidentally been spawned on a runway. Default is 75 m. +-- @field #number onrunwaymaxretry Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. Default is 3. -- @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 ontopradius Radius in meters until which a unit is considered to be on top of another. Default is 2 m. +-- @field Wrapper.Airbase#AIRBASE.TerminalType termtype Type of terminal to be used when spawning at an airbase. +-- @field #number parkingscanradius Radius in meters until which parking spots are scanned for obstacles like other units, statics or scenery. +-- @field #boolean parkingscanscenery If true, area around parking spots is scanned for scenery objects. Default is false. +-- @field #boolean parkingverysafe If true, parking spots are considered as non-free until a possible aircraft has left and taken off. Default false. -- @extends Core.Spawn#SPAWN --- Implements an easy to use way to randomly fill your map with AI aircraft. @@ -302,6 +309,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, ...). @@ -342,6 +350,8 @@ RAT={ FLmaxuser=nil, -- Maximum flight level set by user. FLuser=nil, -- Flight level set by users explicitly. commute=false, -- Aircraft commute between departure and destination, i.e. when respawned the departure airport becomes the new destiation. + starshape=false, -- If true, aircraft travel A-->B-->A-->C-->A-->D... for commute. + homebase=nil, -- Home base for commute. continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. alive=0, -- Number of groups which are alive. ngroups=nil, -- Number of groups to be spawned in total. @@ -353,14 +363,13 @@ 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. livery=nil, -- Livery of the aircraft. skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. - parking_id=nil, -- Specific parking ID when aircraft are spawned at airports. radio=nil, -- If true/false disables radio messages from the RAT groups. frequency=nil, -- Radio frequency used by the RAT groups. modulation=nil, -- Ratio modulation. Either "FM" or "AM". @@ -375,10 +384,15 @@ RAT={ activate_max=1, -- Max number of uncontrolle aircraft, which will be activated at a time. onboardnum=nil, -- Tail number. 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. + onrunwayradius=75, -- Distance from a runway spawn point until a unit is considered to have accidentally been spawned on a runway. + onrunwaymaxretry=3, -- Number of respawn retries (on ground) at other airports if a group gets accidentally spawned on the runway. + checkontop=false, -- Check whether aircraft have been spawned on top of another unit. + ontopradius=2, -- Radius in meters until which a unit is considered to be on top of another. + termtype=nil, -- Terminal type. + parkingscanradius=50, -- Scan radius. + parkingscanscenery=false, -- Scan parking spots for scenery obstacles. + parkingverysafe=false, -- Very safe option. } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -490,10 +504,6 @@ RAT.markerid=0 -- @field #string MenuF10 RAT.MenuF10=nil ---- RAT parking spots data base. --- @list parking -RAT.parking={} - --- Some ID to identify who we are in output of the DCS.log file. -- @field #string id RAT.id="RAT | " @@ -501,11 +511,10 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.2.2", + version = "2.3.0", print = true, } - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --TODO list: @@ -542,14 +551,15 @@ RAT.version={ --DONE: Find way to respawn aircraft at same position where the last was despawned for commute and journey. --TODO: Check that same alias is not given twice. Need to store previous ones and compare. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor New ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new RAT object. -- @param #RAT self -- @param #string groupname Name of the group as defined in the mission editor. This group is serving as a template for all spawned units. -- @param #string alias (Optional) Alias of the group. This is and optional parameter but must(!) be used if the same template group is used for more than one RAT object. --- @return #RAT Object of RAT class. --- @return #nil If the group does not exist in the mission editor. +-- @return #RAT Object of RAT class or nil if the group does not exist in the mission editor. -- @usage yak1:RAT("RAT_YAK") will create a RAT object called "yak1". The template group in the mission editor must have the name "RAT_YAK". -- @usage yak2:RAT("RAT_YAK", "Yak2") will create a RAT object "yak2". The template group in the mission editor must have the name "RAT_YAK" but the group will be called "Yak2" in e.g. the F10 menu. function RAT:New(groupname, alias) @@ -565,7 +575,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 +588,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() @@ -597,11 +610,14 @@ function RAT:New(groupname, alias) return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Spawn function ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Triggers the spawning of AI aircraft. Note that all additional options should be set before giving the spawn command. -- @param #RAT self -- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. +-- @return #boolean True if spawning was successful or nil if nothing was spawned. -- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton. function RAT:Spawn(naircraft) @@ -690,6 +706,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)) @@ -727,9 +744,8 @@ function RAT:Spawn(naircraft) text=text..string.format("Radio modulation : %s\n", tostring(self.frequency)) text=text..string.format("Tail # prefix : %s\n", tostring(self.onboardnum)) text=text..string.format("Check on runway: %s\n", tostring(self.checkonrunway)) + text=text..string.format("Max respawn attempts: %s\n", tostring(self.onrunwaymaxretry)) text=text..string.format("Check on top: %s\n", tostring(self.checkontop)) - text=text..string.format("Max respawn attempts: %s\n", tostring(self.rbug_maxretry)) - text=text..string.format("Parking DB: %s\n", tostring(self.useparkingdb)) text=text..string.format("Uncontrolled: %s\n", tostring(self.uncontrolled)) if self.uncontrolled and self.activate_uncontrolled then text=text..string.format("Uncontrolled max : %4.1f\n", self.activate_max) @@ -789,8 +805,12 @@ function RAT:Spawn(naircraft) if self.uncontrolled and self.activate_uncontrolled then SCHEDULER:New(nil, self._ActivateUncontrolled, {self}, self.activate_delay, self.activate_delta, self.activate_frand) end + + return true end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Consistency Check ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function checks consistency of user input and automatically adjusts parameters if necessary. @@ -814,12 +834,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 @@ -906,10 +926,15 @@ function RAT:_CheckConsistency() end end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Set the friendly coalitions from which the airports can be used as departure and destination. -- @param #RAT self -- @param #string friendly "same"=own coalition+neutral (default), "sameonly"=own coalition only, "neutral"=all neutral airports. -- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports. +-- @return #RAT RAT self object. -- @usage yak:SetCoalition("neutral") will spawn aircraft randomly on all neutral airports. -- @usage yak:SetCoalition("sameonly") will spawn aircraft randomly on airports belonging to the same coalition only as the template. function RAT:SetCoalition(friendly) @@ -921,11 +946,21 @@ function RAT:SetCoalition(friendly) else self.friendly=RAT.coal.same end + return self end --- Set coalition of RAT group. You can make red templates blue and vice versa. +-- Note that a country is also set automatically if it has not done before via RAT:SetCountry. +-- +-- * For blue, the country is set to USA. +-- * For red, the country is set to RUSSIA. +-- * For neutral, the country is set to SWITZERLAND. +-- +-- This is important, since it is ultimately the COUNTRY that determines the coalition of the aircraft. +-- You can set the country explicitly via the RAT:SetCountry() function if necessary. -- @param #RAT self --- @param #string color Color of coalition, i.e. "red" or blue". +-- @param #string color Color of coalition, i.e. "red" or blue" or "neutral". +-- @return #RAT RAT self object. function RAT:SetCoalitionAircraft(color) self:F2(color) if color:lower()=="blue" then @@ -940,21 +975,87 @@ function RAT:SetCoalitionAircraft(color) end elseif color:lower()=="neutral" then self.coalition=coalition.side.NEUTRAL + if not self.country then + self.country=country.id.SWITZERLAND + end end + return self end ---- Set country of RAT group. This overrules the coalition settings. +--- Set country of RAT group. +-- See https://wiki.hoggitworld.com/view/DCS_enum_country +-- +-- This overrules the coalition settings. So if you want your group to be of a specific coalition, you have to set a country that is part of that coalition. -- @param #RAT self --- @param #number id DCS country enumerator ID. For example country.id.USA or country.id.RUSSIA. +-- @param #DCS.country.id id DCS country enumerator ID. For example country.id.USA or country.id.RUSSIA. +-- @return #RAT RAT self object. function RAT:SetCountry(id) self:F2(id) self.country=id + return self +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 Wrapper.Airbase#AIRBASE.TerminalType termtype Type of terminal. Use enumerator AIRBASE.TerminalType.XXX. +-- @return #RAT RAT self object. +function RAT:SetTerminalType(termtype) + self:F2(termtype) + self.termtype=termtype + return self +end + +--- Set the scan radius around parking spots. Parking spot is considered to be occupied if any obstacle is found with the radius. +-- @param #RAT self +-- @param #number radius Radius in meters. Default 50 m. +-- @return #RAT RAT self object. +function RAT:SetParkingScanRadius(radius) + self:F2(radius) + self.parkingscanradius=radius or 50 + return self +end + +--- Enables scanning for scenery objects around parking spots which might block the spot. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetParkingScanSceneryON() + self:F2() + self.parkingscanscenery=true + return self +end + +--- Disables scanning for scenery objects around parking spots which might block the spot. This is also the default setting. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetParkingScanSceneryOFF() + self:F2() + self.parkingscanscenery=false + return self +end + +--- A parking spot is not free until a possible aircraft has left and taken off. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetParkingSpotSafeON() + self:F2() + self.parkingverysafe=true + return self +end + +--- A parking spot is free as soon as possible aircraft has left the place. This is the default. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetParkingSpotSafeOFF() + self:F2() + self.parkingverysafe=false + return self 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 -- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air". +-- @return #RAT RAT self object. -- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started. -- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off. -- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. @@ -975,11 +1076,54 @@ function RAT:SetTakeoff(type) end self.takeoff=_Type + + return self +end + +--- Set takeoff type cold. Aircraft will spawn at a parking spot with engines off. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetTakeoffCold() + self.takeoff=RAT.wp.cold + return self +end + +--- Set takeoff type to hot. Aircraft will spawn at a parking spot with engines on. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetTakeoffHot() + self.takeoff=RAT.wp.hot + return self +end + +--- Set takeoff type to runway. Aircraft will spawn directly on the runway. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetTakeoffRunway() + self.takeoff=RAT.wp.runway + return self +end + +--- Set takeoff type to cold or hot. Aircraft will spawn at a parking spot with 50:50 change of engines on or off. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetTakeoffColdOrHot() + self.takeoff=RAT.wp.coldorhot + return self +end + +--- Set takeoff type to air. Aircraft will spawn in the air. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:SetTakeoffAir() + self.takeoff=RAT.wp.air + return self end --- Set possible departure ports. This can be an airport or a zone defined in the mission editor. -- @param #RAT self -- @param #string departurenames Name or table of names of departure airports or zones. +-- @return #RAT RAT self object. -- @usage RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport. -- @usage RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport. -- @usage RAT:SetDeparture({"Zone A", "Gudauta"}) will spawn RAT aircraft in air randomly within Zone A, which has to be defined in the mission editor, or within a zone around Gudauta airport. Note that this also requires RAT:takeoff("air") to be set. @@ -1015,11 +1159,13 @@ function RAT:SetDeparture(departurenames) end + return self end --- Set name of destination airports or zones for the AI aircraft. -- @param #RAT self -- @param #string destinationnames Name of the destination airport or table of destination airports. +-- @return #RAT RAT self object. -- @usage RAT:SetDestination("Krymsk") makes all aircraft of this RAT oject fly to Krymsk airport. function RAT:SetDestination(destinationnames) self:F2(destinationnames) @@ -1053,10 +1199,12 @@ function RAT:SetDestination(destinationnames) end + return self end --- Destinations are treated as zones. Aircraft will not land but rather be despawned when they reach a random point in the zone. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:DestinationZone() self:F2() @@ -1065,20 +1213,25 @@ function RAT:DestinationZone() -- Landing type is "air" because we don't actually land at the airport. self.landing=RAT.wp.air + + return self end --- Aircraft will fly to a random point within a zone and then return to its departure airport or zone. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:ReturnZone() self:F2() -- Destination is a zone. Needs special care. self.returnzone=true + return self end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self -- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. +-- @return #RAT RAT self object. function RAT:SetDestinationsFromZone(zone) self:F2(zone) @@ -1087,11 +1240,14 @@ function RAT:SetDestinationsFromZone(zone) -- Set zone. self.destination_Azone=zone + + return self end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self -- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. +-- @return #RAT RAT self object. function RAT:SetDeparturesFromZone(zone) self:F2(zone) @@ -1100,25 +1256,32 @@ function RAT:SetDeparturesFromZone(zone) -- Set zone. self.departure_Azone=zone + + return self end --- Add all friendly airports to the list of possible departures. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:AddFriendlyAirportsToDepartures() self:F2() self.addfriendlydepartures=true + return self end --- Add all friendly airports to the list of possible destinations -- @param #RAT self +-- @return #RAT RAT self object. function RAT:AddFriendlyAirportsToDestinations() self:F2() self.addfriendlydestinations=true + return self end --- Airports, FARPs and ships explicitly excluded as departures and destinations. -- @param #RAT self -- @param #string ports Name or table of names of excluded airports. +-- @return #RAT RAT self object. function RAT:ExcludedAirports(ports) self:F2(ports) if type(ports)=="string" then @@ -1126,11 +1289,13 @@ function RAT:ExcludedAirports(ports) else self.excluded_ports=ports end + return self end --- Set skill of AI aircraft. Default is "High". -- @param #RAT self -- @param #string skill Skill, options are "Average", "Good", "High", "Excellent" and "Random". Parameter is case insensitive. +-- @return #RAT RAT self object. function RAT:SetAISkill(skill) self:F2(skill) if skill:lower()=="average" then @@ -1144,11 +1309,13 @@ function RAT:SetAISkill(skill) else self.skill="High" end + return self end --- Set livery of aircraft. If more than one livery is specified in a table, the actually used one is chosen randomly from the selection. -- @param #RAT self -- @param #table skins Name of livery or table of names of liveries. +-- @return #RAT RAT self object. function RAT:Livery(skins) self:F2(skins) if type(skins)=="string" then @@ -1156,222 +1323,285 @@ function RAT:Livery(skins) else self.livery=skins end + return self end --- Change aircraft type. This is a dirty hack which allows to change the aircraft type of the template group. -- Note that all parameters like cruise speed, climb rate, range etc are still taken from the template group which likely leads to strange behaviour. -- @param #RAT self -- @param #string actype Type of aircraft which is spawned independent of the template group. Use with care and expect problems! +-- @return #RAT RAT self object. function RAT:ChangeAircraft(actype) self:F2(actype) self.actype=actype + return self end --- Aircraft will continue their journey from their destination. This means they are respawned at their destination and get a new random destination. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:ContinueJourney() self:F2() self.continuejourney=true self.commute=false + return self 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... +-- @return #RAT RAT self object. +function RAT:Commute(starshape) self:F2() self.commute=true self.continuejourney=false + if starshape then + self.starshape=starshape + else + self.starshape=false + end + return self end --- Set the delay before first group is spawned. -- @param #RAT self -- @param #number delay Delay in seconds. Default is 5 seconds. Minimum delay is 0.5 seconds. +-- @return #RAT RAT self object. function RAT:SetSpawnDelay(delay) self:F2(delay) delay=delay or 5 self.spawndelay=math.max(0.5, delay) + return self end --- Set the interval between spawnings of the template group. -- @param #RAT self -- @param #number interval Interval in seconds. Default is 5 seconds. Minimum is 0.5 seconds. +-- @return #RAT RAT self object. function RAT:SetSpawnInterval(interval) self:F2(interval) interval=interval or 5 self.spawninterval=math.max(0.5, interval) + return self 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. +-- @return #RAT RAT self object. 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 + return self end --- Sets the delay between despawning and respawning aircraft. -- @param #RAT self -- @param #number delay Delay in seconds until respawn happens. Default is 1 second. Minimum is 1 second. +-- @return #RAT RAT self object. 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 + return self end --- Aircraft will not get respawned when they finished their route. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:NoRespawn() self:F2() self.norespawn=true + return self end --- Number of tries to respawn an aircraft in case it has accitentally been spawned on runway. -- @param #RAT self -- @param #number n Number of retries. Default is 3. +-- @return #RAT RAT self object. function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n) self:F2(n) n=n or 3 - self.rbug_maxretry=n + self.onrunwaymaxretry=n + return self end --- Aircraft will be respawned directly after take-off. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RespawnAfterTakeoff() self:F2() self.respawn_after_takeoff=true + return self end --- Aircraft will be respawned after they crashed or get shot down. This is the default behavior. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RespawnAfterCrashON() self:F2() self.respawn_after_crash=true + return self end --- Aircraft will not be respawned after they crashed or get shot down. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RespawnAfterCrashOFF() self:F2() self.respawn_after_crash=false + return self end --- If aircraft cannot be spawned on parking spots, it is allowed to spawn them in air above the same airport. Note that this is also the default behavior. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RespawnInAirAllowed() self:F2() self.respawn_inair=true + return self end --- If aircraft cannot be spawned on parking spots, it is NOT allowed to spawn them in air. This has only impact if aircraft are supposed to be spawned on the ground (and not in a zone). -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RespawnInAirNotAllowed() self:F2() self.respawn_inair=false + return self end --- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediatly. -- @param #RAT self -- @param #boolean switch If true, check is performed. If false, this check is omitted. -function RAT:CheckOnRunway(switch) +-- @param #number radius Distance in meters until a unit is considered to have spawned accidentally on the runway. Default is 75 m. +-- @return #RAT RAT self object. +function RAT:CheckOnRunway(switch, distance) self:F2(switch) if switch==nil then switch=true end self.checkonrunway=switch + self.onrunwayradius=distance or 75 + return self end --- Check if aircraft have accidentally been spawned on top of each other. If yes, they will be removed immediately. -- @param #RAT self -- @param #boolean switch If true, check is performed. If false, this check is omitted. -function RAT:CheckOnTop(switch) +-- @param #number radius Radius in meters until which a unit is considered to be on top of each other. Default is 2 m. +-- @return #RAT RAT self object. +function RAT:CheckOnTop(switch, radius) self:F2(switch) if switch==nil then switch=true end self.checkontop=switch + self.ontopradius=radius or 2 + return self end ---- Put parking spot coordinates in a data base for future use of aircraft. +--- Put parking spot coordinates in a data base for future use of aircraft. (Obsolete! API function will be removed soon.) -- @param #RAT self -- @param #boolean switch If true, parking spots are memorized. This is also the default setting. +-- @return #RAT RAT self object. function RAT:ParkingSpotDB(switch) - self:F2(switch) - if switch==nil then - switch=true - end - self.useparkingdb=switch -end - ---- Set parking id of aircraft. --- @param #RAT self --- @param #string id Parking ID of the aircraft. -function RAT:SetParkingID(id) - self:F2(id) - self.parking_id=id - self:T(RAT.id.."Setting parking ID to "..self.parking_id) + self:E("RAT ParkingSpotDB function is obsolete and will be removed soon!") + return self end --- Enable Radio. Overrules the ME setting. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RadioON() self:F2() self.radio=true + return self end --- Disable Radio. Overrules the ME setting. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RadioOFF() self:F2() self.radio=false + return self end --- Set radio frequency. -- @param #RAT self -- @param #number frequency Radio frequency. +-- @return #RAT RAT self object. function RAT:RadioFrequency(frequency) self:F2(frequency) self.frequency=frequency + return self end ---- Spawn aircraft in uncontrolled state. Aircraft will only sit at their parking spots. They can be activated randomly by the RAT:ActivateUncontrolled() function. +--- Set radio modulation. Default is AM. -- @param #RAT self -function RAT:Uncontrolled() - self:F2() - self.uncontrolled=true -end - ---- Aircraft are invisible. --- @param #RAT self -function RAT:Invisible() - self:F2() - self.invisible=true -end - ---- Aircraft are immortal. --- @param #RAT self -function RAT:Immortal() - self:F2() - self.immortal=true +-- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. +-- @return #RAT RAT self object. +function RAT:RadioModulation(modulation) + self:F2(modulation) + if modulation=="AM" then + self.modulation=radio.modulation.AM + elseif modulation=="FM" then + self.modulation=radio.modulation.FM + else + self.modulation=radio.modulation.AM + end + return self end --- Radio menu On. Default is off. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RadioMenuON() self:F2() self.f10menu=true + return self end --- Radio menu Off. This is the default setting. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:RadioMenuOFF() self:F2() self.f10menu=false + return self end +--- Aircraft are invisible. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:Invisible() + self:F2() + self.invisible=true + return self +end + +--- Aircraft are immortal. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:Immortal() + self:F2() + self.immortal=true + return self +end + +--- Spawn aircraft in uncontrolled state. Aircraft will only sit at their parking spots. They can be activated randomly by the RAT:ActivateUncontrolled() function. +-- @param #RAT self +-- @return #RAT RAT self object. +function RAT:Uncontrolled() + self:F2() + self.uncontrolled=true + return self +end --- Activate uncontrolled aircraft. -- @param #RAT self @@ -1379,6 +1609,7 @@ end -- @param #number delay Time delay in seconds before (first) aircraft is activated. Default is 1 second. -- @param #number delta Time difference in seconds before next aircraft is activated. Default is 1 second. -- @param #number frand Factor [0,...,1] for randomization of time difference between aircraft activations. Default is 0, i.e. no randomization. +-- @return #RAT RAT self object. function RAT:ActivateUncontrolled(maxactivated, delay, delta, frand) self:F2({max=maxactivated, delay=delay, delta=delta, rand=frand}) @@ -1397,66 +1628,63 @@ function RAT:ActivateUncontrolled(maxactivated, delay, delta, frand) -- Ensure frand is in [0,...,1] self.activate_frand=math.max(self.activate_frand,0) self.activate_frand=math.min(self.activate_frand,1) -end - ---- Set radio modulation. Default is AM. --- @param #RAT self --- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -function RAT:RadioModulation(modulation) - self:F2(modulation) - if modulation=="AM" then - self.modulation=radio.modulation.AM - elseif modulation=="FM" then - self.modulation=radio.modulation.FM - else - self.modulation=radio.modulation.AM - end + + return self end --- Set the time after which inactive groups will be destroyed. -- @param #RAT self -- @param #number time Time in seconds. Default is 600 seconds = 10 minutes. Minimum is 60 seconds. +-- @return #RAT RAT self object. function RAT:TimeDestroyInactive(time) self:F2(time) time=time or self.Tinactive time=math.max(time, 60) self.Tinactive=time + return self end --- Set the maximum cruise speed of the aircraft. -- @param #RAT self -- @param #number speed Speed in km/h. +-- @return #RAT RAT self object. function RAT:SetMaxCruiseSpeed(speed) self:F2(speed) -- Convert to m/s. self.Vcruisemax=speed/3.6 + return self end --- Set the climb rate. This automatically sets the climb angle. -- @param #RAT self -- @param #number rate Climb rate in ft/min. Default is 1500 ft/min. Minimum is 100 ft/min. Maximum is 15,000 ft/min. +-- @return #RAT RAT self object. function RAT:SetClimbRate(rate) self:F2(rate) rate=rate or self.Vclimb rate=math.max(rate, 100) rate=math.min(rate, 15000) self.Vclimb=rate + return self end --- Set the angle of descent. Default is 3.6 degrees, which corresponds to 3000 ft descent after one mile of travel. -- @param #RAT self -- @param #number angle Angle of descent in degrees. Minimum is 0.5 deg. Maximum 50 deg. +-- @return #RAT RAT self object. function RAT:SetDescentAngle(angle) self:F2(angle) angle=angle or self.AlphaDescent angle=math.max(angle, 0.5) angle=math.min(angle, 50) self.AlphaDescent=angle + return self end --- Set rules of engagement (ROE). Default is weapon hold. This is a peaceful class. -- @param #RAT self -- @param #string roe "hold" = weapon hold, "return" = return fire, "free" = weapons free. +-- @return #RAT RAT self object. function RAT:SetROE(roe) self:F2(roe) if roe=="return" then @@ -1466,11 +1694,13 @@ function RAT:SetROE(roe) else self.roe=RAT.ROE.weaponhold end + return self end --- Set reaction to threat (ROT). Default is no reaction, i.e. aircraft will simply ignore all enemies. -- @param #RAT self -- @param #string rot "noreaction" = no reaction to threats, "passive" = passive defence, "evade" = evade enemy attacks. +-- @return #RAT RAT self object. function RAT:SetROT(rot) self:F2(rot) if rot=="passive" then @@ -1480,184 +1710,225 @@ function RAT:SetROT(rot) else self.rot=RAT.ROT.noreaction end + return self end --- Set the name of the F10 submenu. Default is the name of the template group. -- @param #RAT self -- @param #string name Submenu name. +-- @return #RAT RAT self object. function RAT:MenuName(name) self:F2(name) self.SubMenuName=tostring(name) + return self end --- Enable ATC, which manages the landing queue for RAT aircraft if they arrive simultaniously at the same airport. -- @param #RAT self --- @param #boolean switch Enable ATC (true) or Disable ATC (false). No argument means ATC enabled. +-- @param #boolean switch Enable ATC (true) or Disable ATC (false). No argument means ATC enabled. +-- @return #RAT RAT self object. function RAT:EnableATC(switch) self:F2(switch) if switch==nil then switch=true end self.ATCswitch=switch + return self end --- Turn messages from ATC on or off. Default is on. This setting effects all RAT objects and groups! -- @param #RAT self --- @param #boolean switch Enable (true) or disable (false) messages from ATC. +-- @param #boolean switch Enable (true) or disable (false) messages from ATC. +-- @return #RAT RAT self object. function RAT:ATC_Messages(switch) self:F2(switch) if switch==nil then switch=true end RAT.ATC.messages=switch + return self end --- Max number of planes that get landing clearance of the RAT ATC. This setting effects all RAT objects and groups! -- @param #RAT self -- @param #number n Number of aircraft that are allowed to land simultaniously. Default is 2. +-- @return #RAT RAT self object. function RAT:ATC_Clearance(n) self:F2(n) RAT.ATC.Nclearance=n or 2 + return self end --- Delay between granting landing clearance for simultanious landings. This setting effects all RAT objects and groups! -- @param #RAT self -- @param #number time Delay time when the next aircraft will get landing clearance event if the previous one did not land yet. Default is 240 sec. +-- @return #RAT RAT self object. function RAT:ATC_Delay(time) self:F2(time) RAT.ATC.delay=time or 240 + return self end --- Set minimum distance between departure and destination. Default is 5 km. -- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different. -- @param #RAT self -- @param #number dist Distance in km. +-- @return #RAT RAT self object. function RAT:SetMinDistance(dist) self:F2(dist) -- Distance in meters. Absolute minimum is 500 m. self.mindist=math.max(500, dist*1000) + return self end --- Set maximum distance between departure and destination. Default is 5000 km but aircarft range is also taken into account automatically. -- @param #RAT self -- @param #number dist Distance in km. +-- @return #RAT RAT self object. function RAT:SetMaxDistance(dist) self:F2(dist) -- Distance in meters. self.maxdist=dist*1000 + return self end --- Turn debug messages on or off. Default is off. -- @param #RAT self -- @param #boolean switch Turn debug on=true or off=false. No argument means on. +-- @return #RAT RAT self object. function RAT:_Debug(switch) self:F2(switch) if switch==nil then switch=true end self.Debug=switch + return self end --- Enable debug mode. More output in dcs.log file and onscreen messages to all. -- @param #RAT self +-- @return #RAT RAT self object. function RAT:Debugmode() self:F2() self.Debug=true + return self end --- Aircraft report status update messages along the route. -- @param #RAT self -- @param #boolean switch Swtich reports on (true) or off (false). No argument is on. +-- @return #RAT RAT self object. function RAT:StatusReports(switch) self:F2(switch) if switch==nil then switch=true end self.reportstatus=switch + return self end --- Place markers of waypoints on the F10 map. Default is off. -- @param #RAT self -- @param #boolean switch true=yes, false=no. +-- @return #RAT RAT self object. function RAT:PlaceMarkers(switch) self:F2(switch) if switch==nil then switch=true end self.placemarkers=switch + return self end --- Set flight level. Setting this value will overrule all other logic. Aircraft will try to fly at this height regardless. -- @param #RAT self -- @param #number FL Fight Level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +-- @return #RAT RAT self object. function RAT:SetFL(FL) self:F2(FL) FL=FL or self.FLcruise FL=math.max(FL,0) self.FLuser=FL*RAT.unit.FL2m + return self end --- Set max flight level. Setting this value will overrule all other logic. Aircraft will try to fly at less than this FL regardless. -- @param #RAT self -- @param #number FL Maximum Fight Level in hundrets of feet. +-- @return #RAT RAT self object. function RAT:SetFLmax(FL) self:F2(FL) self.FLmaxuser=FL*RAT.unit.FL2m + return self end --- Set max cruising altitude above sea level. -- @param #RAT self -- @param #number alt Altitude ASL in meters. +-- @return #RAT RAT self object. function RAT:SetMaxCruiseAltitude(alt) self:F2(alt) self.FLmaxuser=alt + return self end --- Set min flight level. Setting this value will overrule all other logic. Aircraft will try to fly at higher than this FL regardless. -- @param #RAT self -- @param #number FL Maximum Fight Level in hundrets of feet. +-- @return #RAT RAT self object. function RAT:SetFLmin(FL) self:F2(FL) self.FLminuser=FL*RAT.unit.FL2m + return self end --- Set min cruising altitude above sea level. -- @param #RAT self -- @param #number alt Altitude ASL in meters. +-- @return #RAT RAT self object. function RAT:SetMinCruiseAltitude(alt) self:F2(alt) self.FLminuser=alt + return self end --- Set flight level of cruising part. This is still be checked for consitancy with selected route and prone to radomization. -- Default is FL200 for planes and FL005 for helicopters. -- @param #RAT self -- @param #number FL Flight level in hundrets of feet. E.g. FL200 = 20000 ft ASL. +-- @return #RAT RAT self object. function RAT:SetFLcruise(FL) self:F2(FL) self.FLcruise=FL*RAT.unit.FL2m + return self end --- Set cruising altitude. This is still be checked for consitancy with selected route and prone to radomization. -- @param #RAT self -- @param #number alt Cruising altitude ASL in meters. +-- @return #RAT RAT self object. function RAT:SetCruiseAltitude(alt) self:F2(alt) self.FLcruise=alt + return self end --- Set onboard number prefix. Same as setting "TAIL #" in the mission editor. Note that if you dont use this function, the values defined in the template group of the ME are taken. -- @param #RAT self -- @param #string tailnumprefix String of the tail number prefix. If flight consists of more than one aircraft, two digits are appended automatically, i.e. 001, 002, ... -- @param #number zero (Optional) Starting value of the automatically appended numbering of aircraft within a flight. Default is 0. +-- @return #RAT RAT self object. function RAT:SetOnboardNum(tailnumprefix, zero) self:F2({tailnumprefix=tailnumprefix, zero=zero}) self.onboardnum=tailnumprefix if zero ~= nil then self.onboardnum0=zero end + return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Initialize basic parameters of the aircraft based on its (template) group in the mission editor. @@ -1783,12 +2054,6 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live if not (departure and destination and waypoints) then return nil end - - -- 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 -- Set (another) livery. local livery @@ -1805,7 +2070,10 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live end -- Modify the spawn template to follow the flight plan. - self:_ModifySpawnTemplate(waypoints, livery, _spawnpos) + local successful=self:_ModifySpawnTemplate(waypoints, livery, _lastpos, departure, takeoff) + if not successful then + return nil + end -- Actually spawn the group. local group=self:SpawnWithIndex(self.SpawnIndex) -- Wrapper.Group#GROUP @@ -1887,12 +2155,6 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live -- Number of preformed spawn attempts for this group. 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 -- Create submenu for this group. if self.f10menu then @@ -1930,11 +2192,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 +2207,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 +2268,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 +2345,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 +2366,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 +2455,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 +3323,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 +3439,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 +3454,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 +3487,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 +3537,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 +3546,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. @@ -3289,25 +3614,26 @@ function RAT:_OnBirth(EventData) local _landing=self.ratcraft[i].landing local _livery=self.ratcraft[i].livery + -- Some is only useful for an actual airbase (not a zone). + local _airbase=AIRBASE:FindByName(_departure) + -- Check if aircraft group was accidentally spawned on the runway. -- This can happen due to no parking slots available and other DCS bugs. local onrunway=false - if _takeoff ~= RAT.wp.runway and self.checkonrunway then - for _,unit in pairs(SpawnGroup:GetUnits()) do - local _onrunway=self:_CheckOnRunway(unit, _departure) - if _onrunway then - onrunway=true - end - end - end + if _airbase then + -- Check that we did not want to spawn at a runway or in air. + if self.checkonrunway and _takeoff ~= RAT.wp.runway and _takeoff ~= RAT.wp.air then + onrunway=_airbase:CheckOnRunWay(SpawnGroup, self.onrunwayradius, false) + end + end -- Workaround if group was spawned on runway. if onrunway then -- Error message. - local text=string.format("ERROR: RAT group of %s was spawned on runway (DCS bug). Group #%d will be despawned immediately!", self.alias, i) + local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!", self.alias, i) MESSAGE:New(text,30):ToAllIf(self.Debug) - self:T(RAT.id..text) + self:E(RAT.id..text) if self.Debug then SpawnGroup:FlareRed() end @@ -3316,12 +3642,12 @@ function RAT:_OnBirth(EventData) self:_Despawn(SpawnGroup) -- Try to respawn the group if there is at least another airport or random airport selection is used. - if (self.Ndeparture_Airports>=2 or self.random_departure) and _nrespawn=2 or self.random_departure) and _nrespawn 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 +3882,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 +3991,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 +4031,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 +4072,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 +4110,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 @@ -4281,125 +4622,6 @@ function RAT:_CommandImmortal(group, switch) group:SetCommand(SetImmortal) end ---- Adds a parking spot at an airport when it has been used by a spawned RAT aircraft to the RAT parking data base. --- This is mainly to circumvent (not perfectly) the DCS parking spot bug. --- @param #RAT self --- @param Wrapper.Airbase#AIRBASE airbase Airbase at which we add a parking spot. --- @param Wrapper.Group#GROUP group Group to check for parking positions. -function RAT:_AddParkingSpot(airbase, group) - - -- Name of the airport. - local airport=airbase:GetName() - - -- Radius of the parking spot in meters. - local spotradius=15 - - -- Init array for this airport if it does not exist. - if not RAT.parking[airport] then - RAT.parking[airport]={} - end - - -- Debug info. - self:T(RAT.id..string.format("Searching parking spot at airport %s. Number of currently known = %d.", airport, #RAT.parking[airport])) - - -- Loop over all units in the group. - for _,_unit in pairs(group:GetUnits()) do - - -- Check if unit is on the runway. - local onrunway=self:_CheckOnRunway(_unit, airport) - - if not onrunway then - - -- Coordinate of unit. - local coord=_unit:GetCoordinate() - - -- Loop over all known spots. - local gotit=false - for _,_spot in pairs(RAT.parking[airport]) do - local _spot=_spot -- Core.Point#COORDINATE - local _dist=_spot:Get2DDistance(coord) - - -- Check if this spot is already known. - if _dist < spotradius then - gotit=true - break - end - end - - -- Add spot spot if it not known already. - if not gotit then - table.insert(RAT.parking[airport], coord) - -- Place marker for debugging. - if self.Debug then - coord:MarkToAll(string.format("%s parking spot #%d", airport, #RAT.parking[airport])) - end - end - end - end -end - ---- Seach an unoccupied parking spot at a specific airport in the RAT parking data base. --- @param #RAT self --- @param Wrapper.Airbase#AIRBASE airbase The airbase where we want to find a parking position. --- @return Core.Point#COORDINATE Coordinate of the parking spot. -function RAT:_FindParkingSpot(airbase) - - -- Get airport name. - local airport=airbase:GetName() - - self:T(RAT.id..string.format("Checking spawn position DB for airport %s.", airport)) - - if RAT.parking[airport] then - self:T(RAT.id..string.format("Number of parking spots in DB for %s: %d", airport, #RAT.parking[airport])) - - local parkingspot --Core.Point#COORDINATE - - -- Loop over all known parking spots - for _i,spawnplace in pairs(RAT.parking[airport]) do - - -- Loop over ALL units present. - local occupied=false - for _,unit in pairs(_DATABASE.UNITS) do - local unit=unit --Wrapper.Unit#UNIT - if unit then - local _upos=unit:GetCoordinate() --Core.Point#COORDINATE - if _upos then - local _dist=_upos:Get2DDistance(spawnplace) - - -- We need two times the size of the aircraft to be "safe" and not spawn on top of each other. - local safe=_dist > self.aircraft.box*2 - -- Or (if possible) even better to take our and the other object's size (plus 10% safety margin) - local size=self:_GetObjectSize(unit) - if size then - safe=_dist > (self.aircraft.box+size)*1.1 - end - self:T2(RAT.id..string.format("RAT aircraft size = %.1f m, other object size = %.1f m", self.aircraft.box, size or 0)) - if not safe then - occupied=true - end - self:T2(RAT.id..string.format("Unit %s to parking spot %d: distance = %.1f m (occupied = %s).", unit:GetName(), _i, _dist, tostring(safe))) - end - end - end - - if occupied then - self:T(RAT.id..string.format("Parking spot #%d occupied at %s.", _i, airport)) - else - parkingspot=spawnplace - self:T(RAT.id..string.format("Found free parking spot in DB at airport %s.", airport)) - break - end - - end - - return parkingspot - else - self:T2(RAT.id..string.format("No parking position in DB yet for %s.", airport)) - end - - self:T(RAT.id..string.format("No free parking position found in DB at airport %s.", airport)) - return nil -end --- Get aircraft dimensions length, width, height. -- @param #RAT self @@ -4410,9 +4632,9 @@ function RAT:_GetObjectSize(unit) if DCSunit then local DCSdesc=DCSunit:getDesc() -- dimensions - local length=DCSdesc.box.max.x - local height=DCSdesc.box.max.y - local width=DCSdesc.box.max.z + local length=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) + local height=DCSdesc.box.max.y+math.abs(DCSdesc.box.max.y) + local width =DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) return math.max(length,width) end return nil @@ -4421,11 +4643,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 +4670,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) + if (not uniti:InAir()) and (not unitj:InAir()) then + -- 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 @@ -4471,67 +4691,6 @@ function RAT:_CheckOnTop(group) return false end ---- Function to check whether an aircraft is on the runway. --- @param #RAT self --- @param Wrapper.Unit#UNIT unit The unit to be checked. --- @param #string airport The name of the airport we want to check. --- @return #boolean True if aircraft is on the runway and on the ground. -function RAT:_CheckOnRunway(unit, airport) - - -- We use the tabulated points in the ATC_GROUND classes to find out if the group is on the runway. - -- Note that land.SurfaceType.RUNWAY also is true for the parking areas etc. Hence, not useful. - -- This is useful to check if an aircraft was accidentally spawned on the runway due to missing parking spots. - --BASE:E(ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways) - - -- Table holding the points around the runway. - local pointsrwy={} - - -- Loop over all airports on Caucaus map. - for id,name in pairs(AIRBASE.Caucasus) do - if name==airport then - --pointsrwy=ATC_GROUND_CAUCASUS.Airbases[AIRBASE.Caucasus.Batumi].PointsRunways - pointsrwy=ATC_GROUND_CAUCASUS.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - -- Loop over all airports on NTTR map. - for id,name in pairs(AIRBASE.Nevada) do - if name==airport then - pointsrwy=ATC_GROUND_NEVADA.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - -- Loop over all airports on Normandy map. - for id,name in pairs(AIRBASE.Normandy) do - if name==airport then - pointsrwy=ATC_GROUND_NORMANDY.Airbases[name].PointsRunways - self:T2({name=name, points=pointsrwy}) - end - end - - -- Assume we are not on the runway. - local onrunway=false - - -- Loop over all runways. Some airports have more than one. - for PointsRunwayID, PointsRunway in pairs(pointsrwy) do - -- Create zone around runway. - local runway = ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID, PointsRunway) - - -- Check if unit is in on the runway. - if runway:IsVec3InZone(unit:GetVec3()) then - onrunway=true - end - end - - -- Check that aircraft is on ground. - onrunway=onrunway and unit:InAir()==false - - -- Debug - self:T(RAT.id..string.format("Check on runway of %s airport for unit %s = %s", airport, unit:GetName(),tostring(onrunway))) - - return onrunway -end - --- Calculate minimum distance between departure and destination for given minimum flight level and climb/decent rates. -- @param #RAT self @@ -4806,8 +4965,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 +4972,54 @@ 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) - self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace}) +-- @param Wrapper.Airbase#AIRBASE departure Departure airbase or zone. +-- @param #number takeoff Takeoff type. +-- @return #boolean True if modification was successful or nil if not, e.g. when no parking space was found and spawn in air is disabled. +function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, takeoff) + self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace, departure=departure, takeoff=takeoff}) -- 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 + + -- Template group and unit. + local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) + local TemplateUnit=TemplateGroup:GetUnit(1) + -- 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 + local AirbaseCategory = departure:GetDesc().category + if AirbaseCategory == Airbase.Category.SHIP then + spawnonship=true + elseif AirbaseCategory == Airbase.Category.HELIPAD then + spawnonfarp=true + elseif AirbaseCategory == Airbase.Category.AIRDROME then + spawnonairport=true + end + 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 +5030,198 @@ 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. Can also be nil. + local termtype=self.termtype + if spawnonrunway then + termtype=AIRBASE.TerminalType.Runway + end + + -- Scan options. Might make that input somehow. + local scanradius=self.parkingscanradius + local scanunits=true + local scanstatics=true + local scanscenery=self.parkingscanscenery + local verysafe=self.parkingverysafe + + -- Get free parking spots depending on where we spawn. + if spawnonship or spawnonfarp or spawnonrunway then + -- These places work procedural and have some kind of build in queue ==> Less effort. + self:T(RAT.id..string.format("Group %s is spawned on farp/ship/runway %s.", self.alias, departure:GetName())) + nfree=departure:GetFreeParkingSpotsNumber(termtype, spawnonship or spawnonfarp or spawnonrunway) + spots=departure:GetFreeParkingSpotsTable(termtype, spawnonship or spawnonfarp or spawnonrunway) + else + -- Helo is spawned. + if self.category==RAT.cat.heli then + if termtype==nil then + -- Try exclusive helo spots first. + self:T(RAT.id..string.format("Helo group %s is spawned at %s using terminal type %d.", self.alias, departure:GetName(), AIRBASE.TerminalType.HelicopterOnly)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + if nfree<#SpawnTemplate.units then + -- Not enough helo ports. Let's try also other terminal types. + self:T(RAT.id..string.format("Helo group %s is spawned at %s using terminal type %d.", self.alias, departure:GetName(), AIRBASE.TerminalType.HelicopterOnly)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- Terminal type specified explicitly. + self:T(RAT.id..string.format("Helo group %s is at %s using terminal type %d.", self.alias, departure:GetName(), termtype)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- Fixed wing aircraft is spawned. + if termtype==nil then + --TODO: Add some default cases for transport, bombers etc. if no explicit terminal type is provided. + --TODO: We don't want Bombers to spawn in shelters. But I don't know a good attribute for just fighers. + --TODO: Some attributes are "Helicopters", "Bombers", "Transports", "Battleplanes". Need to check it out. + local bomber=TemplateUnit:HasAttribute("Bombers") + local transport=TemplateUnit:HasAttribute("Transports") + if bomber or transport then + -- First we fill the potentially bigger spots. + self:T(RAT.id..string.format("Transport/bomber group %s is spawned at %s using terminal type %d.", self.alias, departure:GetName(), AIRBASE.TerminalType.OpenBig)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + if nfree<#SpawnTemplate.units then + -- Now we try the smaller ones. + self:T(RAT.id..string.format("Transport/bomber group %s is at %s using terminal type %d.", self.alias, departure:GetName(), AIRBASE.TerminalType.OpenMed)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMed, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + self:T(RAT.id..string.format("Fighter group %s is spawned at %s using terminal type %d.", self.alias, departure:GetName(), AIRBASE.TerminalType.FighterAircraft)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + else + -- Terminal type explicitly given. + self:T(RAT.id..string.format("Plane group %s is spawned at %s using terminal type %d.", self.alias, departure:GetName(), termtype)) + spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe) + nfree=#spots + end + end + end + + -- Get parking data (just for debugging). + local parkingdata=departure:GetParkingSpotsTable(termtype) + self:T2(RAT.id..string.format("Parking at %s, terminal type %s:", departure:GetName(), tostring(termtype))) + for _,_spot in pairs(parkingdata) do + self:T2(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:T(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 + + else + if self.respawn_inair and not self.uncontrolled then + self:E(RAT.id..string.format("WARNING: 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 and randomize position and altitude of the spawn point. + PointVec3.x=PointVec3.x+math.random(-500,500) + PointVec3.z=PointVec3.z+math.random(-500,500) + if self.category==RAT.cat.heli then + PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + else + PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + end + else + self:E(RAT.id..string.format("WARNING: RAT group %s has no parking spots at %s. Air start deactivated or uncontrolled AC!", self.alias, departure:GetName())) + return nil + end + 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) - -- 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 self.Debug then - local unitspawn=COORDINATE:New(TX,PointVec3.y,TY) - unitspawn:MarkToAll(string.format("Spawnplace unit #%d", UnitID)) + + 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 + 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 + + -- Debug info. + 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 +5249,6 @@ 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) end -- Copy waypoints into spawntemplate. By this we avoid the nasty DCS "landing bug" :) @@ -4926,6 +5277,8 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace) self:T(SpawnTemplate) end end + + return true end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5310,6 +5663,7 @@ end -- @param #RATMANAGER self -- @param #RAT ratobject RAT object to be managed. -- @param #number min Minimum number of groups for this RAT object. Default is 1. +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:Add(ratobject,min) --Automatic respawning is disabled. @@ -5329,11 +5683,14 @@ function RATMANAGER:Add(ratobject,min) -- Call spawn to initialize RAT parameters. ratobject:Spawn(0) + + return self end --- Starts the RAT manager and spawns the initial random number RAT groups for each RAT object. -- @param #RATMANAGER self -- @param #number delay Time delay in seconds after which the RAT manager is started. Default is 5 seconds. +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:Start(delay) -- Time delay. @@ -5350,10 +5707,13 @@ function RATMANAGER:Start(delay) -- Start scheduler. SCHEDULER:New(nil, self._Start, {self}, delay) + + return self end --- Instantly starts the RAT manager and spawns the initial random number RAT groups for each RAT object. -- @param #RATMANAGER self +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:_Start() -- Ensure that ntot is at least sum of min RAT groups. @@ -5384,29 +5744,36 @@ function RATMANAGER:_Start() local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s.", self.managerid) self:E(text) + return self end --- Stops the RAT manager. -- @param #RATMANAGER self -- @param #number delay Delay in seconds before the manager is stopped. Default is 1 second. +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:Stop(delay) delay=delay or 1 self:E(string.format(RATMANAGER.id.."Manager will be stopped in %d seconds.", delay)) SCHEDULER:New(nil, self._Stop, {self}, delay) + return self end --- Instantly stops the RAT manager by terminating its scheduler. -- @param #RATMANAGER self +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:_Stop() self:E(string.format(RATMANAGER.id.."Stopping manager with scheduler ID %s.", self.managerid)) self.manager:Stop(self.managerid) + return self end --- Sets the time interval between checks of alive RAT groups. Default is 30 seconds. -- @param #RATMANAGER self -- @param #number dt Time interval in seconds. +-- @return #RATMANAGER RATMANAGER self object. function RATMANAGER:SetTcheck(dt) self.Tcheck=dt or 30 + return self end --- Manager function. Calculating the number of current groups and respawning new groups if necessary. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5dab11d52..3ac8ca74e 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -495,4 +495,100 @@ function UTILS.BeaufortScale(speed) bd="Hurricane" end return bn,bd +end + +--- Split string at seperators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua +-- @param #string str Sting to split. +-- @param #string sep Speparator for split. +-- @return #table Split text. +function UTILS.Split(str, sep) + local result = {} + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) + end + return result +end + +--- Convert time in seconds to hours, minutes and seconds. +-- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. +-- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D). +function UTILS.SecondsToClock(seconds) + + -- Nil check. + if seconds==nil then + return nil + end + + -- Seconds + local seconds = tonumber(seconds) + + -- Seconds of this day. + local _seconds=seconds%(60*60*24) + + if seconds <= 0 then + return nil + else + local hours = string.format("%02.f", math.floor(_seconds/3600)) + local mins = string.format("%02.f", math.floor(_seconds/60 - (hours*60))) + local secs = string.format("%02.f", math.floor(_seconds - hours*3600 - mins *60)) + local days = string.format("%d", seconds/(60*60*24)) + return hours..":"..mins..":"..secs.."+"..days + end +end + +--- Convert clock time from hours, minutes and seconds to seconds. +-- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. +-- @param #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. +function UTILS.ClockToSeconds(clock) + + -- Nil check. + if clock==nil then + return nil + end + + -- Seconds init. + local seconds=0 + + -- Split additional days. + local dsplit=UTILS.split(clock, "+") + + -- Convert days to seconds. + if #dsplit>1 then + seconds=seconds+tonumber(dsplit[2])*60*60*24 + end + + -- Split hours, minutes, seconds + local tsplit=UTILS.Split(dsplit[1], ":") + + -- Get time in seconds + local i=1 + for _,time in ipairs(tsplit) do + if i==1 then + -- Hours + seconds=seconds+tonumber(time)*60*60 + elseif i==2 then + -- Minutes + seconds=seconds+tonumber(time)*60 + elseif i==3 then + -- Seconds + seconds=seconds+tonumber(time) + end + i=i+1 + end + + return seconds +end + +--- Display clock and mission time on screen as a message to all. +-- @param #number duration Duration in seconds how long the time is displayed. Default is 5 seconds. +function UTILS.DisplayMissionTime(duration) + duration=duration or 5 + local Tnow=timer.getAbsTime() + local mission_time=Tnow-timer.getTime0() + local mission_time_minutes=mission_time/60 + local mission_time_seconds=mission_time%60 + local local_time=UTILS.SecondsToClock(Tnow) + local text=string.format("Time: %s - %02d:%02d", local_time, mission_time_minutes, mission_time_seconds) + MESSAGE:New(text, duration):ToAll() end \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index d31779479..0b4663bb5 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -106,10 +106,8 @@ AIRBASE.Caucasus = { ["Mozdok"] = "Mozdok", ["Beslan"] = "Beslan", } - ---- @field Nevada --- --- These are all airbases of Nevada: + +--- These are all airbases of Nevada: -- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB @@ -129,7 +127,7 @@ AIRBASE.Caucasus = { -- * 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", @@ -151,9 +149,7 @@ AIRBASE.Nevada = { ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", } ---- @field Normandy --- --- These are all airbases of Normandy: +--- These are all airbases of Normandy: -- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles @@ -186,6 +182,7 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Ford +-- @field Normandy AIRBASE.Normandy = { ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", ["Lignerolles"] = "Lignerolles", @@ -220,6 +217,70 @@ AIRBASE.Normandy = { ["Ford"] = "Ford", } +--- 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 +-- @field PersianGulf +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", + } + +--- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking +-- +-- Supported types are: +-- +-- * AIRBASE.TerminalType.Runway: Valid spawn points on runway. +-- * AIRBASE.TerminalType.HelicopterOnly: Special spots for Helicopers. +-- * AIRBASE.TerminalType.Shelter: Hardened Air Shelter. Currently only on Caucaus map. +-- * AIRBASE.TerminalType.OpenMed: Open/Shelter air airplane only. +-- * AIRBASE.TerminalType.OpenBig: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. +-- * AIRBASE.TerminalType.OpenMedOrBig: Combines OpenMed and OpenBig spots. +-- * AIRBASE.TerminalType.HelicopterUnsable: Combines HelicopterOnly, OpenMed and OpenBig. +-- * AIRBASE.TerminalType.FighterAircraft: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. +-- @field TerminalType +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. @@ -257,6 +318,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 +338,548 @@ 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 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) + 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 + 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 + + 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. +-- @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) + + -- 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 + + -- Get the size of an object. + local function _GetObjectSize(unit,mooseobject) + if mooseobject then + unit=unit:GetDCSObject() + end + if unit and unit:isExist() then + local DCSdesc=unit:getDesc() + if DCSdesc.box then + local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) + local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height + local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) + return math.max(x,z), x , y, z + end + end + return 0,0,0,0 + end + + -- Function calculating the overlap of two (square) objects. + local function _overlap(mooseobject, dcsobject, dist) + local l1=_GetObjectSize(mooseobject, true) + local l2=_GetObjectSize(dcsobject) + local safedist=(l1/2+l2/2)*1.1 + local safe = (dist > safedist) + self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) + return safe + 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. + local parkingdata=self:GetParkingSpotsTable(terminaltype) + + -- Get the aircraft size, i.e. it's longest side of x,z. + local aircraft=group:GetUnit(1) + local nspots=group:GetSize() + local _aircraftsize, ax,ay,az=_GetObjectSize(aircraft, true) + + -- Debug info. + self:E(string.format("Looking for %d parking spot(s) at %s for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", nspots, airport, _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 + + -- 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 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).", airport, parkingspot.TerminalID)) + + 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 _vec3=unit:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _dist=_coord:Get2DDistance(_spot) + local _safe=_overlap(aircraft, unit, _dist) + + if markobstacles then + local l,x,y,z=_GetObjectSize(unit) + _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 _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=_GetObjectSize(static) + _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 _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=_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 + + --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) + if occupied then + self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) + else + self:E(string.format("%s: Parking spot id %d free.", airport, _termid)) + if nvalid=nspots then + return validspots + end + 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 + +--- 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 \ No newline at end of file