diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 7b44c14fb..7e57c24d0 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -345,7 +345,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) AIPatrol:OptionROEReturnFire() AIPatrol:OptionROTEvadeFire() - AIPatrol:Route( PatrolRoute, 0.5 ) + AIPatrol:Route( PatrolRoute, 0.5) end end diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index 736dbcf98..efd48c397 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -74,21 +74,22 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit if DefenderGroup:IsAlive() then + local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) + local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) + -- Determine the distance to the target. -- If it is less than 10km, then attack without a route. -- Otherwise perform a route attack. local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) local EngageRoute = {} - - local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) --- Calculate the target route point. @@ -96,58 +97,66 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + EngageSpeed, true ) EngageRoute[#EngageRoute+1] = FromWP - local ToCoord = self.AttackSetUnit:GetFirst():GetCoordinate() - self:SetTargetDistance( ToCoord ) -- For RTB status check + self:SetTargetDistance( TargetCoord ) -- For RTB status check - local FromEngageAngle = ToCoord:GetAngleDegrees( ToCoord:GetDirectionVec3( DefenderCoord ) ) + local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) ) + local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) --- Create a route point of type air. - local ToWP = ToCoord:Translate( 10000, FromEngageAngle ):WaypointAir( + local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle ):WaypointAir( self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + EngageSpeed, true ) - self:F( { Angle = FromEngageAngle, ToTargetSpeed = ToTargetSpeed } ) - self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } ) - EngageRoute[#EngageRoute+1] = ToWP local AttackTasks = {} + + self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1 - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) + + local AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex] + + if not AttackUnit then + self.AttackSetUnit.AttackIndex = 1 + AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex] + end + + if AttackUnit then if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "Engage Unit evaluation:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsGround() } ) - self:T( { "Eliminating Unit:", AttackUnit:GetName() } ) - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) + self:T( { "BAI Unit:", AttackUnit:GetName() } ) + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude ) end end if #AttackTasks == 0 then self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) else DefenderGroup:OptionROEOpenFire() DefenderGroup:OptionROTEvadeFire() + DefenderGroup:OptionKeepWeaponsOnThreat() AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self ) EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) end - DefenderGroup:Route( EngageRoute, 0.5 ) + DefenderGroup:Route( EngageRoute, self.TaskDelay ) end else self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) end end diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index 8cc974b7a..8d483f317 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -74,21 +74,18 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit if DefenderGroup:IsAlive() then - -- Determine the distance to the target. - -- If it is less than 10km, then attack without a route. - -- Otherwise perform a route attack. + local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) + local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) local EngageRoute = {} - - local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) --- Calculate the target route point. @@ -96,7 +93,7 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + EngageSpeed, true ) @@ -105,50 +102,58 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit self:SetTargetDistance( TargetCoord ) -- For RTB status check local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) --- Create a route point of type air. - local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle ):WaypointAir( + local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + EngageSpeed, true ) - self:F( { Angle = FromEngageAngle, ToTargetSpeed = ToTargetSpeed } ) - self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } ) - EngageRoute[#EngageRoute+1] = ToWP local AttackTasks = {} + + self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1 - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) + + local AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex] + + if not AttackUnit then + self.AttackSetUnit.AttackIndex = 1 + AttackUnit = AttackSetUnitPerThreatLevel[self.AttackSetUnit.AttackIndex] + end + + if AttackUnit then if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "Eliminating Unit:", AttackUnit:GetName() } ) - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) + self:F( { "CAS Unit:", AttackUnit:GetName() } ) + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude ) end end if #AttackTasks == 0 then self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) else DefenderGroup:OptionROEOpenFire() DefenderGroup:OptionROTEvadeFire() + DefenderGroup:OptionKeepWeaponsOnThreat() AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self ) EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) end - DefenderGroup:Route( EngageRoute, 0.5 ) + DefenderGroup:Route( EngageRoute, self.TaskDelay ) end else self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) end end diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 34988ce7d..94beddcbc 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -305,7 +305,7 @@ do -- AI_A2G_DISPATCHER -- -- An reconnaissance network, is used to detect enemy ground targets, potentially group them into areas, and to understand the position, level of threat of the enemy. -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia5.JPG) + -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\Dia5.JPG) -- -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. @@ -493,9 +493,9 @@ do -- AI_A2G_DISPATCHER -- -- You need to configure each squadron which task types you want it to perform. Read on ... -- - -- ### 3.2. Squadrons enemy ground target **Engagement**. + -- ### 3.2. Squadrons enemy ground target **engagement types**. -- - -- There are two ways how targets can be engaged: directly upon call from the airfield, farp or carrier, or through a patrol. + -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. -- -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required @@ -505,7 +505,7 @@ do -- AI_A2G_DISPATCHER -- -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. -- - -- ### 3.3. Squadron **on call engagement**. + -- ### 3.3. Squadron **on call** engagement. -- -- So to make squadrons engage targets from the airfields, use the following methods: -- @@ -558,12 +558,350 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) -- + -- + -- ### 3.5. Set squadron take-off methods + -- + -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. + -- + -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. + -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. + -- + -- **The default landing method is to spawn new aircraft directly in the air.** + -- + -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. + -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: + -- + -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. + -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. + -- * aircraft may collide at the airbase. + -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... + -- + -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. + -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! + -- + -- This example sets the default takeoff method to be from the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Takeoff methods + -- + -- -- The default takeoff + -- A2ADispatcher:SetDefaultTakeOffFromRunway() + -- + -- -- The individual takeoff per squadron + -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) + -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) + -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- + -- + -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. + -- + -- In the case of the @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. + -- That is modifying or setting the **altitude** from where planes spawn in the air. + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. + -- The default takeoff altitude can be modified or set using the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). + -- As part of the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. + -- If this parameter is not specified, then the default altitude will be used for the squadron. + -- + -- ### 3.5.2. Set Squadron takeoff interval. + -- + -- The different types of available airfields have different amounts of available launching platforms: + -- + -- - Airbases typically have a lot of platforms. + -- - FARPs have 4 platforms. + -- - Ships have 2 to 4 platforms. + -- + -- Depending on the demand of requested takeoffs by the A2G dispatcher, an airfield can become overloaded. Too many aircraft need to be taken + -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, + -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. + -- The takeff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. + -- + -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of + -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. + -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time + -- has been reached, a new group will be spawned or activated for takeoff. + -- + -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and + -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. + -- + -- See the underlying example: + -- + -- -- Imagine a squadron launched from a FARP, with a grouping of 4. + -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. + -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. + -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. + -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) + -- + -- + -- ### 3.6. Set squadron landing methods + -- + -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: + -- + -- * @{#AI_A2G_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. + -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. + -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. + -- + -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the + -- A2A defense system, as no new CAP or GCI planes can takeoff. + -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. + -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. + -- + -- This example defines the default landing method to be at the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Landing methods + -- + -- -- The default landing method + -- A2ADispatcher:SetDefaultLandingAtRunway() + -- + -- -- The individual landing per squadron + -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) + -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) + -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) + -- + -- + -- ### 3.7. Set squadron **grouping**. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. + -- + -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\Dia12.JPG) + -- + -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. + -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining + -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. + -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, + -- an additional on call flight needs to be started. + -- + -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. + -- + -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the A2G defenses. + -- + -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. + -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. + -- + -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\Dia11.JPG) + -- + -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. + -- + -- The @{#AI_A2G_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, + -- taking into account the plane types of the squadron. + -- + -- For example, a A-10C with full long-distance A2G missiles payload, may still be less effective than a Su-23 with short range A2G missiles... + -- So in this case, one may want to use the @{#AI_A2G_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: + -- + -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. + -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. + -- + -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group + -- multiplied by the overhead parameter, and rounded up to the smallest integer. + -- + -- Typically, for A2G defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: + -- + -- - A-10C: 0.15 + -- - Su-34: 0.15 + -- - A-10A: 0.25 + -- - SU-25T: 0.10 + -- + -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, + -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. + -- + -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. + -- + -- ### 3.8. Set the squadron **engage limit**. + -- + -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. + -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. + -- + -- ## 4. Set the **fuel treshold**. + -- + -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. + -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- + -- ## 6. Other configuration options + -- + -- ### 6.1. Set a tactical display panel. + -- + -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_A2G_DISPATCHER. + -- Use the method @{#AI_A2G_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. + -- Note that there may be some performance impact if this panel is shown. + -- + -- ## 10. Default settings. + -- + -- Default settings configure the standard behaviour of the squadrons. + -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. + -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. + -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. + -- + -- ## 10.1. Default **takeoff** behaviour. + -- + -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. + -- + -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** + -- + -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. + -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. + -- + -- ## 10.2. Default landing behaviour. + -- + -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. + -- + -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. + -- + -- * @{#AI_A2G_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. + -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. + -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. + -- + -- ## 10.3. Default **overhead**. + -- + -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. + -- + -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. + -- + -- Use the @{#AI_A2G_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. + -- + -- ## 10.4. Default **grouping**. + -- + -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. + -- + -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. + -- + -- ## 10.5. Default RTB fuel treshold. + -- + -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.6. Default RTB damage treshold. + -- + -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.7. Default settings for **patrol**. + -- + -- ### 10.7.1. Default **patrol time Interval**. + -- + -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. + -- + -- The default patrol time interval is between **180** and **600** seconds. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. + -- + -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using + -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. + -- + -- ### 10.7.2. Default **patrol limit**. + -- + -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. + -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. + -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. + -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. + -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using + -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. + -- + -- ## 10.7.3. Default tanker for refuelling when executing CAP. + -- + -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. + -- This greatly increases the efficiency of your CAP operations. + -- + -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. + -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- + -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. + -- + -- For example, the following setup will set the default refuel tanker to "Tanker": + -- + -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_11.JPG) + -- + -- -- Define the CAP + -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) + -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) + -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) + -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) + -- + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) + -- A2ADispatcher:SetDefaultTanker( "Tanker" ) + -- + -- ## 10.8. Default settings for GCI. + -- + -- ## 10.8.1. Optimal intercept point calculation. + -- + -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. + -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. + -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. + -- + -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: + -- + -- * The average bearing of the intruders for an amount of seconds. + -- * The average speed of the intruders for an amount of seconds. + -- * An assumed time it takes to get planes operational at the airbase. + -- + -- The **intercept point** will determine: + -- + -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. + -- * The optimal airbase from where defenders will takeoff for GCI. + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. + -- + -- ## 10.8.2. Default Disengage Radius. + -- + -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. + -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! + -- + -- Use the method @{#AI_A2G_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. + -- + -- ## 11. Airbase capture: + -- + -- Different squadrons can be located at one airbase. + -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. + -- As a result, the GCI and CAP will stop! + -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes + -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. + -- + -- + -- + -- -- @field #AI_A2G_DISPATCHER AI_A2G_DISPATCHER = { ClassName = "AI_A2G_DISPATCHER", Detection = nil, } + --- Definition of a Squadron. + -- @type AI_A2G_DISPATCHER.Squadron + -- @field #string Name The Squadron name. + -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase. + -- @field #string AirbaseName The name of the home airbase. + -- @field Core.Spawn#SPAWN Spawn The spawning object. + -- @field #number ResourceCount The number of resources available. + -- @field #list<#string> TemplatePrefixes The list of template prefixes. + -- @field #boolean Captured true if the squadron is captured. + -- @field #number Overhead The overhead for the squadron. + --- List of defense coordinates. -- @type AI_A2G_DISPATCHER.DefenseCoordinates @@ -587,6 +925,32 @@ do -- AI_A2G_DISPATCHER AtEngineShutdown = 3, } + --- A defense queue item description + -- @type AI_A2G_DISPATCHER.DefenseQueueItem + -- @field Squadron + -- @field #AI_A2G_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. + -- @field DefendersNeeded + -- @field Defense + -- @field DefenseTaskType + -- @field Functional.Detection#DETECTION_BASE AttackerDetection + -- @field DefenderGrouping + -- @field #string SquadronName The name of the squadron. + + --- Queue of planned defenses to be launched. + -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. + -- And some of these platforms have very limited amount of "launching" platforms. + -- Therefore, this queue concept is introduced that queues each defender request. + -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. + -- This guarantees that launched defenders are also directly existing ... + -- @type AI_A2G_DISPATCHER.DefenseQueue + -- @list<#AI_A2G_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... + + --- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue + AI_A2G_DISPATCHER.DefenseQueue = {} + + + + --- AI_A2G_DISPATCHER constructor. -- This is defining the A2G DISPATCHER for one coaliton. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. @@ -759,6 +1123,8 @@ do -- AI_A2G_DISPATCHER self:SetDefenseReactivityMedium() + self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) + self:__Start( 5 ) return self @@ -774,14 +1140,14 @@ do -- AI_A2G_DISPATCHER for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do DefenderSquadron.Resource = {} for Resource = 1, DefenderSquadron.ResourceCount or 0 do - self:ParkDefender( DefenderSquadron ) + self:ResourcePark( DefenderSquadron ) end end end --- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ParkDefender( DefenderSquadron ) + function AI_A2G_DISPATCHER:ResourcePark( DefenderSquadron ) local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN Spawn:InitGrouping( 1 ) @@ -838,7 +1204,7 @@ do -- AI_A2G_DISPATCHER self:RemoveDefenderFromSquadron( Squadron, Defender ) end DefenderUnit:Destroy() - self:ParkDefender( Squadron, Defender ) + self:ResourcePark( Squadron, Defender ) return end if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then @@ -865,11 +1231,11 @@ do -- AI_A2G_DISPATCHER self:RemoveDefenderFromSquadron( Squadron, Defender ) end DefenderUnit:Destroy() - self:ParkDefender( Squadron, Defender ) + self:ResourcePark( Squadron, Defender ) end end end - + do -- Manage the defensive behaviour --- @param #AI_A2G_DISPATCHER self @@ -1352,7 +1718,6 @@ do -- AI_A2G_DISPATCHER -- @return #AI_A2G_DISPATCHER function AI_A2G_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} local DefenderSquadron = self.DefenderSquadrons[SquadronName] @@ -1379,6 +1744,8 @@ do -- AI_A2G_DISPATCHER DefenderSquadron.TemplatePrefixes = TemplatePrefixes DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. + self:SetSquadronTakeoffInterval( SquadronName, 0 ) + self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) return self @@ -1449,6 +1816,28 @@ do -- AI_A2G_DISPATCHER end + --- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. + -- @usage + -- + -- -- Set the Squadron Takeoff interval every 60 seconds for squadron "SQ50", which is good for a FARP cold start. + -- A2GDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) + -- + function AI_A2G_DISPATCHER:SetSquadronTakeoffInterval( SquadronName, TakeoffInterval ) + + self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + if DefenderSquadron then + DefenderSquadron.TakeoffInterval = TakeoffInterval or 0 + DefenderSquadron.TakeoffTime = 0 + end + + end + + --- Set the squadron patrol parameters for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. @@ -2806,6 +3195,7 @@ do -- AI_A2G_DISPATCHER if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then local SquadronOverhead = self:GetSquadronOverhead( DefenderSquadronName ) + self:F( { SquadronOverhead = SquadronOverhead } ) if DefenderSize then DefendersEngaged = DefendersEngaged + DefenderSize DefendersMissing = DefendersMissing - DefenderSize / SquadronOverhead @@ -2820,6 +3210,15 @@ do -- AI_A2G_DISPATCHER end + for QueueID, QueueItem in pairs( self.DefenseQueue ) do + local QueueItem = QueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem + if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID == AttackerDetection.ItemID then + DefendersMissing = DefendersMissing - QueueItem.DefendersNeeded / QueueItem.DefenderSquadron.Overhead + --DefendersEngaged = DefendersEngaged + QueueItem.DefenderGrouping + end + self:F( { QueueItemName = QueueItem.Defense, QueueItem_ItemID = QueueItem.AttackerDetection.ItemID, DetectedItem = AttackerDetection.ItemID, DefendersMissing = DefendersMissing } ) + end + self:F( { DefenderCount = DefendersEngaged } ) return DefendersTotal, DefendersEngaged, DefendersMissing @@ -2864,7 +3263,7 @@ do -- AI_A2G_DISPATCHER break end end - + return Friendlies end @@ -2950,72 +3349,264 @@ do -- AI_A2G_DISPATCHER -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:onafterPatrol( From, Event, To, SquadronName, DefenseTaskType ) - self:F({SquadronName = SquadronName}) - local DefenderSquadron, Patrol = self:CanPatrol( SquadronName, DefenseTaskType ) if Patrol then - - local DefenderPatrol, DefenderGrouping = self:ResourceActivate( DefenderSquadron ) - - if DefenderPatrol then - - local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderPatrol, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderPatrol, DefenseTaskType, Fsm, nil, DefenderGrouping ) - - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Patrol Birth", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Squadron then - Fsm:__Patrol( 2 ) -- Start Patrolling - end - end - - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Patrol RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - Dispatcher:ClearDefenderTaskTarget( Defender ) - end - - --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Patrol Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ParkDefender( Squadron, Defender ) - end - end - end + self:ResourceQueue( true, DefenderSquadron, nil, Patrol, DefenseTaskType, nil, SquadronName ) end end + --- + -- @param #AI_A2G_DISPATCHER self + function AI_A2G_DISPATCHER:ResourceQueue( Patrol, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) + + self:F( { DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName } ) + + local DefenseQueueItem = {} -- #AI_A2G_DISPATCHER.DefenderQueueItem + + + DefenseQueueItem.Patrol = Patrol + DefenseQueueItem.DefenderSquadron = DefenderSquadron + DefenseQueueItem.DefendersNeeded = DefendersNeeded + DefenseQueueItem.Defense = Defense + DefenseQueueItem.DefenseTaskType = DefenseTaskType + DefenseQueueItem.AttackerDetection = AttackerDetection + DefenseQueueItem.SquadronName = SquadronName + + table.insert( self.DefenseQueue, DefenseQueueItem ) + self:F( { QueueItems = #self.DefenseQueue } ) + + end + + --- + -- @param #AI_A2G_DISPATCHER self + function AI_A2G_DISPATCHER:ResourceTakeoff() + + for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do + self:F( { DefenseQueueID } ) + end + + for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do + + if #self.DefenseQueue > 0 then + + self:F( { SquadronName, Squadron.Name, Squadron.TakeoffTime, Squadron.TakeoffInterval, timer.getTime() } ) + + local DefenseQueueItem = self.DefenseQueue[1] + self:F( {DefenderSquadron=DefenseQueueItem.DefenderSquadron} ) + + if DefenseQueueItem.SquadronName == SquadronName then + + if Squadron.TakeoffTime + Squadron.TakeoffInterval < timer.getTime() then + Squadron.TakeoffTime = timer.getTime() + + if DefenseQueueItem.Patrol == true then + self:ResourcePatrol( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) + else + self:ResourceEngage( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) + end + table.remove( self.DefenseQueue, 1 ) + end + end + end + + end + + end + + --- + -- @param #AI_A2G_DISPATCHER self + function AI_A2G_DISPATCHER:ResourcePatrol( DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, AttackerDetection, SquadronName ) + + + self:F({DefenderSquadron=DefenderSquadron}) + self:F({DefendersNeeded=DefendersNeeded}) + self:F({Patrol=Patrol}) + self:F({DefenseTaskType=DefenseTaskType}) + self:F({AttackerDetection=AttackerDetection}) + self:F({SquadronName=SquadronName}) + + local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) + + if DefenderGroup then + + local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } + + local Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) + Fsm:SetDispatcher( self ) + Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + Fsm:SetDisengageRadius( self.DisengageRadius ) + Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) + Fsm:Start() + + self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, nil, DefenderGrouping ) + + function Fsm:onafterTakeoff( Defender, From, Event, To ) + self:F({"Defender Birth", Defender:GetName()}) + --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + + if Squadron then + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Fsm:Patrol() -- Engage on the TargetSetUnit + end + end + + function Fsm:onafterRTB( Defender, From, Event, To ) + self:F({"Defender RTB", Defender:GetName()}) + self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + + Dispatcher:ClearDefenderTaskTarget( Defender ) + end + + --- @param #AI_A2G_DISPATCHER self + function Fsm:onafterLostControl( Defender, From, Event, To ) + self:F({"Defender LostControl", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + if Defender:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + end + + --- @param #AI_A2G_DISPATCHER self + function Fsm:onafterHome( Defender, From, Event, To, Action ) + self:F({"Defender Home", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + + if Action and Action == "Destroy" then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + + if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + Dispatcher:ResourcePark( Squadron, Defender ) + end + end + end + + end + + + --- + -- @param #AI_A2G_DISPATCHER self + function AI_A2G_DISPATCHER:ResourceEngage( DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) + + self:F({DefenderSquadron=DefenderSquadron}) + self:F({DefendersNeeded=DefendersNeeded}) + self:F({Defense=Defense}) + self:F({DefenseTaskType=DefenseTaskType}) + self:F({AttackerDetection=AttackerDetection}) + self:F({SquadronName=SquadronName}) + + local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) + + if DefenderGroup then + + local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } + + local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE + Fsm:SetDispatcher( self ) + Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + Fsm:SetDisengageRadius( self.DisengageRadius ) + Fsm:Start() + + self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, AttackerDetection, DefenderGrouping ) + + function Fsm:onafterTakeoff( Defender, From, Event, To ) + self:F({"Defender Birth", Defender:GetName()}) + --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) + + self:F( { DefenderTarget = DefenderTarget } ) + + if DefenderTarget then + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Fsm:Engage( DefenderTarget.Set ) -- Engage on the TargetSetUnit + end + end + + function Fsm:onafterRTB( Defender, From, Event, To ) + self:F({"Defender RTB", Defender:GetName()}) + + local DefenderName = Defender:GetName() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + + self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + + Dispatcher:ClearDefenderTaskTarget( Defender ) + end + + --- @param #AI_A2G_DISPATCHER self + function Fsm:onafterLostControl( Defender, From, Event, To ) + self:F({"Defender LostControl", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + + if Defender:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + end + + --- @param #AI_A2G_DISPATCHER self + function Fsm:onafterHome( Defender, From, Event, To, Action ) + self:F({"Defender Home", Defender:GetName()}) + self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetName() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + + if Action and Action == "Destroy" then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + end + + if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then + Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) + Defender:Destroy() + Dispatcher:ResourcePark( Squadron, Defender ) + end + end + end + end --- -- @param #AI_A2G_DISPATCHER self @@ -3143,83 +3734,9 @@ do -- AI_A2G_DISPATCHER end while ( DefendersNeeded > 0 ) do - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - + self:ResourceQueue( false, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, ClosestDefenderSquadronName ) DefendersNeeded = DefendersNeeded - DefenderGrouping - - if DefenderGroup then - - DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - - local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() - - self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGroup, DefenseTaskType, Fsm, AttackerDetection, DefenderGrouping ) - - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Birth", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) - - self:F( { DefenderTarget = DefenderTarget } ) - - if DefenderTarget then - Fsm:Engage( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - Dispatcher:ClearDefenderTaskTarget( Defender ) - end - - --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - --if Defender:IsAboveRunway() then - --Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - --Defender:Destroy() - --end - end - - --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ParkDefender( Squadron, Defender ) - end - end - end -- if DefenderGCI then + DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead end -- while ( DefendersNeeded > 0 ) do else -- No more resources, try something else. @@ -3257,6 +3774,8 @@ do -- AI_A2G_DISPATCHER self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "SEAD" ) + + if DetectedItem.IsDetected == true then @@ -3464,7 +3983,7 @@ do -- AI_A2G_DISPATCHER if self.TacticalDisplay then -- Show tactical situation - Report:Add( string.format( "\n - %4s %s ( %s ): ( #%d ) %s" , DetectedItem.Type or " --- ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + Report:Add( string.format( " - %s ( %s ): ( #%d - %4s ) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", DetectedItem.Set:GetObjectNames() ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then @@ -3510,6 +4029,13 @@ do -- AI_A2G_DISPATCHER end end Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) + + Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) + for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do + local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem + Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) + + end self:F( Report:Text( "\n" ) ) trigger.action.outText( Report:Text( "\n" ), 25 ) diff --git a/Moose Development/Moose/AI/AI_A2G_Engage.lua b/Moose Development/Moose/AI/AI_A2G_Engage.lua index e6be845c3..b71da1dce 100644 --- a/Moose Development/Moose/AI/AI_A2G_Engage.lua +++ b/Moose Development/Moose/AI/AI_A2G_Engage.lua @@ -295,10 +295,10 @@ end --- @param Wrapper.Group#GROUP AIControllable function AI_A2G_ENGAGE.EngageRoute( AIGroup, Fsm ) - AIGroup:F( { "AI_A2G_ENGAGE.EngageRoute:", AIGroup:GetName() } ) + AIGroup:I( { "AI_A2G_ENGAGE.EngageRoute:", AIGroup:GetName() } ) if AIGroup:IsAlive() then - Fsm:__Engage( 0.5 ) + Fsm:__Engage( Fsm.TaskDelay ) --local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) --AIGroup:SetTask( Task ) @@ -327,7 +327,7 @@ end function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To ) AIGroup:ClearTasks() self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) end @@ -372,7 +372,7 @@ function AI_A2G_ENGAGE:OnEventDead( EventData ) if EventData.IniDCSUnit then if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) + self:__Destroy( self.TaskDelay, EventData ) end end end diff --git a/Moose Development/Moose/AI/AI_A2G_Patrol.lua b/Moose Development/Moose/AI/AI_A2G_Patrol.lua index dd9e0342a..4e8a6e953 100644 --- a/Moose Development/Moose/AI/AI_A2G_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2G_Patrol.lua @@ -234,12 +234,12 @@ function AI_A2G_PATROL:onafterPatrol( AIPatrol, From, Event, To ) self:ClearTargetDistance() - self:__Route( 1 ) + self:__Route( self.TaskDelay ) AIPatrol:OnReSpawn( function( PatrolGroup ) - self:__Reset( 1 ) - self:__Route( 5 ) + self:__Reset( self.TaskDelay ) + self:__Route( self.TaskDelay ) end ) end @@ -306,7 +306,7 @@ function AI_A2G_PATROL:onafterRoute( AIPatrol, From, Event, To ) AIPatrol:OptionROEReturnFire() AIPatrol:OptionROTEvadeFire() - AIPatrol:Route( PatrolRoute, 0.5 ) + AIPatrol:Route( PatrolRoute, self.TaskDelay ) end end @@ -314,10 +314,10 @@ end --- @param Wrapper.Group#GROUP AIPatrol function AI_A2G_PATROL.Resume( AIPatrol, Fsm ) - AIPatrol:I( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } ) + AIPatrol:F( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } ) if AIPatrol:IsAlive() then - Fsm:__Reset( 1 ) - Fsm:__Route( 5 ) + Fsm:__Reset( self.TaskDelay ) + Fsm:__Route( self.TaskDelay ) end end diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index 142d73e6a..124fe5049 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -129,107 +129,93 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni -- If it is less than 50km, then attack without a route. -- Otherwise perform a route attack. + local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) + local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) + local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetCoord = self.AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) ) -- Ground targets don't have an altitude. + TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) --- if TargetDistance >= 50000 then + local EngageRoute = {} + + + --- Calculate the target route point. + + local FromWP = DefenderCoord:WaypointAir( + self.PatrolAltType or "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + EngageSpeed, + true + ) + + EngageRoute[#EngageRoute+1] = FromWP + + self:SetTargetDistance( TargetCoord ) -- For RTB status check + + local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) ) + local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 25000 ) + + --- Create a route point of type air, 50km from the center of the attack point. + + local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( + self.PatrolAltType or "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + EngageSpeed, + true + ) - local EngageRoute = {} + EngageRoute[#EngageRoute+1] = ToWP + + local AttackTasks = {} + + self.AttackSetUnit.AttackIndex = self.AttackSetUnit.AttackIndex and self.AttackSetUnit.AttackIndex + 1 or 1 + if self.AttackSetUnit.AttackIndex > self.AttackSetUnit:Count() then + self.AttackSetUnit.AttackIndex = 1 + end + + local AttackSetUnitPerThreatLevel = self.AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - --- Calculate the target route point. - - local FromWP = DefenderCoord:WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = FromWP - - local ToCoord = self.AttackSetUnit:GetFirst():GetCoordinate() - self:SetTargetDistance( ToCoord ) -- For RTB status check - - local FromEngageAngle = ToCoord:GetAngleDegrees( ToCoord:GetDirectionVec3( DefenderCoord ) ) - - --- Create a route point of type air, 50km from the center of the attack point. - local ToWP = ToCoord:Translate( 50000, FromEngageAngle ):WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - self:F( { Angle = FromEngageAngle, ToTargetSpeed = ToTargetSpeed } ) - self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } ) - - EngageRoute[#EngageRoute+1] = ToWP - - local AttackTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "Engage Unit evaluation:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsGround() } ) - local HasRadar = AttackUnit:HasSEAD() - if HasRadar then - self:T( { "Eliminating Unit:", AttackUnit:GetName() } ) - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) + for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do + if AttackUnitID >= self.AttackSetUnit.AttackIndex then + if AttackUnit then + if AttackUnit:IsAlive() and AttackUnit:IsGround() then + local HasRadar = AttackUnit:HasSEAD() + if HasRadar then + self:F( { "SEAD Unit:", AttackUnit:GetName() } ) + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, false, false, nil, nil, EngageAltitude ) + end end end end - - if #AttackTasks == 0 then - self:E( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( 0.5 ) - else - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTVertical() - DefenderGroup:OptionKeepWeaponsOnThreat() - --DefenderGroup:OptionRTBAmmo( Weapon.flag.AnyASM ) - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - end + end - DefenderGroup:Route( EngageRoute, 2 ) + if #AttackTasks == 0 then + self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:Return() + self:__RTB( self.TaskDelay ) + else + DefenderGroup:OptionROEOpenFire() + DefenderGroup:OptionROTVertical() + DefenderGroup:OptionKeepWeaponsOnThreat() + --DefenderGroup:OptionRTBAmmo( Weapon.flag.AnyASM ) + + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self ) + EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) + end + + DefenderGroup:Route( EngageRoute, self.TaskDelay ) --- else --- local AttackTasks = {} --- --local AttackUnit = self.AttackSetUnit:GetRandom() -- Wrapper.Unit#UNIT --- for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do --- if AttackUnit:IsAlive() and AttackUnit:IsGround() then --- local HasRadar = AttackUnit:HasSEAD() --- if HasRadar then --- self:T( { "Eliminating Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsGround() } ) --- AttackTasks[#AttackTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) --- AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self ) --- end --- end --- end --- local DefenderTask = DefenderGroup:TaskCombo( AttackTasks ) --- --- DefenderGroup:OptionROEOpenFire() --- DefenderGroup:OptionROTVertical() --- DefenderGroup:OptionKeepWeaponsOnThreat() --- DefenderGroup:OptionRTBAmmo( Weapon.flag.AnyASM ) --- --- DefenderGroup:SetTask( DefenderTask, 0 ) --- end end else self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) end end diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 2c708d2fc..91a6eaffa 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -51,6 +51,8 @@ AI_AIR = { ClassName = "AI_AIR", } +AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI. + --- Creates a new AI_AIR process. -- @param #AI_AIR self -- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process. @@ -64,6 +66,8 @@ function AI_AIR:New( AIGroup ) self:SetStartState( "Stopped" ) + self:AddTransition( "*", "Queue", "Queued" ) + self:AddTransition( "*", "Start", "Started" ) --- Start Handler OnBefore for AI_AIR @@ -400,6 +404,8 @@ function AI_AIR:SetDamageThreshold( PatrolDamageThreshold ) return self end + + --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. -- @param #AI_AIR self -- @return #AI_AIR self @@ -520,7 +526,7 @@ function AI_AIR:onafterStatus() end if RTB == true then - self:__RTB( 0.5 ) + self:__RTB( self.TaskDelay ) end if not self:Is("Home") then @@ -537,7 +543,7 @@ function AI_AIR.RTBRoute( AIGroup, Fsm ) AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } ) if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) + Fsm:RTB() end end @@ -547,7 +553,7 @@ function AI_AIR.RTBHold( AIGroup, Fsm ) AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } ) if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) + Fsm:__RTB( Fsm.TaskDelay ) Fsm:Return() local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) AIGroup:SetTask( Task ) @@ -573,19 +579,29 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Calculate the target route point. - local CurrentCoord = AIGroup:GetCoordinate() + local FromCoord = AIGroup:GetCoordinate() local ToTargetCoord = self.HomeAirbase:GetCoordinate() local ToTargetSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) - local ToAirbaseAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) + local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) ) - local Distance = CurrentCoord:Get2DDistance( ToTargetCoord ) + local Distance = FromCoord:Get2DDistance( ToTargetCoord ) - local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle ) + local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) if Distance < 5000 then self:E( "RTB and near the airbase!" ) self:Home() return end + + --- Create a route point of type air. + local FromRTBRoutePoint = FromCoord:WaypointAir( + self.PatrolAltType, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + --- Create a route point of type air. local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( self.PatrolAltType, @@ -595,21 +611,19 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) true ) - EngageRoute[#EngageRoute+1] = ToRTBRoutePoint + EngageRoute[#EngageRoute+1] = FromRTBRoutePoint EngageRoute[#EngageRoute+1] = ToRTBRoutePoint + local Tasks = {} + Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self ) + + EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks ) + AIGroup:OptionROEHoldFire() AIGroup:OptionROTEvadeFire() - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - AIGroup:WayPointInitialize( EngageRoute ) - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self ) - EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks ) - --- NOW ROUTE THE GROUP! - AIGroup:Route( EngageRoute, 0.5 ) + AIGroup:Route( EngageRoute, self.TaskDelay ) end @@ -656,7 +670,7 @@ function AI_AIR.Resume( AIGroup, Fsm ) AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } ) if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) + Fsm:__RTB( Fsm.TaskDelay ) end end @@ -676,10 +690,19 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) --- Calculate the target route point. - local CurrentCoord = AIGroup:GetCoordinate() + local FromRefuelCoord = AIGroup:GetCoordinate() local ToRefuelCoord = Tanker:GetCoordinate() local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) + --- Create a route point of type air. + local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir( + self.PatrolAltType, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToRefuelSpeed, + true + ) + --- Create a route point of type air. local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir( self.PatrolAltType, @@ -691,7 +714,7 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) self:F( { ToRefuelSpeed = ToRefuelSpeed } ) - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint + RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint AIGroup:OptionROEHoldFire() @@ -702,7 +725,7 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self ) RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks ) - AIGroup:Route( RefuelRoute, 0.5 ) + AIGroup:Route( RefuelRoute, self.TaskDelay ) else self:RTB() end @@ -725,7 +748,7 @@ function AI_AIR:OnCrash( EventData ) if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then self:E( self.Controllable:GetUnits() ) if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) + self:__Crash( self.TaskDelay, EventData ) end end end @@ -735,7 +758,7 @@ end function AI_AIR:OnEjection( EventData ) if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) + self:__Eject( self.TaskDelay, EventData ) end end @@ -744,6 +767,6 @@ end function AI_AIR:OnPilotDead( EventData ) if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) + self:__PilotDead( self.TaskDelay, EventData ) end end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 6acb294ab..e4e2c3edf 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2033,6 +2033,54 @@ do -- SET_UNIT return self end + + --- Get the SET of the SET_UNIT **sorted per Threat Level**. + -- + -- @param #SET_UNIT self + -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). + -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). + -- @return #SET_UNIT self + -- @usage + -- + -- + function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) + self:F2( arg ) + + local ThreatLevelSet = {} + + if self:Count() ~= 0 then + for UnitName, UnitObject in pairs( self.Set ) do + local Unit = UnitObject -- Wrapper.Unit#UNIT + + local ThreatLevel = Unit:GetThreatLevel() + ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {} + ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {} + ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject + self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) + end + + + local OrderedPerThreatLevelSet = {} + + local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 + + + for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do + self:F( { ThreatLevel = ThreatLevel } ) + local ThreatLevelItem = ThreatLevelSet[ThreatLevel] + if ThreatLevelItem then + for UnitName, UnitObject in pairs( ThreatLevelItem.Set ) do + table.insert( OrderedPerThreatLevelSet, UnitObject ) + end + end + end + + return OrderedPerThreatLevelSet + end + + end + + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. -- -- @param #SET_UNIT self diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 9e4532e39..4603adf82 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -46,6 +46,7 @@ do -- DETECTION MANAGER --- @type DETECTION_MANAGER -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players. -- @extends Core.Fsm#FSM --- DETECTION_MANAGER class. @@ -218,6 +219,33 @@ do -- DETECTION MANAGER return self._ReportDisplayTime end + --- Set a command center to communicate actions to the players reporting to the command center. + -- @param #DETECTION_MANAGER self + -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. + -- @return #DETECTION_MANGER self + function DETECTION_MANAGER:SetCommandCenter( CommandCenter ) + + self.CC = CommandCenter + + return self + end + + + --- Send an information message to the players reporting to the command center. + -- @param #DETECTION_MANAGER self + -- @param #string Message The message to be sent. + -- @return #DETECTION_MANGER self + function DETECTION_MANAGER:MessageToPlayers( Message ) + + if self.CC then + self.CC:MessageToAll( Message ) + end + + return self + end + + + --- Reports the detected items to the @{Core.Set#SET_GROUP}. -- @param #DETECTION_MANAGER self -- @param Functional.Detection#DETECTION_BASE Detection diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index ee56856b2..4b59ef8a2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -400,7 +400,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local Controller = self:_GetController() --self:I( "Before SetTask" ) Controller:setTask( DCSTask ) - --self:I( "After SetTask" ) + self:I( { ControllableName = self:GetName(), DCSTask = DCSTask } ) else BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) end @@ -408,6 +408,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) if not WaitTime or WaitTime == 0 then SetTask( self, DCSTask ) + self:I( { ControllableName = self:GetName(), DCSTask = DCSTask } ) else self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime ) end @@ -1057,7 +1058,7 @@ end -- @param #CONTROLLABLE self -- @param #number Altitude The altitude [m] to hold the position. -- @param #number Speed The speed [m/s] flying when holding the position. --- @param Core.Point#COORDINATE Coordinate The coordinate where to orbit. +-- @param Core.Point#COORDINATE Coordinate (optional) The coordinate where to orbit. If the coordinate is not given, then the current position of the controllable is used. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate ) self:F2( { self.ControllableName, Altitude, Speed } )