MOOSE/Moose Development/Moose/AI/AI_Air_Dispatcher.lua
FlightControl 5cdaf53727 - Added a NoTrace option to the scheduler, so for those schedulers that have a schedule in microseconds, you may wanna use the function NoTrace(), to avoid spamming the dcs.log.
- Started with the implementation of multiple languages of speech. Got now a Russian and English prototype working.
- Moved radio frequency settings to a squadron, so multiple squadrons can communicate in their own radio frequency.
2019-09-25 17:53:11 +03:00

3298 lines
139 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--- **AI** - Intermediary class that realized the common methods for the A2G and A2A dispatchers.
--
-- ===
--
-- Features:
--
-- * Common methods for A2G and A2A dispatchers.
--
-- ===
--
-- ### Author: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons).
--
-- @module AI.AI_Air_Dispatcher
-- @image AI_Air_To_Ground_Dispatching.JPG
do -- AI_AIR_DISPATCHER
--- AI_AIR_DISPATCHER class.
-- @type AI_AIR_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- Intermediary class that realized the common methods for the A2G and A2A dispatchers.
--
-- ===
--
-- @field #AI_AIR_DISPATCHER
AI_AIR_DISPATCHER = {
ClassName = "AI_AIR_DISPATCHER",
Detection = nil,
}
--- Definition of a Squadron.
-- @type AI_AIR_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_AIR_DISPATCHER.DefenseCoordinates
-- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name.
--- Enumerator for spawns at airbases
-- @type AI_AIR_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defnes Landing location.
-- @field Landing
AI_AIR_DISPATCHER.Landing = {
NearAirbase = 1,
AtRunway = 2,
AtEngineShutdown = 3,
}
--- A defense queue item description
-- @type AI_AIR_DISPATCHER.DefenseQueueItem
-- @field Squadron
-- @field #AI_AIR_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_AIR_DISPATCHER.DefenseQueue
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
AI_AIR_DISPATCHER.DefenseQueue = {}
--- AI_AIR_DISPATCHER constructor.
-- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network.
-- @return #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:New( Detection )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_AIR_DISPATCHER
self.Detection = Detection -- Functional.Detection#DETECTION_AREAS
-- This table models the DefenderSquadron templates.
self.DefenderSquadrons = {} -- The Defender Squadrons.
self.DefenderSpawns = {}
self.DefenderTasks = {} -- The Defenders Tasks.
self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons.
self:SetDefenseRadius()
self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds.
self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Air )
self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground.
self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.NearAirbase )
self:SetDefaultEngageRadius( 150000 )
self:SetDefaultOverhead( 1 )
self:SetDefaultGrouping( 1 )
self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel.
self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB.
self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds.
self:SetDefaultPatrolLimit( 1 ) -- Maximum one Patrol per squadron.
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
-- @function [parent=#AI_AIR_DISPATCHER] OnAfterAssign
-- @param #AI_AIR_DISPATCHER self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Tasking.Task_A2G#AI_A2G Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName
self:AddTransition( "*", "Patrol", "*" )
--- Patrol Handler OnBefore for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnBeforePatrol
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Patrol Handler OnAfter for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnAfterPatrol
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Patrol Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] Patrol
-- @param #AI_AIR_DISPATCHER self
--- Patrol Asynchronous Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] __Patrol
-- @param #AI_AIR_DISPATCHER self
-- @param #number Delay
self:AddTransition( "*", "Defend", "*" )
--- Defend Handler OnBefore for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnBeforeDefend
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Defend Handler OnAfter for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnAfterDefend
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Defend Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] Defend
-- @param #AI_AIR_DISPATCHER self
--- Defend Asynchronous Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] __Defend
-- @param #AI_AIR_DISPATCHER self
-- @param #number Delay
self:AddTransition( "*", "Engage", "*" )
--- Engage Handler OnBefore for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnBeforeEngage
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Engage Handler OnAfter for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] OnAfterEngage
-- @param #AI_AIR_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Engage Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] Engage
-- @param #AI_AIR_DISPATCHER self
--- Engage Asynchronous Trigger for AI_AIR_DISPATCHER
-- @function [parent=#AI_AIR_DISPATCHER] __Engage
-- @param #AI_AIR_DISPATCHER self
-- @param #number Delay
-- Subscribe to the CRASH event so that when planes are shot
-- by a Unit from the dispatcher, they will be removed from the detection...
-- This will avoid the detection to still "know" the shot unit until the next detection.
-- Otherwise, a new defense or engage may happen for an already shot plane!
self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead )
self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead )
--self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead )
self:HandleEvent( EVENTS.Land )
self:HandleEvent( EVENTS.EngineShutdown )
-- Handle the situation where the airbases are captured.
self:HandleEvent( EVENTS.BaseCaptured )
self:SetTacticalDisplay( false )
self.DefenderPatrolIndex = 0
self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self )
self:__Start( 5 )
return self
end
--- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:onafterStart( From, Event, To )
self:GetParent( self ).onafterStart( self, From, Event, To )
-- Spawn the resources.
for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do
DefenderSquadron.Resource = {}
for Resource = 1, DefenderSquadron.ResourceCount or 0 do
self:ResourcePark( DefenderSquadron )
end
end
end
--- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ResourcePark( DefenderSquadron )
local TemplateID = math.random( 1, #DefenderSquadron.Spawn )
local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN
Spawn:InitGrouping( 1 )
local SpawnGroup
if self:IsSquadronVisible( DefenderSquadron.Name ) then
SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold )
local GroupName = SpawnGroup:GetName()
DefenderSquadron.Resources = DefenderSquadron.Resources or {}
DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {}
DefenderSquadron.Resources[TemplateID][GroupName] = {}
DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup
end
end
--- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventBaseCaptured( EventData )
local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured.
self:I( "Captured " .. AirbaseName )
-- Now search for all squadrons located at the airbase, and sanatize them.
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
if Squadron.AirbaseName == AirbaseName then
Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
Squadron.Captured = true
self:I( "Squadron " .. SquadronName .. " captured." )
end
end
end
--- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventCrashOrDead( EventData )
self.Detection:ForgetDetectedUnit( EventData.IniUnitName )
end
--- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventLand( EventData )
self:F( "Landed" )
local DefenderUnit = EventData.IniUnit
local Defender = EventData.IniGroup
local Squadron = self:GetSquadronFromDefender( Defender )
if Squadron then
self:F( { SquadronName = Squadron.Name } )
local LandingMethod = self:GetSquadronLanding( Squadron.Name )
if LandingMethod == AI_AIR_DISPATCHER.Landing.AtRunway then
local DefenderSize = Defender:GetSize()
if DefenderSize == 1 then
self:RemoveDefenderFromSquadron( Squadron, Defender )
end
DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender )
return
end
if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then
-- Damaged units cannot be repaired anymore.
DefenderUnit:Destroy()
return
end
end
end
--- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventEngineShutdown( EventData )
local DefenderUnit = EventData.IniUnit
local Defender = EventData.IniGroup
local Squadron = self:GetSquadronFromDefender( Defender )
if Squadron then
self:F( { SquadronName = Squadron.Name } )
local LandingMethod = self:GetSquadronLanding( Squadron.Name )
if LandingMethod == AI_AIR_DISPATCHER.Landing.AtEngineShutdown and
not DefenderUnit:InAir() then
local DefenderSize = Defender:GetSize()
if DefenderSize == 1 then
self:RemoveDefenderFromSquadron( Squadron, Defender )
end
DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender )
end
end
end
--- Define the defense radius to check if a target can be engaged by a squadron group.
-- When targets are detected that are still really far off, you don't want the dispatcher to launch defenders, as they might need to travel too far.
-- You want it to wait until a certain defend radius is reached, which is calculated as:
-- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**.
-- 2. the **distance to any defense reference point**.
--
-- The **default** defense radius is defined as **400000** or **40km**. Override the default defense radius when the era of the warfare is early, or,
-- when you don't want to let the AI_AIR_DISPATCHER react immediately when a certain border or area is not being crossed.
--
-- Use the method @{#AI_AIR_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons,
-- **the Defense Radius is defined for ALL squadrons which are operational.**
--
-- @param #AI_AIR_DISPATCHER self
-- @param #number DefenseRadius (Optional, Default = 200000) The defense radius to engage detected targets from the nearest capable and available squadron airbase.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Set 100km as the radius to defend from detected targets from the nearest airbase.
-- A2GDispatcher:SetDefendRadius( 100000 )
--
-- -- Set 200km as the radius to defend.
-- A2GDispatcher:SetDefendRadius() -- 200000 is the default value.
--
function AI_AIR_DISPATCHER:SetDefenseRadius( DefenseRadius )
self.DefenseRadius = DefenseRadius or 100000
self.Detection:SetAcceptRange( self.DefenseRadius )
return self
end
--- Define a border area to simulate a **cold war** scenario.
-- A **cold war** is one where Patrol aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border.
-- A **hot war** is one where Patrol aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send Patrol and GCI aircraft to attack it.
-- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{zone} object derived from @{Core.Zone#ZONE_BASE}. This method needs to be used for this.
-- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1
-- @param #AI_AIR_DISPATCHER self
-- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Set one ZONE_POLYGON object as the border for the A2G dispatcher.
-- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit.
-- A2GDispatcher:SetBorderZone( BorderZone )
--
-- or
--
-- -- Set two ZONE_POLYGON objects as the border for the A2G dispatcher.
-- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit.
-- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit.
-- A2GDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } )
--
--
function AI_AIR_DISPATCHER:SetBorderZone( BorderZone )
self.Detection:SetAcceptZones( BorderZone )
return self
end
--- Display a tactical report every 30 seconds about which aircraft are:
-- * Patrolling
-- * Engaging
-- * Returning
-- * Damaged
-- * Out of Fuel
-- * ...
-- @param #AI_AIR_DISPATCHER self
-- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the Tactical Display for debug mode.
-- A2GDispatcher:SetTacticalDisplay( true )
--
function AI_AIR_DISPATCHER:SetTacticalDisplay( TacticalDisplay )
self.TacticalDisplay = TacticalDisplay
return self
end
--- Set the default damage treshold when defenders will RTB.
-- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
-- @param #AI_AIR_DISPATCHER self
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default damage treshold.
-- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
--
function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold )
self.DefenderDefault.DamageThreshold = DamageThreshold
return self
end
--- Set the default Patrol time interval for squadrons, which will be used to determine a random Patrol timing.
-- The default Patrol time interval is between 180 and 600 seconds.
-- @param #AI_AIR_DISPATCHER self
-- @param #number PatrolMinSeconds The minimum amount of seconds for the random time interval.
-- @param #number PatrolMaxSeconds The maximum amount of seconds for the random time interval.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default Patrol time interval.
-- A2GDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds.
--
function AI_AIR_DISPATCHER:SetDefaultPatrolTimeInterval( PatrolMinSeconds, PatrolMaxSeconds )
self.DefenderDefault.PatrolMinSeconds = PatrolMinSeconds
self.DefenderDefault.PatrolMaxSeconds = PatrolMaxSeconds
return self
end
--- Set the default Patrol limit for squadrons, which will be used to determine how many Patrol can be airborne at the same time for the squadron.
-- The default Patrol limit is 1 Patrol, which means one Patrol group being spawned.
-- @param #AI_AIR_DISPATCHER self
-- @param #number PatrolLimit The maximum amount of Patrol that can be airborne at the same time for the squadron.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default Patrol limit.
-- A2GDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron.
--
function AI_AIR_DISPATCHER:SetDefaultPatrolLimit( PatrolLimit )
self.DefenderDefault.PatrolLimit = PatrolLimit
return self
end
--- Set the default engage limit for squadrons, which will be used to determine how many air units will engage at the same time with the enemy.
-- The default eatrol limit is 1, which means one patrol group maximum per squadron.
-- @param #AI_AIR_DISPATCHER self
-- @param #number EngageLimit The maximum engages that can be done at the same time per squadron.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default Patrol limit.
-- A2GDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron.
--
function AI_AIR_DISPATCHER:SetDefaultEngageLimit( EngageLimit )
self.DefenderDefault.EngageLimit = EngageLimit
return self
end
function AI_AIR_DISPATCHER:SetIntercept( InterceptDelay )
self.DefenderDefault.InterceptDelay = InterceptDelay
local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS
Detection:SetIntercept( true, InterceptDelay )
return self
end
--- Calculates which defender friendlies are nearby the area, to help protect the area.
-- @param #AI_AIR_DISPATCHER self
-- @param DetectedItem
-- @return #table A list of the defender friendlies nearby, sorted by distance.
function AI_AIR_DISPATCHER:GetDefenderFriendliesNearBy( DetectedItem )
-- local DefenderFriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem )
local DefenderFriendliesNearBy = {}
local DetectionCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem )
local ScanZone = ZONE_RADIUS:New( "ScanZone", DetectionCoordinate:GetVec2(), self.DefenseRadius )
ScanZone:Scan( Object.Category.UNIT, { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
local DefenderUnits = ScanZone:GetScannedUnits()
for DefenderUnitID, DefenderUnit in pairs( DefenderUnits ) do
local DefenderUnit = UNIT:FindByName( DefenderUnit:getName() )
DefenderFriendliesNearBy[#DefenderFriendliesNearBy+1] = DefenderUnit
end
return DefenderFriendliesNearBy
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:GetDefenderTasks()
return self.DefenderTasks or {}
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:GetDefenderTask( Defender )
return self.DefenderTasks[Defender]
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:GetDefenderTaskFsm( Defender )
return self:GetDefenderTask( Defender ).Fsm
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:GetDefenderTaskTarget( Defender )
return self:GetDefenderTask( Defender ).Target
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:GetDefenderTaskSquadronName( Defender )
return self:GetDefenderTask( Defender ).SquadronName
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ClearDefenderTask( Defender )
if Defender:IsAlive() and self.DefenderTasks[Defender] then
local Target = self.DefenderTasks[Defender].Target
local Message = "Clearing (" .. self.DefenderTasks[Defender].Type .. ") "
Message = Message .. Defender:GetName()
if Target then
Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or ""
end
self:F( { Target = Message } )
end
self.DefenderTasks[Defender] = nil
return self
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ClearDefenderTaskTarget( Defender )
local DefenderTask = self:GetDefenderTask( Defender )
if Defender:IsAlive() and DefenderTask then
local Target = DefenderTask.Target
local Message = "Clearing (" .. DefenderTask.Type .. ") "
Message = Message .. Defender:GetName()
if Target then
Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or ""
end
self:F( { Target = Message } )
end
if Defender and DefenderTask and DefenderTask.Target then
DefenderTask.Target = nil
end
-- if Defender and DefenderTask then
-- if DefenderTask.Fsm:Is( "Fuel" )
-- or DefenderTask.Fsm:Is( "LostControl")
-- or DefenderTask.Fsm:Is( "Damaged" ) then
-- self:ClearDefenderTask( Defender )
-- end
-- end
return self
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target, Size )
self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } )
self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {}
self.DefenderTasks[Defender].Type = Type
self.DefenderTasks[Defender].Fsm = Fsm
self.DefenderTasks[Defender].SquadronName = SquadronName
self.DefenderTasks[Defender].Size = Size
if Target then
self:SetDefenderTaskTarget( Defender, Target )
end
return self
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection )
local Message = "(" .. self.DefenderTasks[Defender].Type .. ") "
Message = Message .. Defender:GetName()
Message = Message .. ( AttackerDetection and ( " target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]" ) ) or ""
self:F( { AttackerDetection = Message } )
if AttackerDetection then
self.DefenderTasks[Defender].Target = AttackerDetection
end
return self
end
--- This is the main method to define Squadrons programmatically.
-- Squadrons:
--
-- * Have a **name or key** that is the identifier or key of the squadron.
-- * Have **specific plane types** defined by **templates**.
-- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through.
-- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources.
--
-- The name of the squadron given acts as the **squadron key** in the AI\_A2G\_DISPATCHER:Squadron...() methods.
--
-- Additionally, squadrons have specific configuration options to:
--
-- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway).
-- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown).
-- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped.
-- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned.
--
-- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft.
--
-- @param #AI_AIR_DISPATCHER self
--
-- @param #string SquadronName A string (text) that defines the squadron identifier or the key of the Squadron.
-- It can be any name, for example `"104th Squadron"` or `"SQ SQUADRON1"`, whatever.
-- As long as you remember that this name becomes the identifier of your squadron you have defined.
-- You need to use this name in other methods too!
--
-- @param #string AirbaseName The airbase name where you want to have the squadron located.
-- You need to specify here EXACTLY the name of the airbase as you see it in the mission editor.
-- Examples are `"Batumi"` or `"Tbilisi-Lochini"`.
-- EXACTLY the airbase name, between quotes `""`.
-- To ease the airbase naming when using the LDT editor and IntelliSense, the @{Wrapper.Airbase#AIRBASE} class contains enumerations of the airbases of each map.
--
-- * Caucasus: @{Wrapper.Airbase#AIRBASE.Caucaus}
-- * Nevada or NTTR: @{Wrapper.Airbase#AIRBASE.Nevada}
-- * Normandy: @{Wrapper.Airbase#AIRBASE.Normandy}
--
-- @param #string TemplatePrefixes A string or an array of strings specifying the **prefix names of the templates** (not going to explain what is templates here again).
-- Examples are `{ "104th", "105th" }` or `"104th"` or `"Template 1"` or `"BLUE PLANES"`.
-- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code.
-- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets.
--
-- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available.
--
-- @usage
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- @usage
-- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock...
-- A2GDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 )
--
-- @usage
-- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock...
-- -- Note that in this implementation, the A2G dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses.
-- -- Note the usage of the {} for the airplane templates list.
-- A2GDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 )
--
-- @usage
-- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock...
-- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 )
-- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 )
--
-- @usage
-- -- This is an example like the previous, but now with infinite resources.
-- -- The ResourceCount parameter is not given in the SetSquadron method.
-- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" )
-- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27" )
--
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
local DefenderSquadron = self.DefenderSquadrons[SquadronName]
DefenderSquadron.Name = SquadronName
DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName )
DefenderSquadron.AirbaseName = DefenderSquadron.Airbase:GetName()
if not DefenderSquadron.Airbase then
error( "Cannot find airbase with name:" .. AirbaseName )
end
DefenderSquadron.Spawn = {}
if type( TemplatePrefixes ) == "string" then
local SpawnTemplate = TemplatePrefixes
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate]
else
for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
end
end
DefenderSquadron.ResourceCount = ResourceCount
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
end
--- Get an item from the Squadron table.
-- @param #AI_AIR_DISPATCHER self
-- @return #table
function AI_AIR_DISPATCHER:GetSquadron( SquadronName )
local DefenderSquadron = self.DefenderSquadrons[SquadronName]
if not DefenderSquadron then
error( "Unknown Squadron:" .. SquadronName )
end
return DefenderSquadron
end
--- Set the Squadron visible before startup of the dispatcher.
-- All planes will be spawned as uncontrolled on the parking spot.
-- They will lock the parking spot.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.
-- A2GDispatcher:SetSquadronVisible( "Mineralnye" )
--
-- TODO: disabling because of bug in queueing.
-- function AI_AIR_DISPATCHER:SetSquadronVisible( SquadronName )
--
-- self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
--
-- local DefenderSquadron = self:GetSquadron( SquadronName )
--
-- DefenderSquadron.Uncontrolled = true
-- self:SetSquadronTakeoffFromParkingCold( SquadronName )
-- self:SetSquadronLandingAtEngineShutdown( SquadronName )
--
-- for SpawnTemplate, DefenderSpawn in pairs( self.DefenderSpawns ) do
-- DefenderSpawn:InitUnControlled()
-- end
--
-- end
--- Check if the Squadron is visible before startup of the dispatcher.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #bool true if visible.
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.
-- local IsVisible = A2GDispatcher:IsSquadronVisible( "Mineralnye" )
--
function AI_AIR_DISPATCHER:IsSquadronVisible( SquadronName )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
local DefenderSquadron = self:GetSquadron( SquadronName )
if DefenderSquadron then
return DefenderSquadron.Uncontrolled == true
end
return nil
end
--- @param #AI_AIR_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_AIR_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.
--
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for SEAD tasks.
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for CAS tasks.
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for BAI tasks.
--
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group.
-- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds.
-- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds.
-- @param #number Probability Is not in use, you can skip this parameter.
-- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI".
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
-- A2GDispatcher:SetSquadronPatrolInterval( "Mineralnye", 2, 30, 60, 1, "SEAD" )
--
function AI_AIR_DISPATCHER:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, DefenseTaskType )
local DefenderSquadron = self:GetSquadron( SquadronName )
local Patrol = DefenderSquadron[DefenseTaskType]
if Patrol then
Patrol.LowInterval = LowInterval or 180
Patrol.HighInterval = HighInterval or 600
Patrol.Probability = Probability or 1
Patrol.PatrolLimit = PatrolLimit or 1
Patrol.Scheduler = Patrol.Scheduler or SCHEDULER:New( self )
local Scheduler = Patrol.Scheduler -- Core.Scheduler#SCHEDULER
local ScheduleID = Patrol.ScheduleID
local Variance = ( Patrol.HighInterval - Patrol.LowInterval ) / 2
local Repeat = Patrol.LowInterval + Variance
local Randomization = Variance / Repeat
local Start = math.random( 1, Patrol.HighInterval )
if ScheduleID then
Scheduler:Stop( ScheduleID )
end
Patrol.ScheduleID = Scheduler:Schedule( self, self.SchedulerPatrol, { SquadronName }, Start, Repeat, Randomization )
else
error( "This squadron does not exist:" .. SquadronName )
end
end
--- Set the squadron Patrol parameters for SEAD tasks.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group.
-- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds.
-- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds.
-- @param #number Probability Is not in use, you can skip this parameter.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
-- A2GDispatcher:SetSquadronSeadPatrolInterval( "Mineralnye", 2, 30, 60, 1 )
--
function AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability )
self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "SEAD" )
end
--- Set the squadron Patrol parameters for CAS tasks.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group.
-- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds.
-- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds.
-- @param #number Probability Is not in use, you can skip this parameter.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
-- A2GDispatcher:SetSquadronCasPatrolInterval( "Mineralnye", 2, 30, 60, 1 )
--
function AI_AIR_DISPATCHER:SetSquadronCasPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability )
self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "CAS" )
end
--- Set the squadron Patrol parameters for BAI tasks.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group.
-- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds.
-- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds.
-- @param #number Probability Is not in use, you can skip this parameter.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
-- A2GDispatcher:SetSquadronBaiPatrolInterval( "Mineralnye", 2, 30, 60, 1 )
--
function AI_AIR_DISPATCHER:SetSquadronBaiPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability )
self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "BAI" )
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:GetPatrolDelay( SquadronName )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
self.DefenderSquadrons[SquadronName].Patrol = self.DefenderSquadrons[SquadronName].Patrol or {}
local DefenderSquadron = self:GetSquadron( SquadronName )
local Patrol = self.DefenderSquadrons[SquadronName].Patrol
if Patrol then
return math.random( Patrol.LowInterval, Patrol.HighInterval )
else
error( "This squadron does not exist:" .. SquadronName )
end
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #table DefenderSquadron
function AI_AIR_DISPATCHER:CanPatrol( SquadronName, DefenseTaskType )
self:F({SquadronName = SquadronName})
local DefenderSquadron = self:GetSquadron( SquadronName )
if DefenderSquadron.Captured == false then -- We can only spawn new Patrol if the base has not been captured.
if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources.
local Patrol = DefenderSquadron[DefenseTaskType]
if Patrol and Patrol.Patrol == true then
local PatrolCount = self:CountPatrolAirborne( SquadronName, DefenseTaskType )
self:F( { PatrolCount = PatrolCount, PatrolLimit = Patrol.PatrolLimit, PatrolProbability = Patrol.Probability } )
if PatrolCount < Patrol.PatrolLimit then
local Probability = math.random()
if Probability <= Patrol.Probability then
return DefenderSquadron, Patrol
end
end
else
self:F( "No patrol for " .. SquadronName )
end
end
end
return nil
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #table DefenderSquadron
function AI_AIR_DISPATCHER:CanDefend( SquadronName, DefenseTaskType )
self:F({SquadronName = SquadronName, DefenseTaskType})
local DefenderSquadron = self:GetSquadron( SquadronName )
if DefenderSquadron.Captured == false then -- We can only spawn new defense if the home airbase has not been captured.
if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources.
if DefenderSquadron[DefenseTaskType] and ( DefenderSquadron[DefenseTaskType].Defend == true ) then
return DefenderSquadron, DefenderSquadron[DefenseTaskType]
end
end
end
return nil
end
--- Set the squadron engage limit 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.
--
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for SEAD tasks.
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for CAS tasks.
-- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for BAI tasks.
--
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron.
-- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI".
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronEngageLimit( "Mineralnye", 2, "SEAD" ) -- Engage maximum 2 groups with the enemy for SEAD defense.
--
function AI_AIR_DISPATCHER:SetSquadronEngageLimit( SquadronName, EngageLimit, DefenseTaskType )
local DefenderSquadron = self:GetSquadron( SquadronName )
local Defense = DefenderSquadron[DefenseTaskType]
if Defense then
Defense.EngageLimit = EngageLimit or 1
else
error( "This squadron does not exist:" .. SquadronName )
end
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @usage
--
-- -- SEAD Squadron execution.
-- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200 )
-- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100 )
-- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200 )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.SEAD = DefenderSquadron.SEAD or {}
local Sead = DefenderSquadron.SEAD
Sead.Name = SquadronName
Sead.EngageMinSpeed = EngageMinSpeed
Sead.EngageMaxSpeed = EngageMaxSpeed
Sead.EngageFloorAltitude = EngageFloorAltitude or 500
Sead.EngageCeilingAltitude = EngageCeilingAltitude or 1000
Sead.Defend = true
self:F( { Sead = Sead } )
end
--- Set the squadron SEAD engage limit.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronSeadEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for SEAD defense.
--
function AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit( SquadronName, EngageLimit )
self:SetSquadronEngageLimit( SquadronName, EngageLimit, "SEAD" )
end
--- Set a Sead patrol for a Squadron.
-- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed.
-- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed.
-- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed.
-- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed.
-- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed.
-- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Sead Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
--
function AI_AIR_DISPATCHER:SetSquadronSeadPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.SEAD = DefenderSquadron.SEAD or {}
local SeadPatrol = DefenderSquadron.SEAD
SeadPatrol.Name = SquadronName
SeadPatrol.Zone = Zone
SeadPatrol.PatrolFloorAltitude = FloorAltitude
SeadPatrol.PatrolCeilingAltitude = CeilingAltitude
SeadPatrol.EngageFloorAltitude = FloorAltitude
SeadPatrol.EngageCeilingAltitude = CeilingAltitude
SeadPatrol.PatrolMinSpeed = PatrolMinSpeed
SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed
SeadPatrol.EngageMinSpeed = EngageMinSpeed
SeadPatrol.EngageMaxSpeed = EngageMaxSpeed
SeadPatrol.AltType = AltType
SeadPatrol.Patrol = true
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" )
self:F( { Sead = SeadPatrol } )
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @usage
--
-- -- CAS Squadron execution.
-- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200 )
-- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100 )
-- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200 )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.CAS = DefenderSquadron.CAS or {}
local Cas = DefenderSquadron.CAS
Cas.Name = SquadronName
Cas.EngageMinSpeed = EngageMinSpeed
Cas.EngageMaxSpeed = EngageMaxSpeed
Cas.EngageFloorAltitude = EngageFloorAltitude or 500
Cas.EngageCeilingAltitude = EngageCeilingAltitude or 1000
Cas.Defend = true
self:F( { Cas = Cas } )
end
--- Set the squadron CAS engage limit.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronCasEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for CAS defense.
--
function AI_AIR_DISPATCHER:SetSquadronCasEngageLimit( SquadronName, EngageLimit )
self:SetSquadronEngageLimit( SquadronName, EngageLimit, "CAS" )
end
--- Set a Cas patrol for a Squadron.
-- The Cas patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed.
-- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed.
-- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed.
-- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed.
-- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed.
-- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Cas Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
--
function AI_AIR_DISPATCHER:SetSquadronCasPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.CAS = DefenderSquadron.CAS or {}
local CasPatrol = DefenderSquadron.CAS
CasPatrol.Name = SquadronName
CasPatrol.Zone = Zone
CasPatrol.PatrolFloorAltitude = FloorAltitude
CasPatrol.PatrolCeilingAltitude = CeilingAltitude
CasPatrol.EngageFloorAltitude = FloorAltitude
CasPatrol.EngageCeilingAltitude = CeilingAltitude
CasPatrol.PatrolMinSpeed = PatrolMinSpeed
CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed
CasPatrol.EngageMinSpeed = EngageMinSpeed
CasPatrol.EngageMaxSpeed = EngageMaxSpeed
CasPatrol.AltType = AltType
CasPatrol.Patrol = true
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" )
self:F( { Cas = CasPatrol } )
end
---
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed.
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
-- @usage
--
-- -- BAI Squadron execution.
-- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200 )
-- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100 )
-- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200 )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.BAI = DefenderSquadron.BAI or {}
local Bai = DefenderSquadron.BAI
Bai.Name = SquadronName
Bai.EngageMinSpeed = EngageMinSpeed
Bai.EngageMaxSpeed = EngageMaxSpeed
Bai.EngageFloorAltitude = EngageFloorAltitude or 500
Bai.EngageCeilingAltitude = EngageCeilingAltitude or 1000
Bai.Defend = true
self:F( { Bai = Bai } )
end
--- Set the squadron BAI engage limit.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronBaiEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for BAI defense.
--
function AI_AIR_DISPATCHER:SetSquadronBaiEngageLimit( SquadronName, EngageLimit )
self:SetSquadronEngageLimit( SquadronName, EngageLimit, "BAI" )
end
--- Set a Bai patrol for a Squadron.
-- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed.
-- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed.
-- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed.
-- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed.
-- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed.
-- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed.
-- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed.
-- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Bai Patrol Squadron execution.
-- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) )
-- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 )
--
function AI_AIR_DISPATCHER:SetSquadronBaiPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.BAI = DefenderSquadron.BAI or {}
local BaiPatrol = DefenderSquadron.BAI
BaiPatrol.Name = SquadronName
BaiPatrol.Zone = Zone
BaiPatrol.PatrolFloorAltitude = FloorAltitude
BaiPatrol.PatrolCeilingAltitude = CeilingAltitude
BaiPatrol.EngageFloorAltitude = FloorAltitude
BaiPatrol.EngageCeilingAltitude = CeilingAltitude
BaiPatrol.PatrolMinSpeed = PatrolMinSpeed
BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed
BaiPatrol.EngageMinSpeed = EngageMinSpeed
BaiPatrol.EngageMaxSpeed = EngageMaxSpeed
BaiPatrol.AltType = AltType
BaiPatrol.Patrol = true
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" )
self:F( { Bai = BaiPatrol } )
end
--- Defines the default amount of extra planes that will take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
-- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values:
--
-- * Higher than 1, will increase the defense unit amounts.
-- * Lower than 1, will decrease the defense unit amounts.
--
-- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group
-- multiplied by the Overhead and rounded up to the smallest integer.
--
-- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution.
--
-- See example below.
--
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2.
-- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3.
-- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes.
-- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes.
--
-- A2GDispatcher:SetDefaultOverhead( 1.5 )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultOverhead( Overhead )
self.DefenderDefault.Overhead = Overhead
return self
end
--- Defines the amount of extra planes that will take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
-- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values:
--
-- * Higher than 1, will increase the defense unit amounts.
-- * Lower than 1, will decrease the defense unit amounts.
--
-- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group
-- multiplied by the Overhead and rounded up to the smallest integer.
--
-- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution.
--
-- See example below.
--
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2.
-- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3.
-- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes.
-- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes.
--
-- A2GDispatcher:SetSquadronOverhead( "SquadronName", 1.5 )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.Overhead = Overhead
return self
end
--- Gets the overhead of planes as part of the defense system, in comparison with the attackers.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles...
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
-- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values:
--
-- * Higher than 1, will increase the defense unit amounts.
-- * Lower than 1, will decrease the defense unit amounts.
--
-- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group
-- multiplied by the Overhead and rounded up to the smallest integer.
--
-- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution.
--
-- See example below.
--
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2.
-- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3.
-- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes.
-- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes.
--
-- local SquadronOverhead = A2GDispatcher:GetSquadronOverhead( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:GetSquadronOverhead( SquadronName )
local DefenderSquadron = self:GetSquadron( SquadronName )
return DefenderSquadron.Overhead or self.DefenderDefault.Overhead
end
--- Sets the default grouping of new airplanes spawned.
-- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense.
-- @param #AI_AIR_DISPATCHER self
-- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Set a grouping by default per 2 airplanes.
-- A2GDispatcher:SetDefaultGrouping( 2 )
--
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultGrouping( Grouping )
self.DefenderDefault.Grouping = Grouping
return self
end
--- Sets the grouping of new airplanes spawned.
-- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Set a grouping per 2 airplanes.
-- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 )
--
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.Grouping = Grouping
return self
end
--- Defines the default method at which new flights will spawn and take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default take-off in the air.
-- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air )
--
-- -- Let new flights by default take-off from the runway.
-- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway )
--
-- -- Let new flights by default take-off from the airbase hot.
-- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot )
--
-- -- Let new flights by default take-off from the airbase cold.
-- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold )
--
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoff( Takeoff )
self.DefenderDefault.Takeoff = Takeoff
return self
end
--- Defines the method at which new flights will spawn and take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off in the air.
-- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air )
--
-- -- Let new flights take-off from the runway.
-- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway )
--
-- -- Let new flights take-off from the airbase hot.
-- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot )
--
-- -- Let new flights take-off from the airbase cold.
-- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold )
--
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.Takeoff = Takeoff
return self
end
--- Gets the default method at which new flights will spawn and take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default take-off in the air.
-- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff()
-- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then
-- ...
-- end
--
function AI_AIR_DISPATCHER:GetDefaultTakeoff( )
return self.DefenderDefault.Takeoff
end
--- Gets the method at which new flights will spawn and take-off as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off in the air.
-- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" )
-- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then
-- ...
-- end
--
function AI_AIR_DISPATCHER:GetSquadronTakeoff( SquadronName )
local DefenderSquadron = self:GetSquadron( SquadronName )
return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff
end
--- Sets flights to default take-off in the air, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default take-off in the air.
-- A2GDispatcher:SetDefaultTakeoffInAir()
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoffInAir()
self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Air )
return self
end
--- Sets flights to take-off in the air, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off in the air.
-- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude )
self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Air )
if TakeoffAltitude then
self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude )
end
return self
end
--- Sets flights by default to take-off from the runway, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default take-off from the runway.
-- A2GDispatcher:SetDefaultTakeoffFromRunway()
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoffFromRunway()
self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Runway )
return self
end
--- Sets flights to take-off from the runway, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off from the runway.
-- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName )
self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Runway )
return self
end
--- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default take-off at a hot parking spot.
-- A2GDispatcher:SetDefaultTakeoffFromParkingHot()
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoffFromParkingHot()
self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Hot )
return self
end
--- Sets flights to take-off from the airbase at a hot location, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off in the air.
-- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName )
self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Hot )
return self
end
--- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off from a cold parking spot.
-- A2GDispatcher:SetDefaultTakeoffFromParkingCold()
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoffFromParkingCold()
self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Cold )
return self
end
--- Sets flights to take-off from the airbase at a cold location, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights take-off from a cold parking spot.
-- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName )
self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Cold )
return self
end
--- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.
-- @param #AI_AIR_DISPATCHER self
-- @param #number TakeoffAltitude The altitude in meters above the ground.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Set the default takeoff altitude when taking off in the air.
-- A2GDispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground.
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude )
self.DefenderDefault.TakeoffAltitude = TakeoffAltitude
return self
end
--- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number TakeoffAltitude The altitude in meters above the ground.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Set the default takeoff altitude when taking off in the air.
-- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground.
--
-- @return #AI_AIR_DISPATCHER
--
function AI_AIR_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.TakeoffAltitude = TakeoffAltitude
return self
end
--- Defines the default method at which flights will land and despawn as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default despawn near the airbase when returning.
-- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase )
--
-- -- Let new flights by default despawn after landing land at the runway.
-- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtRunway )
--
-- -- Let new flights by default despawn after landing and parking, and after engine shutdown.
-- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtEngineShutdown )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultLanding( Landing )
self.DefenderDefault.Landing = Landing
return self
end
--- Defines the method at which flights will land and despawn as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights despawn near the airbase when returning.
-- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase )
--
-- -- Let new flights despawn after landing land at the runway.
-- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtRunway )
--
-- -- Let new flights despawn after landing and parking, and after engine shutdown.
-- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtEngineShutdown )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronLanding( SquadronName, Landing )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.Landing = Landing
return self
end
--- Gets the default method at which flights will land and despawn as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights by default despawn near the airbase when returning.
-- local LandingMethod = A2GDispatcher:GetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase )
-- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then
-- ...
-- end
--
function AI_AIR_DISPATCHER:GetDefaultLanding()
return self.DefenderDefault.Landing
end
--- Gets the method at which flights will land and despawn as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let new flights despawn near the airbase when returning.
-- local LandingMethod = A2GDispatcher:GetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase )
-- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then
-- ...
-- end
--
function AI_AIR_DISPATCHER:GetSquadronLanding( SquadronName )
local DefenderSquadron = self:GetSquadron( SquadronName )
return DefenderSquadron.Landing or self.DefenderDefault.Landing
end
--- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights by default to land near the airbase and despawn.
-- A2GDispatcher:SetDefaultLandingNearAirbase()
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultLandingNearAirbase()
self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.NearAirbase )
return self
end
--- Sets flights to land and despawn near the airbase in the air, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights to land near the airbase and despawn.
-- A2GDispatcher:SetSquadronLandingNearAirbase( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName )
self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.NearAirbase )
return self
end
--- Sets flights by default to land and despawn at the runway, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights by default land at the runway and despawn.
-- A2GDispatcher:SetDefaultLandingAtRunway()
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultLandingAtRunway()
self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.AtRunway )
return self
end
--- Sets flights to land and despawn at the runway, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights land at the runway and despawn.
-- A2GDispatcher:SetSquadronLandingAtRunway( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronLandingAtRunway( SquadronName )
self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.AtRunway )
return self
end
--- Sets flights by default to land and despawn at engine shutdown, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights by default land and despawn at engine shutdown.
-- A2GDispatcher:SetDefaultLandingAtEngineShutdown()
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetDefaultLandingAtEngineShutdown()
self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.AtEngineShutdown )
return self
end
--- Sets flights to land and despawn at engine shutdown, as part of the defense system.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @usage:
--
-- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... )
--
-- -- Let flights land and despawn at engine shutdown.
-- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" )
--
-- @return #AI_AIR_DISPATCHER
function AI_AIR_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName )
self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.AtEngineShutdown )
return self
end
--- Set the default fuel treshold when defenders will RTB or Refuel in the air.
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
-- @param #AI_AIR_DISPATCHER self
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default fuel treshold.
-- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
--
function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold )
self.DefenderDefault.FuelThreshold = FuelThreshold
return self
end
--- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default fuel treshold.
-- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
--
function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.FuelThreshold = FuelThreshold
return self
end
--- Set the default tanker where defenders will Refuel in the air.
-- @param #AI_AIR_DISPATCHER self
-- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the default fuel treshold.
-- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
--
-- -- Now Setup the default tanker.
-- A2GDispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor.
function AI_AIR_DISPATCHER:SetDefaultTanker( TankerName )
self.DefenderDefault.TankerName = TankerName
return self
end
--- Set the squadron tanker where defenders will Refuel in the air.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
-- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor.
-- @return #AI_AIR_DISPATCHER
-- @usage
--
-- -- Now Setup the A2G dispatcher, and initialize it using the Detection object.
-- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection )
--
-- -- Now Setup the squadron fuel treshold.
-- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
--
-- -- Now Setup the squadron tanker.
-- A2GDispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor.
function AI_AIR_DISPATCHER:SetSquadronTanker( SquadronName, TankerName )
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.TankerName = TankerName
return self
end
--- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size )
self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName()
self.Defenders[ DefenderName ] = Squadron
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount - Size
end
self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end
--- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender )
self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName()
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize()
end
self.Defenders[ DefenderName ] = nil
self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end
function AI_AIR_DISPATCHER:GetSquadronFromDefender( Defender )
self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName()
self:F( { DefenderName = DefenderName } )
return self.Defenders[ DefenderName ]
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:CountPatrolAirborne( SquadronName, DefenseTaskType )
local PatrolCount = 0
local DefenderSquadron = self.DefenderSquadrons[SquadronName]
if DefenderSquadron then
for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do
if DefenderTask.SquadronName == SquadronName then
if DefenderTask.Type == DefenseTaskType then
if AIGroup:IsAlive() then
-- Check if the Patrol is patrolling or engaging. If not, this is not a valid Patrol, even if it is alive!
-- The Patrol could be damaged, lost control, or out of fuel!
if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" )
or DefenderTask.Fsm:Is( "Started" ) then
PatrolCount = PatrolCount + 1
end
end
end
end
end
end
return PatrolCount
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount )
-- First, count the active AIGroups Units, targetting the DetectedSet
local DefendersEngaged = 0
local DefendersTotal = 0
local AttackerSet = AttackerDetection.Set
local DefendersMissing = AttackerCount
--DetectedSet:Flush()
local DefenderTasks = self:GetDefenderTasks()
for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do
local Defender = DefenderGroup -- Wrapper.Group#GROUP
local DefenderTaskTarget = DefenderTask.Target
local DefenderSquadronName = DefenderTask.SquadronName
local DefenderSize = DefenderTask.Size
-- Count the total of defenders on the battlefield.
--local DefenderSize = Defender:GetInitialSize()
if DefenderTask.Target then
--if DefenderTask.Fsm:Is( "Engaging" ) then
self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize )
DefendersTotal = DefendersTotal + DefenderSize
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
self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize )
else
DefendersEngaged = 0
end
end
--end
end
end
for QueueID, QueueItem in pairs( self.DefenseQueue ) do
local QueueItem = QueueItem -- #AI_AIR_DISPATCHER.DefenseQueueItem
if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID == AttackerDetection.ItemID then
DefendersMissing = DefendersMissing - QueueItem.DefendersNeeded / QueueItem.DefenderSquadron.Overhead
--DefendersEngaged = DefendersEngaged + QueueItem.DefenderGrouping
self:F( { QueueItemName = QueueItem.Defense, QueueItem_ItemID = QueueItem.AttackerDetection.ItemID, DetectedItem = AttackerDetection.ItemID, DefendersMissing = DefendersMissing } )
end
end
self:F( { DefenderCount = DefendersEngaged } )
return DefendersTotal, DefendersEngaged, DefendersMissing
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:CountDefenders( AttackerDetection, DefenderCount, DefenderTaskType )
local Friendlies = nil
local AttackerSet = AttackerDetection.Set
local AttackerCount = AttackerSet:Count()
local DefenderFriendlies = self:GetDefenderFriendliesNearBy( AttackerDetection )
for FriendlyDistance, DefenderFriendlyUnit in UTILS.spairs( DefenderFriendlies or {} ) do
-- We only allow to engage targets as long as the units on both sides are balanced.
if AttackerCount > DefenderCount then
local FriendlyGroup = DefenderFriendlyUnit:GetGroup() -- Wrapper.Group#GROUP
if FriendlyGroup and FriendlyGroup:IsAlive() then
-- Ok, so we have a friendly near the potential target.
-- Now we need to check if the AIGroup has a Task.
local DefenderTask = self:GetDefenderTask( FriendlyGroup )
if DefenderTask then
-- The Task should be of the same type.
if DefenderTaskType == DefenderTask.Type then
-- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet
if DefenderTask.Target == nil then
if DefenderTask.Fsm:Is( "Returning" )
or DefenderTask.Fsm:Is( "Patrolling" ) then
Friendlies = Friendlies or {}
Friendlies[FriendlyGroup] = FriendlyGroup
DefenderCount = DefenderCount + FriendlyGroup:GetSize()
self:F( { Friendly = FriendlyGroup:GetName(), FriendlyDistance = FriendlyDistance } )
end
end
end
end
end
else
break
end
end
return Friendlies
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded )
local SquadronName = DefenderSquadron.Name
DefendersNeeded = DefendersNeeded or 4
local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping
DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded
if self:IsSquadronVisible( SquadronName ) then
-- Here we Patrol the new planes.
-- The Resources table is filled in advance.
local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template.
-- We determine the grouping based on the parameters set.
self:F( { DefenderGrouping = DefenderGrouping } )
-- New we will form the group to spawn in.
-- We search for the first free resource matching the template.
local DefenderUnitIndex = 1
local DefenderPatrolTemplate = nil
local DefenderName = nil
for GroupName, DefenderGroup in pairs( DefenderSquadron.Resources[TemplateID] or {} ) do
self:F( { GroupName = GroupName } )
local DefenderTemplate = _DATABASE:GetGroupTemplate( GroupName )
if DefenderUnitIndex == 1 then
DefenderPatrolTemplate = UTILS.DeepCopy( DefenderTemplate )
self.DefenderPatrolIndex = self.DefenderPatrolIndex + 1
--DefenderPatrolTemplate.name = SquadronName .. "#" .. self.DefenderPatrolIndex .. "#" .. GroupName
DefenderPatrolTemplate.name = GroupName
DefenderName = DefenderPatrolTemplate.name
else
-- Add the unit in the template to the DefenderPatrolTemplate.
local DefenderUnitTemplate = DefenderTemplate.units[1]
DefenderPatrolTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate
end
DefenderPatrolTemplate.units[DefenderUnitIndex].name = string.format( DefenderPatrolTemplate.name .. '-%02d', DefenderUnitIndex )
DefenderPatrolTemplate.units[DefenderUnitIndex].unitId = nil
DefenderUnitIndex = DefenderUnitIndex + 1
DefenderSquadron.Resources[TemplateID][GroupName] = nil
if DefenderUnitIndex > DefenderGrouping then
break
end
end
if DefenderPatrolTemplate then
local TakeoffMethod = self:GetSquadronTakeoff( SquadronName )
local SpawnGroup = GROUP:Register( DefenderName )
DefenderPatrolTemplate.lateActivation = nil
DefenderPatrolTemplate.uncontrolled = nil
local Takeoff = self:GetSquadronTakeoff( SquadronName )
DefenderPatrolTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
DefenderPatrolTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
local Defender = _DATABASE:Spawn( DefenderPatrolTemplate )
self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping )
Defender:Activate()
return Defender, DefenderGrouping
end
else
local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN
if DefenderGrouping then
Spawn:InitGrouping( DefenderGrouping )
else
Spawn:InitGrouping()
end
local TakeoffMethod = self:GetSquadronTakeoff( SquadronName )
local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP
self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping )
return Defender, DefenderGrouping
end
return nil, nil
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:onafterPatrol( From, Event, To, SquadronName, DefenseTaskType )
local DefenderSquadron, Patrol = self:CanPatrol( SquadronName, DefenseTaskType )
-- Determine if there are sufficient resources to form a complete group for patrol.
if DefenderSquadron then
local DefendersNeeded
local DefendersGrouping = ( DefenderSquadron.Grouping or self.DefenderDefault.Grouping )
if DefenderSquadron.ResourceCount == nil then
DefendersNeeded = DefendersGrouping
else
if DefenderSquadron.ResourceCount >= DefendersGrouping then
DefendersNeeded = DefendersGrouping
else
DefendersNeeded = DefenderSquadron.ResourceCount
end
end
if Patrol then
self:ResourceQueue( true, DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, nil, SquadronName )
end
end
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ResourceQueue( Patrol, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName )
self:F( { DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName } )
local DefenseQueueItem = {} -- #AI_AIR_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_AIR_DISPATCHER self
function AI_AIR_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_AIR_DISPATCHER self
function AI_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, "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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." )
Dispatcher:ClearDefenderTaskTarget( Defender )
end
--- @param #AI_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if Defender:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
end
end
--- @param #AI_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." )
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
end
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_AIR_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
Dispatcher:ResourcePark( Squadron, Defender )
end
end
end
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_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_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender )
self:F( { DefenderTarget = DefenderTarget } )
if DefenderTarget then
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." )
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
end
end
function Fsm:OnAfterEngageRoute( Defender, From, Event, To, AttackSetUnit )
self:F({"Engage Route", Defender:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = Defender:GetName()
local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route, bearing " .. Coordinate:ToString( Defender ) )
end
function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit )
self:F({"Engage Route", Defender:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = Defender:GetName()
local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging target, bearing " .. Coordinate:ToString( Defender ) )
end
function Fsm:onafterRTB( Defender, From, Event, To )
self:F({"Defender RTB", Defender:GetName()})
local DefenderName = Defender:GetName()
local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " RTB." )
self:GetParent(self).onafterRTB( self, Defender, From, Event, To )
Dispatcher:ClearDefenderTaskTarget( Defender )
end
--- @param #AI_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if Defender:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
end
end
--- @param #AI_AIR_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_AIR_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender )
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." )
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
end
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_AIR_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy()
Dispatcher:ResourcePark( Squadron, Defender )
end
end
end
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:onafterEngage( From, Event, To, AttackerDetection, Defenders )
if Defenders then
for DefenderID, Defender in pairs( Defenders or {} ) do
local Fsm = self:GetDefenderTaskFsm( Defender )
Fsm:Engage( AttackerDetection.Set ) -- Engage on the TargetSetUnit
self:SetDefenderTaskTarget( Defender, AttackerDetection )
end
end
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:HasDefenseLine( DefenseCoordinate, DetectedItem )
local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem )
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
-- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates.
-- (y1 y2)x + (x2 x1)y + (x1y2 x2y1) = 0
local c1 = DefenseCoordinate
local c2 = AttackCoordinate
local a = c1.z - c2.z -- Calculate a
local b = c2.x - c1.x -- Calculate b
local c = c1.x * c2.z - c2.x * c1.z -- calculate c
local ok = true
-- Now we check if each coordinate radius of about 30km of each attack is crossing a defense line. If yes, then this is not a good attack!
for AttackItemID, CheckAttackItem in pairs( self.Detection:GetDetectedItems() ) do
-- Only compare other detected coordinates.
if AttackItemID ~= DetectedItem.ID then
local CheckAttackCoordinate = self.Detection:GetDetectedItemCoordinate( CheckAttackItem )
local x = CheckAttackCoordinate.x
local y = CheckAttackCoordinate.z
local r = 5000
-- now we check if the coordinate is intersecting with the defense line.
local IntersectDistance = ( math.abs( a * x + b * y + c ) ) / math.sqrt( a * a + b * b )
self:F( { IntersectDistance = IntersectDistance, x = x, y = y } )
local IntersectAttackDistance = CheckAttackCoordinate:Get2DDistance( DefenseCoordinate )
self:F( { IntersectAttackDistance=IntersectAttackDistance, EvaluateDistance=EvaluateDistance } )
-- If the distance of the attack coordinate is larger than the test radius; then the line intersects, and this is not a good coordinate.
if IntersectDistance < r and IntersectAttackDistance < EvaluateDistance then
ok = false
break
end
end
end
return ok
end
---
-- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:onafterDefend( From, Event, To, DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, DefenderFriendlies, DefenseTaskType )
self:F( { From, Event, To, DetectedItem.Index, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing, DefenderFriendlies = DefenderFriendlies } )
DetectedItem.Type = DefenseTaskType -- This is set to report the task type in the status panel.
local AttackerSet = DetectedItem.Set
local AttackerUnit = AttackerSet:GetFirst()
if AttackerUnit and AttackerUnit:IsAlive() then
local AttackerCount = AttackerSet:Count()
local DefenderCount = 0
for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do
-- Here we check if the defenders have a defense line to the attackers.
-- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage.
local DefenseCoordinate = DefenderGroup:GetCoordinate()
local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem )
if HasDefenseLine == true then
local SquadronName = self:GetDefenderTask( DefenderGroup ).SquadronName
local SquadronOverhead = self:GetSquadronOverhead( SquadronName )
local Fsm = self:GetDefenderTaskFsm( DefenderGroup )
Fsm:EngageRoute( AttackerSet ) -- Engage on the TargetSetUnit
self:SetDefenderTaskTarget( DefenderGroup, DetectedItem )
local DefenderGroupSize = DefenderGroup:GetSize()
DefendersMissing = DefendersMissing - DefenderGroupSize / SquadronOverhead
DefendersTotal = DefendersTotal + DefenderGroupSize / SquadronOverhead
end
if DefendersMissing <= 0 then
break
end
end
self:F( { DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } )
DefenderCount = DefendersMissing
local ClosestDistance = 0
local ClosestDefenderSquadronName = nil
local BreakLoop = false
while( DefenderCount > 0 and not BreakLoop ) do
self:F( { DefenderSquadrons = self.DefenderSquadrons } )
for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do
if DefenderSquadron[DefenseTaskType] then
local AirbaseCoordinate = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE
local AttackerCoord = AttackerUnit:GetCoordinate()
local InterceptCoord = DetectedItem.InterceptCoord
self:F( { InterceptCoord = InterceptCoord } )
if InterceptCoord then
local InterceptDistance = AirbaseCoordinate:Get2DDistance( InterceptCoord )
local AirbaseDistance = AirbaseCoordinate:Get2DDistance( AttackerCoord )
self:F( { InterceptDistance = InterceptDistance, AirbaseDistance = AirbaseDistance, InterceptCoord = InterceptCoord } )
if ClosestDistance == 0 or InterceptDistance < ClosestDistance then
-- Only intercept if the distance to target is smaller or equal to the GciRadius limit.
if AirbaseDistance <= self.DefenseRadius then
-- Check if there is a defense line...
local HasDefenseLine = self:HasDefenseLine( AirbaseCoordinate, DetectedItem )
if HasDefenseLine == true then
ClosestDistance = InterceptDistance
ClosestDefenderSquadronName = SquadronName
end
end
end
end
end
end
if ClosestDefenderSquadronName then
local DefenderSquadron, Defense = self:CanDefend( ClosestDefenderSquadronName, DefenseTaskType )
if Defense then
local DefenderOverhead = DefenderSquadron.Overhead or self.DefenderDefault.Overhead
local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping
local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead )
self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } )
self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } )
self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } )
-- Validate that the maximum limit of Defenders has been reached.
-- If yes, then cancel the engaging of more defenders.
local DefendersLimit = DefenderSquadron.EngageLimit or self.DefenderDefault.EngageLimit
if DefendersLimit then
if DefendersTotal >= DefendersLimit then
DefendersNeeded = 0
BreakLoop = true
else
-- If the total of amount of defenders + the defenders needed, is larger than the limit of defenders,
-- then the defenders needed is the difference between defenders total - defenders limit.
if DefendersTotal + DefendersNeeded > DefendersLimit then
DefendersNeeded = DefendersLimit - DefendersTotal
end
end
end
-- DefenderSquadron.ResourceCount can have the value nil, which expresses unlimited resources.
-- DefendersNeeded cannot exceed DefenderSquadron.ResourceCount!
if DefenderSquadron.ResourceCount and DefendersNeeded > DefenderSquadron.ResourceCount then
DefendersNeeded = DefenderSquadron.ResourceCount
BreakLoop = true
end
while ( DefendersNeeded > 0 ) do
self:ResourceQueue( false, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, DetectedItem, ClosestDefenderSquadronName )
DefendersNeeded = DefendersNeeded - DefenderGrouping
DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead
end -- while ( DefendersNeeded > 0 ) do
else
-- No more resources, try something else.
-- Subject for a later enhancement to try to depart from another squadron and disable this one.
BreakLoop = true
break
end
else
-- There isn't any closest airbase anymore, break the loop.
break
end
end -- if DefenderSquadron then
end -- if AttackerUnit
end
--- Creates an SEAD task when the targets have radars.
-- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return Core.Set#SET_UNIT The set of units of the targets to be engaged.
-- @return #nil If there are no targets to be set.
function AI_AIR_DISPATCHER:Evaluate_SEAD( DetectedItem )
self:F( { DetectedItem.ItemID } )
local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT
local AttackerCount = AttackerSet:HasSEAD() -- Is the AttackerSet a SEAD group, then the amount of radar emitters will be returned; that need to be attacked.
if ( AttackerCount > 0 ) then
-- First, count the active defenders, engaging the DetectedItem.
local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount )
self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "SEAD" )
if DetectedItem.IsDetected == true then
return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups
end
end
return nil, nil, nil
end
--- Creates an CAS task.
-- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return Core.Set#SET_UNIT The set of units of the targets to be engaged.
-- @return #nil If there are no targets to be set.
function AI_AIR_DISPATCHER:Evaluate_CAS( DetectedItem )
self:F( { DetectedItem.ItemID } )
local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT
local AttackerCount = AttackerSet:Count()
local AttackerRadarCount = AttackerSet:HasSEAD()
local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
local IsCas = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == true ) -- Is the AttackerSet a CAS group?
if IsCas == true then
-- First, count the active defenders, engaging the DetectedItem.
local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount )
self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "CAS" )
if DetectedItem.IsDetected == true then
return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups
end
end
return nil, nil, nil
end
--- Evaluates an BAI task.
-- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return Core.Set#SET_UNIT The set of units of the targets to be engaged.
-- @return #nil If there are no targets to be set.
function AI_AIR_DISPATCHER:Evaluate_BAI( DetectedItem )
self:F( { DetectedItem.ItemID } )
local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT
local AttackerCount = AttackerSet:Count()
local AttackerRadarCount = AttackerSet:HasSEAD()
local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
local IsBai = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == false ) -- Is the AttackerSet a BAI group?
if IsBai == true then
-- First, count the active defenders, engaging the DetectedItem.
local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount )
self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "BAI" )
if DetectedItem.IsDetected == true then
return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups
end
end
return nil, nil, nil
end
--- Assigns A2G AI Tasks in relation to the detected items.
-- @param #AI_AIR_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function AI_AIR_DISPATCHER:ProcessDetected( Detection )
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
local TaskReport = REPORT:New()
for DefenderGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do
local DefenderGroup = DefenderGroup -- Wrapper.Group#GROUP
local DefenderTaskFsm = self:GetDefenderTaskFsm( DefenderGroup )
--if DefenderTaskFsm:Is( "LostControl" ) then
-- self:ClearDefenderTask( DefenderGroup )
--end
if not DefenderGroup:IsAlive() then
self:F( { Defender = DefenderGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } )
if not DefenderTaskFsm:Is( "Started" ) then
self:ClearDefenderTask( DefenderGroup )
end
else
if DefenderTask.Target then
local AttackerItem = Detection:GetDetectedItemByIndex( DefenderTask.Target.Index )
if not AttackerItem then
self:F( { "Removing obsolete Target:", DefenderTask.Target.Index } )
self:ClearDefenderTaskTarget( DefenderGroup )
else
if DefenderTask.Target.Set then
local TargetCount = DefenderTask.Target.Set:Count()
if TargetCount == 0 then
self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } )
self:ClearDefenderTask( DefenderGroup )
end
end
end
end
end
end
local Report = REPORT:New( "\nTactical Overview" )
local DefenderGroupCount = 0
local DefendersTotal = 0
-- Now that all obsolete tasks are removed, loop through the detected targets.
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedCount = DetectedSet:Count()
local DetectedZone = DetectedItem.Zone
self:F( { "Target ID", DetectedItem.ItemID } )
DetectedSet:Flush( self )
local DetectedID = DetectedItem.ID
local DetectionIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem )
-- Calculate if for this DetectedItem if a defense needs to be initiated.
-- This calculation is based on the distance between the defense point and the attackers, and the defensiveness parameter.
-- The attackers closest to the defense coordinates will be handled first, or course!
local EngageCoordinate = nil
for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do
local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
if EvaluateDistance <= self.DefenseRadius then
local DistanceProbability = ( self.DefenseRadius / EvaluateDistance * self.DefenseReactivity )
local DefenseProbability = math.random()
self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } )
if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then
EngageCoordinate = DefenseCoordinate
break
end
end
end
if EngageCoordinate then
do
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged...
if DefendersMissing and DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD", EngageCoordinate )
end
end
do
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged...
if DefendersMissing and DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS", EngageCoordinate )
end
end
do
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_BAI( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged...
if DefendersMissing and DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI", EngageCoordinate )
end
end
end
-- do
-- local DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem )
-- if DefendersMissing and DefendersMissing > 0 then
-- self:F( { DefendersMissing = DefendersMissing } )
-- self:CAS( DetectedItem, DefendersMissing, Friendlies )
-- end
-- end
if self.TacticalDisplay then
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
if Defender:IsAlive() then
DefenderGroupCount = DefenderGroupCount + 1
local Fuel = Defender:GetFuelMin() * 100
local Damage = Defender:GetLife() / Defender:GetLife0() * 100
Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask() == true and "Executing" or "Idle" ) )
end
end
end
end
end
if self.TacticalDisplay then
Report:Add( "\n - No Targets:")
local TaskCount = 0
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
TaskCount = TaskCount + 1
local Defender = Defender -- Wrapper.Group#GROUP
if not DefenderTask.Target then
if Defender:IsAlive() then
local DefenderHasTask = Defender:HasTask()
local Fuel = Defender:GetFuelMin() * 100
local Damage = Defender:GetLife() / Defender:GetLife0() * 100
DefenderGroupCount = DefenderGroupCount + 1
Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask() == true and "Executing" or "Idle" ) )
end
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_AIR_DISPATCHER.DefenseQueueItem
Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) )
end
Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) )
for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do
Report:Add( string.format( " - %s - %d", DefenderSquadronName, DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount or "n/a" ) )
end
self:F( Report:Text( "\n" ) )
trigger.action.outText( Report:Text( "\n" ), 25 )
end
return true
end
end
do
--- Calculates which HUMAN friendlies are nearby the area.
-- @param #AI_AIR_DISPATCHER self
-- @param DetectedItem The detected item.
-- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type.
function AI_AIR_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set
local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem )
local PlayerTypes = {}
local PlayersCount = 0
if PlayersNearBy then
local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G()
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
local PlayerName = PlayerUnit:GetPlayerName()
--self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
PlayersCount = PlayersCount + 1
local PlayerType = PlayerUnit:GetTypeName()
PlayerTypes[PlayerName] = PlayerType
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
end
end
end
end
--self:F( { PlayersCount = PlayersCount } )
local PlayerTypesReport = REPORT:New()
if PlayersCount > 0 then
for PlayerName, PlayerType in pairs( PlayerTypes ) do
PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) )
end
else
PlayerTypesReport:Add( "-" )
end
return PlayersCount, PlayerTypesReport
end
--- Calculates which friendlies are nearby the area.
-- @param #AI_AIR_DISPATCHER self
-- @param DetectedItem The detected item.
-- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type.
function AI_AIR_DISPATCHER:GetFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem )
local FriendlyTypes = {}
local FriendliesCount = 0
if FriendlyUnitsNearBy then
local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G()
for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do
local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT
if FriendlyUnit:IsAirPlane() then
local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel()
FriendliesCount = FriendliesCount + 1
local FriendlyType = FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
end
end
end
end
--self:F( { FriendliesCount = FriendliesCount } )
local FriendlyTypesReport = REPORT:New()
if FriendliesCount > 0 then
for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do
FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) )
end
else
FriendlyTypesReport:Add( "-" )
end
return FriendliesCount, FriendlyTypesReport
end
--- Schedules a new Patrol for the given SquadronName.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
function AI_AIR_DISPATCHER:SchedulerPatrol( SquadronName )
local PatrolTaskTypes = { "SEAD", "CAS", "BAI" }
local PatrolTaskType = PatrolTaskTypes[math.random(1,3)]
self:Patrol( SquadronName, PatrolTaskType )
end
end