From ea330128ca4bec4864c8fbcc2ee9d1d54456db8f Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:47:29 +0100 Subject: [PATCH] Delete Moose Development/Moose/AI directory Prep master-ng branch --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 220 - .../Moose/AI/AI_A2A_Dispatcher.lua | 4520 ---------------- Moose Development/Moose/AI/AI_A2A_Gci.lua | 149 - Moose Development/Moose/AI/AI_A2A_Patrol.lua | 411 -- Moose Development/Moose/AI/AI_A2G_BAI.lua | 98 - Moose Development/Moose/AI/AI_A2G_CAS.lua | 98 - .../Moose/AI/AI_A2G_Dispatcher.lua | 4794 ----------------- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 133 - Moose Development/Moose/AI/AI_Air.lua | 841 --- .../Moose/AI/AI_Air_Dispatcher.lua | 3241 ----------- Moose Development/Moose/AI/AI_Air_Engage.lua | 595 -- Moose Development/Moose/AI/AI_Air_Patrol.lua | 393 -- .../Moose/AI/AI_Air_Squadron.lua | 296 - Moose Development/Moose/AI/AI_BAI.lua | 653 --- Moose Development/Moose/AI/AI_Balancer.lua | 308 -- Moose Development/Moose/AI/AI_CAP.lua | 543 -- Moose Development/Moose/AI/AI_CAS.lua | 572 -- Moose Development/Moose/AI/AI_Cargo.lua | 591 -- Moose Development/Moose/AI/AI_Cargo_APC.lua | 609 --- .../Moose/AI/AI_Cargo_Airplane.lua | 512 -- .../Moose/AI/AI_Cargo_Dispatcher.lua | 1240 ----- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 265 - .../Moose/AI/AI_Cargo_Dispatcher_Airplane.lua | 171 - .../AI/AI_Cargo_Dispatcher_Helicopter.lua | 201 - .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 200 - .../Moose/AI/AI_Cargo_Helicopter.lua | 663 --- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 404 -- Moose Development/Moose/AI/AI_Escort.lua | 2193 -------- .../Moose/AI/AI_Escort_Dispatcher.lua | 192 - .../Moose/AI/AI_Escort_Dispatcher_Request.lua | 153 - .../Moose/AI/AI_Escort_Request.lua | 323 -- Moose Development/Moose/AI/AI_Formation.lua | 1281 ----- Moose Development/Moose/AI/AI_Patrol.lua | 950 ---- 33 files changed, 27813 deletions(-) delete mode 100644 Moose Development/Moose/AI/AI_A2A_Cap.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Gci.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Patrol.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_BAI.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_CAS.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_SEAD.lua delete mode 100644 Moose Development/Moose/AI/AI_Air.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Engage.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Patrol.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Squadron.lua delete mode 100644 Moose Development/Moose/AI/AI_BAI.lua delete mode 100644 Moose Development/Moose/AI/AI_Balancer.lua delete mode 100644 Moose Development/Moose/AI/AI_CAP.lua delete mode 100644 Moose Development/Moose/AI/AI_CAS.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_APC.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Airplane.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Helicopter.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Ship.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Request.lua delete mode 100644 Moose Development/Moose/AI/AI_Formation.lua delete mode 100644 Moose Development/Moose/AI/AI_Patrol.lua diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua deleted file mode 100644 index 08c4a7273..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ /dev/null @@ -1,220 +0,0 @@ ---- **AI** - Models the process of Combat Air Patrol (CAP) for airplanes. --- --- This is a class used in the @{AI.AI_A2A_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Cap --- @image AI_Combat_Air_Patrol.JPG - --- @type AI_A2A_CAP --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE - ---- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1. AI_A2A_CAP constructor --- --- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object. --- --- ## 2. AI_A2A_CAP is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_A2A_CAP States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2 AI_A2A_CAP Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_A2A_CAP.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_A2A_CAP.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{#AI_A2A_CAP.SetEngageRange}() to define that range. --- --- ## 4. Set the Zone of Engagement --- --- ![Zone](..\Presentations\AI_CAP\Dia12.JPG) --- --- An optional @{Core.Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{#AI_A2A_CAP.SetEngageZone}() to define that Zone. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2A_CAP -AI_A2A_CAP = { - ClassName = "AI_A2A_CAP", -} - ---- Creates a new AI_A2A_CAP object --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_A2A_CAP -function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - - -- Multiple inheritance ... :-) - local AI_Air = AI_AIR:New( AICap ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - - return self -end - ---- Creates a new AI_A2A_CAP object --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2A_CAP -function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType ) - - return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - -end - ---- onafter State Transition for Event Patrol. --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onafterStart( AICap, From, Event, To ) - - self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To ) - AICap:HandleEvent( EVENTS.Takeoff, nil, self ) - -end - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_A2A_CAP self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_A2A_CAP self -function AI_A2A_CAP:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_A2A_CAP self --- @param #number EngageRange The Engage Range. --- @return #AI_A2A_CAP self -function AI_A2A_CAP:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2A_CAP self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2A_CAP self -function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then - -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() - -- Maybe the detected set also contains - self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua deleted file mode 100644 index 8e8e3df05..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ /dev/null @@ -1,4520 +0,0 @@ ---- **AI** - Manages the process of an automatic A2A defense system based on an EWR network targets and coordinating CAP and GCI. --- --- === --- --- Features: --- --- * Setup quickly an A2A defense system for a coalition. --- * Setup (CAP) Control Air Patrols at defined zones to enhance your A2A defenses. --- * Setup (GCI) Ground Control Intercept at defined airbases to enhance your A2A defenses. --- * Define and use an EWR (Early Warning Radar) network. --- * Define squadrons at airbases. --- * Enable airbases for A2A defenses. --- * Add different plane types to different squadrons. --- * Add multiple squadrons to different airbases. --- * Define different ranges to engage upon intruders. --- * Establish an automatic in air refuel process for CAP using refuel tankers. --- * Setup default settings for all squadrons and A2A defenses. --- * Setup specific settings for specific squadrons. --- * Quickly setup an A2A defense system using @{#AI_A2A_GCICAP}. --- * Setup a more advanced defense system using @{#AI_A2A_DISPATCHER}. --- --- === --- --- ## Missions: --- --- [AID-A2A - AI A2A Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) --- --- === --- --- ![Banner Image](..\Images\deprecated.png) --- --- # QUICK START GUIDE --- --- There are basically two classes available to model an A2A defense system. --- --- AI\_A2A\_DISPATCHER is the main A2A defense class that models the A2A defense system. --- AI\_A2A\_GCICAP derives or inherits from AI\_A2A\_DISPATCHER and is a more **noob** user friendly class, but is less flexible. --- --- Before you start using the AI\_A2A\_DISPATCHER or AI\_A2A\_GCICAP ask yourself the following questions. --- --- ## 0. Do I need AI\_A2A\_DISPATCHER or do I need AI\_A2A\_GCICAP? --- --- AI\_A2A\_GCICAP, automates a lot of the below questions using the mission editor and requires minimal lua scripting. --- But the AI\_A2A\_GCICAP provides less flexibility and a lot of options are defaulted. --- With AI\_A2A\_DISPATCHER you can setup a much more **fine grained** A2A defense mechanism, but some more (easy) lua scripting is required. --- --- ## 1. Which Coalition am I modeling an A2A defense system for? blue or red? --- --- One AI\_A2A\_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI\_A2A\_DISPATCHER **objects**, --- each governing their defense system. --- --- --- ## 2. Which type of EWR will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the EWR detection. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple FACs as one co-operating entity. --- * Communicate with a Head Quarters, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- ## 3. Which EWR units will be used as part of the detection system? Only Ground or also Airborne? --- --- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. --- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). --- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. --- The position of these units is very important as they need to provide enough coverage --- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. --- --- ## 4. Is a border required? --- --- Is this a cold war or a hot war situation? In case of a cold war situation, a border can be set that will only trigger defenses --- if the border is crossed by enemy units. --- --- ## 5. What maximum range needs to be checked to allow defenses to engage any attacker? --- --- A good functioning defense will have a "maximum range" evaluated to the enemy when CAP will be engaged or GCI will be spawned. --- --- ## 6. Which Airbases, Carrier Ships, FARPs will take part in the defense system for the Coalition? --- --- Carefully plan which airbases will take part in the coalition. Color each airbase in the color of the coalition. --- --- ## 7. Which Squadrons will I create and which name will I give each Squadron? --- --- The defense system works with Squadrons. Each Squadron must be given a unique name, that forms the **key** to the defense system. --- Several options and activities can be set per Squadron. --- --- ## 8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? --- --- Squadrons are placed as the "home base" on an airfield, carrier or farp. --- Carefully plan where each Squadron will be located as part of the defense system. --- --- ## 9. Which plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The A2A defense system will select from the given templates a random template to spawn a new plane (group). --- --- ## 10. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The A2A defense system will select from the given templates a random template to spawn a new plane (group). --- --- ## 11. For each Squadron, which will perform CAP? --- --- Per Squadron, evaluate which Squadrons will perform CAP. --- Not all Squadrons need to perform CAP. --- --- ## 12. For each Squadron doing CAP, in which ZONE(s) will the CAP be performed? --- --- Per CAP, evaluate **where** the CAP will be performed, in other words, define the **zone**. --- Near the border or a bit further away? --- --- ## 13. For each Squadron doing CAP, which zone types will I create? --- --- Per CAP zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- ## 14. For each Squadron doing CAP, what are the time intervals and CAP amounts to be performed? --- --- For each CAP: --- --- * **How many** CAP you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new CAP? --- --- ## 15. For each Squadron, which will perform GCI? --- --- For each Squadron, evaluate which Squadrons will perform GCI? --- Not all Squadrons need to perform GCI. --- --- ## 16. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- --- ## 17. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is despawn when near the airbase when returning.** --- --- ## 18. For each Squadron, which overhead will I use? --- --- For each Squadron, depending on the airplane type (modern, old) and payload, which overhead is required to provide any defense? --- In other words, if **X** attacker airplanes are detected, how many **Y** defense airplanes need to be spawned per squadron? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. --- The overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A value greater than 1, like 1.5 will increase the overhead with 50%, a value smaller than 1, like 0.5 will decrease the overhead with 50%.** --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defense airplanes be grouped when multiple defense airplanes are spawned for multiple attackers? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- ### Authors: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). --- ### Authors: **Stonehouse**, **SNAFU** in terms of the advice, documentation, and the original GCICAP script. --- --- @module AI.AI_A2A_Dispatcher --- @image AI_Air_To_Air_Dispatching.JPG - -do -- AI_A2A_DISPATCHER - - --- AI_A2A_DISPATCHER class. - -- @type AI_A2A_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Create an automatic air defence system for a coalition. - -- - -- === - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) - -- - -- It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. - -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept detected enemy aircraft or they run short of fuel and must return to base (RTB). When a CAP flight leaves their zone to perform an interception or return to base a new CAP flight will spawn to take their place. - -- If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. - -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. - -- In short it is a plug in very flexible and configurable air defence module for DCS World. - -- - -- Note that in order to create a two way A2A defense system, two AI\_A2A\_DISPATCHER defense system may need to be created, for each coalition one. - -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- ## 1. AI\_A2A\_DISPATCHER constructor: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_1.JPG) - -- - -- - -- The @{#AI_A2A_DISPATCHER.New}() method creates a new AI\_A2A\_DISPATCHER instance. - -- - -- ### 1.1. Define the **EWR network**: - -- - -- As part of the AI\_A2A\_DISPATCHER :New() constructor, an EWR network must be given as the first parameter. - -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia5.JPG) - -- - -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. - -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. - -- The position of these units is very important as they need to provide enough coverage - -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia7.JPG) - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- - -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the AI\_A2A\_DISPATCHER class. - -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, - -- increasing or decreasing the radar coverage of the Early Warning System. - -- - -- See the following example to setup an EWR network containing EWR stations and AWACS. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_2.JPG) - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_3.JPG) - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the EWR network. - -- -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Setup the detection and group targets to a 30km range! - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with **DF CCCP AWACS** or **DF CCCP EWR** to be included in the Set. - -- **DetectionSetGroup** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. - -- - -- Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is chosen, which is 30km. - -- The **Detection** object is then passed to the @{#AI_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A defense detection mechanism. - -- - -- You could build a **mutual defense system** like this: - -- - -- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red ) - -- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue ) - -- - -- ### 1.2. Define the detected **target grouping radius**: - -- - -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. - -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. - -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! - -- - -- ## 3. Set the **Engage Radius**: - -- - -- Define the **Engage Radius** to **engage any target by airborne friendlies**, - -- which are executing **cap** or **returning** from an intercept mission. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia10.JPG) - -- - -- If there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- - -- For example, if **50000** or **50km** is given as a value, then any friendly that is airborne within **50km** from the detected target, - -- will be considered to receive the command to engage that target area. - -- - -- You need to evaluate the value of this parameter carefully: - -- - -- * If too small, more intercept missions may be triggered upon detected target areas. - -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- - -- The **default** Engage Radius is defined as **100000** or **100km**. - -- Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to set a specific Engage Radius. - -- **The Engage Radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-019%20-%20Engage%20Range%20Test) - -- - -- In this example an Engage Radius is set to various values. - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - -- - -- ## 4. Set the **Ground Controlled Intercept Radius** or **Gci radius**: - -- - -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. - -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** - -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. - -- - -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, - -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. - -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-013%20-%20Intercept%20Test) - -- - -- In these examples, the Gci Radius is set to various values: - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. - -- A2ADispatcher:SetGciRadius( 100000 ) - -- - -- -- Set 200km as the radius to ground control intercept. - -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. - -- - -- ## 5. Set the **borders**: - -- - -- According to the tactical and strategic design of the mission broadly decide the shape and extent of red and blue territories. - -- They should be laid out such that a border area is created between the two coalitions. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia4.JPG) - -- - -- **Define a border area to simulate a cold war scenario.** - -- Use the method @{#AI_A2A_DISPATCHER.SetBorderZone}() to create a border zone for the dispatcher. - -- - -- A **cold war** is one where CAP 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 CAP 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 CAP and GCI aircraft to attack it. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia9.JPG) - -- - -- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE}. - -- 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. - -- - -- Demonstration Mission: [AID-009 - AI_A2A - Border Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-009%20-%20Border%20Test) - -- - -- In this example a border is set for the CCCP A2A dispatcher: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_4.JPG) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Setup the border. - -- -- Initialize the dispatcher, setting up a border zone. This is a polygon, - -- -- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area. - -- -- Any enemy crossing this border will be engaged. - -- - -- CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) - -- A2ADispatcher:SetBorderZone( CCCPBorderZone ) - -- - -- ## 6. Squadrons: - -- - -- The AI\_A2A\_DISPATCHER works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, - -- while defining which plane types are being used by the squadron and how many resources are available. - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or key of the squadron. - -- * Have specific plane types. - -- * Are located at one airbase. - -- * 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\_A2A\_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. - -- - -- This example defines a couple of squadrons. Note the templates defined within the Mission Editor. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_5.JPG) - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_6.JPG) - -- - -- -- Setup the squadrons. - -- A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 ) - -- - -- ### 6.1. Set squadron take-off methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the airfield: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default take-off method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 6.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- ### 6.2. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 6.3. Set squadron grouping - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() to set the grouping of CAP or GCI flights that will take-off when spawned. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia12.JPG) - -- - -- In the case of GCI, the @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() method has additional behavior. When there aren't enough CAP flights airborne, a GCI will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for GCI are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by CAP or any airborne flight, - -- a GCI needs to be started, the GCI flights will be grouped as follows: Group 1 of 2 flights and Group 2 of one flight! - -- - -- Even more ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 6.4. Overhead and Balance the effectiveness of the air defenses in case of GCI. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia11.JPG) - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronOverhead}() 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 A2A missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the @{#AI_A2A_DISPATCHER.SetOverhead}() 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.0, for example 1.5, will increase the defense unit amounts. For 4 planes detected, 6 planes will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 planes detected, only 3 planes will be spawned. - -- - -- 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. - -- - -- For example ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ## 6.5. Squadron fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: - -- - The defender will go RTB, and will be replaced with a new defender if possible. - -- - The defender will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 7. Setup a squadron for CAP - -- - -- ### 7.1. Set the CAP zones - -- - -- CAP zones are patrol areas where Combat Air Patrol (CAP) flights loiter until they either return to base due to low fuel or are assigned an interception task by ground control. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia6.JPG) - -- - -- * As the CAP flights wander around within the zone waiting to be tasked, these zones need to be large enough that the aircraft are not constantly turning - -- but do not have to be big and numerous enough to completely cover a border. - -- - -- * CAP zones can be of any type, and are derived from the @{Core.Zone#ZONE_BASE} class. Zones can be @{Core.Zone#ZONE}, @{Core.Zone#ZONE_POLYGON}, @{Core.Zone#ZONE_UNIT}, @{Core.Zone#ZONE_GROUP}, etc. - -- This allows to setup **static, moving and/or complex zones** wherein aircraft will perform the CAP. - -- - -- * Typically 20000-50000 metres width is used and they are spaced so that aircraft in the zone waiting for tasks don't have to far to travel to protect their coalitions important targets. - -- These targets are chosen as part of the mission design and might be an important airfield or town etc. - -- Zone size is also determined somewhat by territory size, plane types - -- (eg WW2 aircraft might mean smaller zones or more zones because they are slower and take longer to intercept enemy aircraft). - -- - -- * In a **cold war** it is important to make sure a CAP zone doesn't intrude into enemy territory as otherwise CAP flights will likely cross borders - -- and spark a full scale conflict which will escalate rapidly. - -- - -- * CAP flights do not need to be in the CAP zone before they are "on station" and ready for tasking. - -- - -- * Typically if a CAP flight is tasked and therefore leaves their zone empty while they go off and intercept their target another CAP flight will spawn to take their place. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia7.JPG) - -- - -- The following example illustrates how CAP zones are coded: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_8.JPG) - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_7.JPG) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_9.JPG) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- Note the different @{Core.Zone} MOOSE classes being used to create zones of different types. Please click the @{Core.Zone} link for more information about the different zone types. - -- Zones can be circles, can be setup in the mission editor using trigger zones, but can also be setup in the mission editor as polygons and in this case GROUP objects are being used! - -- - -- ## 7.2. Set the squadron to execute CAP: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronCap}() defines a CAP execution for a squadron. - -- - -- Setting-up a CAP zone also requires specific parameters: - -- - -- * The minimum and maximum altitude - -- * The minimum speed and maximum patrol speed - -- * The minimum and maximum engage speed - -- * The type of altitude measurement - -- - -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. - -- - -- It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system. - -- - -- For example, the following setup will create a CAP for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ## 7.3. Squadron tanker to refuel when executing CAP and defender is out of fuel. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the default tanker for the refuelling. - -- You can also specify a specific tanker for refuelling for a squadron by using the method @{#AI_A2A_DISPATCHER.SetSquadronTanker}(). - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will create a CAP for squadron "Gelend" with a refuel task for the squadron: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_10.JPG) - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Gelend", AIRBASE.Caucasus.Gelendzhik, { "SQ CCCP SU-30" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Gelend", ZONE:New( "PatrolZoneGelend" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval( "Gelend", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Gelend", 900, 1200 ) - -- - -- -- Setup the Refuelling for squadron "Gelend", at tanker (group) "TankerGelend" when the fuel in the tank of the CAP defenders is less than 80%. - -- A2ADispatcher:SetSquadronFuelThreshold( "Gelend", 0.8 ) - -- A2ADispatcher:SetSquadronTanker( "Gelend", "TankerGelend" ) - -- - -- ## 7.4 Set up race track pattern - -- - -- By default, flights patrol randomly within the CAP zone. It is also possible to let them fly a race track pattern using the - -- @{#AI_A2A_DISPATCHER.SetDefaultCapRacetrack}(*LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) or - -- @{#AI_A2A_DISPATCHER.SetSquadronCapRacetrack}(*SquadronName*, *LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) functions. - -- The first function enables this for all squadrons, the latter only for specific squadrons. For example, - -- - -- -- Enable race track pattern for CAP squadron "Mineralnye". - -- A2ADispatcher:SetSquadronCapRacetrack("Mineralnye", 10000, 20000, 90, 180, 10*60, 20*60) - -- - -- In this case the squadron "Mineralnye" will a race track pattern at a random point in the CAP zone. The leg length will be randomly selected between 10,000 and 20,000 meters. The heading - -- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees. - -- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location. - -- - -- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the CAP command used earlier on, e.g. - -- - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - -- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the - -- whole pattern lies within the patrol zone. - -- - -- ## 8. Setup a squadron for GCI: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. - -- - -- Setting-up a GCI readiness also requires specific parameters: - -- - -- * The minimum speed and maximum patrol speed - -- - -- Essentially this controls how many flights of GCI aircraft can be active at any time. - -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. - -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have already passed the ideal interception point! - -- - -- For example, the following setup will create a GCI for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- - -- ## 9. Other configuration options - -- - -- ### 9.1. Set a tactical display panel: - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI\_A2A\_DISPATCHER. - -- Use the method @{#AI_A2A_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Defaults settings. - -- - -- This provides a good overview of the different parameters that are setup or hardcoded by default. - -- For some default settings, a method is available that allows you to tweak the defaults. - -- - -- ## 10.1. Default takeoff method. - -- - -- The default **takeoff method** is set to **in the air**, which means that new spawned airplanes will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing method. - -- - -- The default **landing method** is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_A2A_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default overhead. - -- - -- The default **overhead** is set to **1**. That essentially means that there isn't any overhead set by default. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_A2A_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default grouping. - -- - -- The default **grouping** is set to **one airplane**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.7. Default settings for CAP. - -- - -- ### 10.7.1. Default CAP Time Interval. - -- - -- CAP is time driven, and will evaluate in random time intervals if a new CAP needs to be spawned. - -- The **default CAP time interval** is between **180** and **600** seconds. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. - -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. - -- - -- ### 10.7.2. Default CAP limit. - -- - -- Multiple CAP can be airborne at the same time for one squadron, which is controlled by the **CAP limit**. - -- The **default CAP limit** is 1 CAP per squadron to be airborne at the same time. - -- Note that the default CAP limit is used when a Squadron CAP is defined, and cannot be changed afterwards. - -- So, ensure that you set the default CAP limit **before** you spawn the Squadron CAP. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. - -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_11.JPG) - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- ## 12. Q & A: - -- - -- ### 12.1. Which countries will be selected for each coalition? - -- - -- Which countries are assigned to a coalition influences which units are available to the coalition. - -- For example because the mission calls for a EWR radar on the blue side the Ukraine might be chosen as a blue country - -- so that the 55G6 EWR radar unit is available to blue. - -- Some countries assign different tasking to aircraft, for example Germany assigns the CAP task to F-4E Phantoms but the USA does not. - -- Therefore if F4s are wanted as a coalition's CAP or GCI aircraft Germany will need to be assigned to that coalition. - -- - -- ### 12.2. Country, type, load out, skill and skins for CAP and GCI aircraft? - -- - -- * Note these can be from any countries within the coalition but must be an aircraft with one of the main tasks being "CAP". - -- * Obviously skins which are selected must be available to all players that join the mission otherwise they will see a default skin. - -- * Load outs should be appropriate to a CAP mission eg perhaps drop tanks for CAP flights and extra missiles for GCI flights. - -- * These decisions will eventually lead to template aircraft units being placed as late activation units that the script will use as templates for spawning CAP and GCI flights. Up to 4 different aircraft configurations can be chosen for each coalition. The spawned aircraft will inherit the characteristics of the template aircraft. - -- * The selected aircraft type must be able to perform the CAP tasking for the chosen country. - -- - -- - -- @field #AI_A2A_DISPATCHER - AI_A2A_DISPATCHER = { - ClassName = "AI_A2A_DISPATCHER", - Detection = nil, - } - - --- Squadron data structure. - -- @type AI_A2A_DISPATCHER.Squadron - -- @field #string Name Name of the squadron. - -- @field #number ResourceCount Number of resources. - -- @field #string AirbaseName Name of the home airbase. - -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase of the squadron. - -- @field #boolean Captured If true, airbase of the squadron was captured. - -- @field #table Resources Flight group resources Resources[TemplateID][GroupName] = SpawnGroup. - -- @field #boolean Uncontrolled If true, flight groups are spawned uncontrolled and later activated. - -- @field #table Gci GCI. - -- @field #number Overhead Squadron overhead. - -- @field #number Grouping Squadron flight group size. - -- @field #number Takeoff Takeoff type. - -- @field #number TakeoffAltitude Altitude in meters for spawn in air. - -- @field #number Landing Landing type. - -- @field #number FuelThreshold Fuel threshold [0,1] for RTB. - -- @field #string TankerName Name of the refuelling tanker. - -- @field #table Table of template group names of the squadron. - -- @field #table Spawn Table of spawns Core.Spawn#SPAWN. - -- @field #table TemplatePrefixes - -- @field #boolean Racetrack If true, CAP flights will perform a racetrack pattern rather than randomly patrolling the zone. - -- @field #number RacetrackLengthMin Min Length of race track in meters. Default 10,000 m. - -- @field #number RacetrackLengthMax Max Length of race track in meters. Default 15,000 m. - -- @field #number RacetrackHeadingMin Min heading of race track in degrees. Default 0 deg, i.e. from South to North. - -- @field #number RacetrackHeadingMax Max heading of race track in degrees. Default 180 deg, i.e. from North to South. - -- @field #number RacetrackDurationMin Min duration in seconds before the CAP flight changes its orbit position. Default never. - -- @field #number RacetrackDurationMax Max duration in seconds before the CAP flight changes its orbit position. Default never. - - --- Enumerator for spawns at airbases - -- @type AI_A2A_DISPATCHER.Takeoff - -- @extends Wrapper.Group#GROUP.Takeoff - - --- - -- @field #AI_A2A_DISPATCHER.Takeoff Takeoff - AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff - - --- Defines Landing type/location. - -- @field Landing - AI_A2A_DISPATCHER.Landing = { - NearAirbase = 1, - AtRunway = 2, - AtEngineShutdown = 3, - } - - --- AI_A2A_DISPATCHER constructor. - -- This is defining the A2A DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - function AI_A2A_DISPATCHER:New( Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_A2A_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.SetSendPlayerMessages = false --#boolean Flash messages to player - - -- TODO: Check detection through radar. - self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - -- self.Detection:InitDetectRadar( true ) - self.Detection:SetRefreshTimeInterval( 30 ) - - self:SetEngageRadius() - self:SetGciRadius() - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) - 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:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. - self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron. - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterAssign - -- @param #AI_A2A_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_A2A#AI_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:AddTransition( "*", "CAP", "*" ) - - --- CAP Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeCAP - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- CAP Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterCAP - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- CAP Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] CAP - -- @param #AI_A2A_DISPATCHER self - - --- CAP Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __CAP - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "GCI", "*" ) - - --- GCI Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeGCI - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- GCI Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterGCI - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - --- GCI Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] GCI - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - --- GCI Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __GCI - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - self:AddTransition( "*", "ENGAGE", "*" ) - - --- ENGAGE Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - -- @return #boolean - - --- ENGAGE Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - --- ENGAGE Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] ENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - --- ENGAGE Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __ENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - -- 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 intercept 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.DefenderCAPIndex = 0 - - self:__Start( 5 ) - - return self - end - - --- On after "Start" event. - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterStart( From, Event, To ) - - self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To ) - - -- Spawn the resources. - for SquadronName, _DefenderSquadron in pairs( self.DefenderSquadrons ) do - local DefenderSquadron = _DefenderSquadron -- #AI_A2A_DISPATCHER.Squadron - DefenderSquadron.Resources = {} - if DefenderSquadron.ResourceCount then - for Resource = 1, DefenderSquadron.ResourceCount do - self:ParkDefender( DefenderSquadron ) - end - end - end - end - - --- Park defender. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The squadron. - function AI_A2A_DISPATCHER:ParkDefender( 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 - - local Grouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - - Grouping = 1 - - Spawn:InitGrouping( Grouping ) - - 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 - - self.uncontrolled = self.uncontrolled or {} - self.uncontrolled[DefenderSquadron.Name] = self.uncontrolled[DefenderSquadron.Name] or {} - - table.insert( self.uncontrolled[DefenderSquadron.Name], { group = SpawnGroup, name = GroupName, grouping = Grouping } ) - end - - end - - --- Event base captured. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData ) - - local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. - - self:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize 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:T( "Squadron " .. SquadronName .. " captured." ) - end - end - end - - --- Event dead or crash. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventCrashOrDead( EventData ) - self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) - end - - --- Event land. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_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_A2A_DISPATCHER.Landing.AtRunway then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ParkDefender( Squadron ) - return - end - if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then - -- Damaged units cannot be repaired anymore. - DefenderUnit:Destroy() - return - end - end - end - - --- Event engine shutdown. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_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_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir() then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ParkDefender( Squadron ) - end - end - end - - --- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- If there is a target area detected and reported, then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, - -- will be considered to receive the command to engage that target area. - -- - -- You need to evaluate the value of this parameter carefully: - -- - -- * If too small, more intercept missions may be triggered upon detected target areas. - -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- - -- **Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to modify the default Engage Radius for ALL squadrons.** - -- - -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-019%20-%20Engage%20Range%20Test) - -- - -- @param #AI_A2A_DISPATCHER self - -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - function AI_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) - - self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - - return self - end - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_A2A_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius in meters to disengage a target when too far from the home base. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- A2ADispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- A2ADispatcher:SetDisengageRadius() -- 300000 is the default value. - -- - function AI_A2A_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - --- Define the radius to check if a target can be engaged by an ground controlled intercept. - -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. - -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** - -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. - -- - -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, - -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. - -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-013%20-%20Intercept%20Test) - -- - -- @param #AI_A2A_DISPATCHER self - -- @param #number GciRadius (Optional, Default = 200000) The radius to ground control intercept detected targets from the nearest airbase. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. - -- A2ADispatcher:SetGciRadius( 100000 ) - -- - -- -- Set 200km as the radius to ground control intercept. - -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. - -- - function AI_A2A_DISPATCHER:SetGciRadius( GciRadius ) - - self.GciRadius = GciRadius or 200000 - - return self - end - - --- Define a border area to simulate a **cold war** scenario. - -- A **cold war** is one where CAP 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 CAP 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 CAP 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 @{Core.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_A2A_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_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set one ZONE_POLYGON object as the border for the A2A dispatcher. - -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2ADispatcher:SetBorderZone( BorderZone ) - -- - -- or - -- - -- -- Set two ZONE_POLYGON objects as the border for the A2A 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. - -- A2ADispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) - -- - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- A2ADispatcher:SetTacticalDisplay( true ) - -- - function AI_A2A_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. - -- @param #AI_A2A_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- A2ADispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. - -- - function AI_A2A_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) - - self.DefenderDefault.DamageThreshold = DamageThreshold - - return self - end - - --- Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing. - -- The default CAP time interval is between 180 and 600 seconds. - -- @param #AI_A2A_DISPATCHER self - -- @param #number CapMinSeconds The minimum amount of seconds for the random time interval. - -- @param #number CapMaxSeconds The maximum amount of seconds for the random time interval. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default CAP time interval. - -- A2ADispatcher:SetDefaultCapTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. - -- - function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval( CapMinSeconds, CapMaxSeconds ) - - self.DefenderDefault.CapMinSeconds = CapMinSeconds - self.DefenderDefault.CapMaxSeconds = CapMaxSeconds - - return self - end - - --- Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron. - -- The default CAP limit is 1 CAP, which means one CAP group being spawned. - -- @param #AI_A2A_DISPATCHER self - -- @param #number CapLimit The maximum amount of CAP that can be airborne at the same time for the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default CAP limit. - -- A2ADispatcher:SetDefaultCapLimit( 2 ) -- Maximum 2 CAP per squadron. - -- - function AI_A2A_DISPATCHER:SetDefaultCapLimit( CapLimit ) - - self.DefenderDefault.CapLimit = CapLimit - - return self - end - - --- Set intercept. - -- @param #AI_A2A_DISPATCHER self - -- @param #number InterceptDelay Delay in seconds before intercept. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetIntercept( InterceptDelay ) - - self.DefenderDefault.InterceptDelay = InterceptDelay - - local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS - Detection:SetIntercept( true, InterceptDelay ) - - return self - end - - --- Calculates which AI friendlies are nearby the area - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return #table A list of the friendlies nearby. - function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) - - local FriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem ) - - return FriendliesNearBy - end - - --- Return the defender tasks table. - -- @param #AI_A2A_DISPATCHER self - -- @return #table Defender tasks as table. - function AI_A2A_DISPATCHER:GetDefenderTasks() - return self.DefenderTasks or {} - end - - --- Get defender task. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #table Defender task. - function AI_A2A_DISPATCHER:GetDefenderTask( Defender ) - return self.DefenderTasks[Defender] - end - - --- Get defender task FSM. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return Core.Fsm#FSM The FSM. - function AI_A2A_DISPATCHER:GetDefenderTaskFsm( Defender ) - return self:GetDefenderTask( Defender ).Fsm - end - - --- Get target of defender. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return Target - function AI_A2A_DISPATCHER:GetDefenderTaskTarget( Defender ) - return self:GetDefenderTask( Defender ).Target - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #string Squadron name of the defender task. - function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName( Defender ) - return self:GetDefenderTask( Defender ).SquadronName - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_DISPATCHER:ClearDefenderTask( Defender ) - if Defender and 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_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_DISPATCHER:ClearDefenderTaskTarget( Defender ) - - local DefenderTask = self:GetDefenderTask( Defender ) - - if Defender and 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 - - --- Set defender task. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param #table Type Type of the defender task - -- @param Core.Fsm#FSM Fsm The defender task FSM. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem Target The defender detected item. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) - - self:F( { SquadronName = SquadronName, Defender = Defender:GetName(), Type = Type, Target = Target } ) - - self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} - self.DefenderTasks[Defender].Type = Type - self.DefenderTasks[Defender].Fsm = Fsm - self.DefenderTasks[Defender].SquadronName = SquadronName - - if Target then - self:SetDefenderTaskTarget( Defender, Target ) - end - return self - end - - --- Set defender task target. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection The detection object. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_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\_A2A\_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_A2A_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. - -- @return #AI_A2A_DISPATCHER self - -- - -- @usage - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- @usage - -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- A2ADispatcher: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 A2A 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. - -- A2ADispatcher: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... - -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- A2ADispatcher: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. - -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] -- #AI_A2A_DISPATCHER.Squadron - - 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:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. - - self:F( { Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - - return self - end - - --- Get an item from the Squadron table. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @return #AI_A2A_DISPATCHER.Squadron Defender squadron table. - function AI_A2A_DISPATCHER:GetSquadron( SquadronName ) - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron:" .. SquadronName ) - end - - return DefenderSquadron - end - - --- Get a resource count from a specific squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron Name of the squadron. - -- @return #number Number of airframes available or nil if the squadron does not exist - function AI_A2A_DISPATCHER:QuerySquadron(Squadron) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) - return Squadron.ResourceCount - end - self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - return nil - end - - --- [DEPRECATED - Might create problems launching planes] 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_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- A2ADispatcher:SetSquadronVisible( "Mineralnye" ) - -- - function AI_A2A_DISPATCHER:SetSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron - - DefenderSquadron.Uncontrolled = true - - -- For now, grouping is forced to 1 due to other parts of the class which would not work well with grouping>1. - DefenderSquadron.Grouping = 1 - - -- Get free parking for fighter aircraft. - local nfreeparking = DefenderSquadron.Airbase:GetFreeParkingSpotsNumber( AIRBASE.TerminalType.FighterAircraft, true ) - - -- Take number of free parking spots if no resource count was specified. - DefenderSquadron.ResourceCount = DefenderSquadron.ResourceCount or nfreeparking - - -- Check that resource count is not larger than free parking spots. - DefenderSquadron.ResourceCount = math.min( DefenderSquadron.ResourceCount, nfreeparking ) - - -- Set uncontrolled spawning option. - for SpawnTemplate, _DefenderSpawn in pairs( self.DefenderSpawns ) do - local DefenderSpawn = _DefenderSpawn -- Core.Spawn#SPAWN - DefenderSpawn:InitUnControlled( true ) - end - - end - - --- Check if the Squadron is visible before startup of the dispatcher. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) - -- - function AI_A2A_DISPATCHER:IsSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron - - if DefenderSquadron then - return DefenderSquadron.Uncontrolled == true - end - - return nil - - end - - --- Set a CAP for a Squadron. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. - -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. - -- @param #number EngageAltType The altitude type to engage, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. - -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. - -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type to patrol, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, - -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. - -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - Cap.Name = SquadronName - Cap.EngageMinSpeed = EngageMinSpeed - Cap.EngageMaxSpeed = EngageMaxSpeed - Cap.EngageFloorAltitude = EngageFloorAltitude - Cap.EngageCeilingAltitude = EngageCeilingAltitude - Cap.Zone = Zone - Cap.PatrolMinSpeed = PatrolMinSpeed - Cap.PatrolMaxSpeed = PatrolMaxSpeed - Cap.PatrolFloorAltitude = PatrolFloorAltitude - Cap.PatrolCeilingAltitude = PatrolCeilingAltitude - Cap.PatrolAltType = PatrolAltType - Cap.EngageAltType = EngageAltType - - self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) - - self:T( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } ) - - -- Add the CAP to the EWR network. - - local RecceSet = self.Detection:GetDetectionSet() - RecceSet:FilterPrefixes( DefenderSquadron.TemplatePrefixes ) - RecceSet:FilterStart() - - self.Detection:SetFriendlyPrefixes( DefenderSquadron.TemplatePrefixes ) - - return self - end - - --- Set a CAP for a Squadron. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. - -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed 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_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - return self:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType ) - end - - --- Set the squadron CAP parameters. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number CapLimit (optional) The maximum amount of CAP groups to be spawned. Note that a CAP is a group, so can consist out of 1 to 4 airplanes. The default is 1 CAP group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new CAP will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new CAP will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCapInterval( SquadronName, CapLimit, LowInterval, HighInterval, Probability ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - if Cap then - Cap.LowInterval = LowInterval or 180 - Cap.HighInterval = HighInterval or 600 - Cap.Probability = Probability or 1 - Cap.CapLimit = CapLimit or 1 - Cap.Scheduler = Cap.Scheduler or SCHEDULER:New( self ) - local Scheduler = Cap.Scheduler -- Core.Scheduler#SCHEDULER - local ScheduleID = Cap.ScheduleID - local Variance = (Cap.HighInterval - Cap.LowInterval) / 2 - local Repeat = Cap.LowInterval + Variance - local Randomization = Variance / Repeat - local Start = math.random( 1, Cap.HighInterval ) - - if ScheduleID then - Scheduler:Stop( ScheduleID ) - end - - Cap.ScheduleID = Scheduler:Schedule( self, self.SchedulerCAP, { SquadronName }, Start, Repeat, Randomization ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:GetCAPDelay( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - if Cap then - return math.random( Cap.LowInterval, Cap.HighInterval ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - end - - --- Check if squadron can do CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER.Squadron DefenderSquadron - function AI_A2A_DISPATCHER:CanCAP( SquadronName ) - self:F( { SquadronName = SquadronName } ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new CAP 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 Cap = DefenderSquadron.Cap - if Cap then - local CapCount = self:CountCapAirborne( SquadronName ) - self:F( { CapCount = CapCount } ) - if CapCount < Cap.CapLimit then - local Probability = math.random() - if Probability <= Cap.Probability then - return DefenderSquadron - end - end - end - end - end - return nil - end - - --- Set race track pattern as default when any squadron is performing CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. - -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. - -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. counter clockwise from South to North. - -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. counter clockwise from North to South. - -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultCapRacetrack( LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - - self.DefenderDefault.Racetrack = true - self.DefenderDefault.RacetrackLengthMin = LeglengthMin - self.DefenderDefault.RacetrackLengthMax = LeglengthMax - self.DefenderDefault.RacetrackHeadingMin = HeadingMin - self.DefenderDefault.RacetrackHeadingMax = HeadingMax - self.DefenderDefault.RacetrackDurationMin = DurationMin - self.DefenderDefault.RacetrackDurationMax = DurationMax - self.DefenderDefault.RacetrackCoordinates = CapCoordinates - - return self - end - - --- Set race track pattern when squadron is performing CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. - -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. - -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. - -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. - -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetSquadronCapRacetrack( SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - DefenderSquadron.Racetrack = true - DefenderSquadron.RacetrackLengthMin = LeglengthMin - DefenderSquadron.RacetrackLengthMax = LeglengthMax - DefenderSquadron.RacetrackHeadingMin = HeadingMin - DefenderSquadron.RacetrackHeadingMax = HeadingMax - DefenderSquadron.RacetrackDurationMin = DurationMin - DefenderSquadron.RacetrackDurationMax = DurationMax - DefenderSquadron.RacetrackCoordinates = CapCoordinates - end - - return self - end - - --- Check if squadron can do GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2A_DISPATCHER:CanGCI( SquadronName ) - self:F( { SquadronName = SquadronName } ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new CAP 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 Gci = DefenderSquadron.Gci - if Gci then - return DefenderSquadron - end - end - end - return nil - end - - --- Set squadron GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. - -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. - -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. - -- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) - -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) - -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) - -- - function AI_A2A_DISPATCHER:SetSquadronGci2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local Intercept = self.DefenderSquadrons[SquadronName].Gci - Intercept.Name = SquadronName - Intercept.EngageMinSpeed = EngageMinSpeed - Intercept.EngageMaxSpeed = EngageMaxSpeed - Intercept.EngageFloorAltitude = EngageFloorAltitude - Intercept.EngageCeilingAltitude = EngageCeilingAltitude - Intercept.EngageAltType = EngageAltType - - self:T( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - end - - --- Set squadron GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. - -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) - -- - function AI_A2A_DISPATCHER:SetSquadronGci( SquadronName, EngageMinSpeed, EngageMaxSpeed ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local Intercept = self.DefenderSquadrons[SquadronName].Gci - Intercept.Name = SquadronName - Intercept.EngageMinSpeed = EngageMinSpeed - Intercept.EngageMaxSpeed = EngageMaxSpeed - - self:F( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed } } ) - end - - --- Defines the default amount of extra planes that will take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- @return #AI_A2A_DISPATCHER - -- The default overhead is 1, so equal balance. The @{#AI_A2A_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 A2A 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 A2ADispatcher = AI_A2A_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. - -- - -- A2ADispatcher:SetDefaultOverhead( 1.5 ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- @return #AI_A2A_DISPATCHER self - -- The default overhead is 1, so equal balance. The @{#AI_A2A_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 A2A 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 A2ADispatcher = AI_A2A_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. - -- - -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) - -- - function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Overhead = Overhead - - return self - 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_A2A_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 airplanes. - -- A2ADispatcher:SetDefaultGrouping( 2 ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set a grouping per 2 airplanes. - -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default take-off from the runway. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default take-off from the airbase hot. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default take-off from the airbase cold. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) - -- - function AI_A2A_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_A2A_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. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights take-off from the runway. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights take-off from the airbase hot. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights take-off from the airbase cold. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- local TakeoffMethod = A2ADispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2A_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_A2A_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 A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- local TakeoffMethod = A2ADispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- A2ADispatcher:SetDefaultTakeoffInAir() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) - - return self - end - - --- Set flashing player messages on or off - -- @param #AI_A2A_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function AI_A2A_DISPATCHER:SetSendMessages( onoff ) - self.SetSendPlayerMessages = onoff - end - - --- Sets flights to take-off in the air, as part of the defense system. - -- @param #AI_A2A_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. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off from the runway. - -- A2ADispatcher:SetDefaultTakeoffFromRunway() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Runway ) - - return self - end - - --- Sets flights to take-off from the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from the runway. - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off at a hot parking spot. - -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() - - self:SetDefaultTakeoff( AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() - - self:SetDefaultTakeoff( AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights by default despawn after landing land at the runway. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights despawn after landing land at the runway. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2ADispatcher:GetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = A2ADispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- A2ADispatcher:SetDefaultLandingNearAirbase() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() - - self:SetDefaultLanding( AI_A2A_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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() - - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) - - return self - end - - --- Sets flights to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_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_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() - - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Set the default fuel threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold 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_A2A_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2A_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) - - self.DefenderDefault.FuelThreshold = FuelThreshold - - return self - end - - --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold 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_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2A_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_A2A_DISPATCHER self - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the default tanker. - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - -- - function AI_A2A_DISPATCHER:SetDefaultTanker( TankerName ) - - self.DefenderDefault.TankerName = TankerName - - return self - end - - --- Set the squadron tanker where defenders will Refuel in the air. - -- @param #AI_A2A_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_A2A_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the squadron tanker. - -- A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - -- - function AI_A2A_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TankerName = TankerName - - return self - end - - --- Set the squadron language. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #string Language A string defining the language to be embedded within the miz file. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set for English. - -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "EN" ) -- This squadron speaks English. - -- - -- -- Set for Russian. - -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "RU" ) -- This squadron speaks Russian. - function AI_A2A_DISPATCHER:SetSquadronLanguage( SquadronName, Language ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Language = Language - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:SetLanguage( Language ) - end - - return self - end - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_A2A_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.RadioFrequency = RadioFrequency - DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM - DefenderSquadron.RadioPower = RadioPower or 100 - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:Stop() - end - - DefenderSquadron.RadioQueue = nil - - DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) - DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower - DefenderSquadron.RadioQueue:Start( 0.5 ) - - DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) - end - - --- Add defender to squadron. Resource count will get smaller. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param #number Size Size of the group. - function AI_A2A_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 - - --- Remove defender from squadron. Resource count will increase. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_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 - - --- Get squadron from defender. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) - self.Defenders = self.Defenders or {} - if Defender ~= nil then - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[DefenderName] - else - return nil - end - end - - --- Creates an SWEEP task when there are targets for it. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - function AI_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - if DetectedItem.IsDetected == false then - - -- Here we're doing something advanced... We're copying the DetectedSet. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Count number of airborne CAP flights. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @return #number Number of defender CAP groups. - function AI_A2A_DISPATCHER:CountCapAirborne( SquadronName ) - - local CapCount = 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 == "CAP" then - if AIGroup and AIGroup:IsAlive() then - -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! - -- The CAP could be damaged, lost control, or out of fuel! - -- env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) or DefenderTask.Fsm:Is( "Started" ) then - -- env.info("FF capcount "..CapCount) - CapCount = CapCount + 1 - end - end - end - end - end - end - - return CapCount - end - - --- Count number of engaging defender groups. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detection object. - -- @return #number Number of defender groups engaging. - function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = 0 - - local DetectedSet = AttackerDetection.Set - -- DetectedSet:Flush() - - local DefenderTasks = self:GetDefenderTasks() - - for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do - local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target -- Functional.Detection#DETECTION_BASE.DetectedItem - local DefenderSquadronName = DefenderTask.SquadronName - - if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then - local Squadron = self:GetSquadron( DefenderSquadronName ) - local SquadronOverhead = Squadron.Overhead or self.DefenderDefault.Overhead - - local DefenderSize = Defender:GetInitialSize() - if DefenderSize then - DefenderCount = DefenderCount + DefenderSize / SquadronOverhead - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - else - DefenderCount = 0 - end - end - end - - self:F( { DefenderCount = DefenderCount } ) - - return DefenderCount - end - - --- Count defenders to be engaged if number of attackers larger than number of defenders. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefenderCount Number of defenders. - -- @return #table Table of friendly groups. - function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( AttackerDetection, DefenderCount ) - - local Friendlies = nil - - local AttackerSet = AttackerDetection.Set - local AttackerCount = AttackerSet:Count() - - local DefenderFriendlies = self:GetAIFriendliesNearBy( AttackerDetection ) - - for FriendlyDistance, AIFriendly 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 - --self:T("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:") - if AIFriendly then - local classname = AIFriendly.ClassName or "No Class Name" - local unitname = AIFriendly.IdentifiableName or "No Unit Name" - --self:T("Class Name: " .. classname) - --self:T("Unit Name: " .. unitname) - --self:T({AIFriendly}) - end - local Friendly = nil - if AIFriendly and AIFriendly:IsAlive() then - --self:T("AIFriendly alive, getting GROUP") - Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP - end - - if Friendly and Friendly: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( Friendly ) - if DefenderTask then - -- The Task should be CAP or GCI - if DefenderTask.Type == "CAP" or DefenderTask.Type == "GCI" 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[Friendly] = Friendly - DefenderCount = DefenderCount + Friendly:GetSize() - self:F( { Friendly = Friendly:GetName(), FriendlyDistance = FriendlyDistance } ) - end - end - end - end - end - else - break - end - end - - return Friendlies - end - - --- Activate resource. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The defender squadron. - -- @param #number DefendersNeeded Number of defenders needed. Default 4. - -- @return Wrapper.Group#GROUP The defender group. - -- @return #boolean Grouping. - function AI_A2A_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 - - -- env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) - - if self:IsSquadronVisible( SquadronName ) then - - local n = #self.uncontrolled[SquadronName] - - if n > 0 then - -- Random number 1,...n - local id = math.random( n ) - - -- Pick a random defender group. - local Defender = self.uncontrolled[SquadronName][id].group -- Wrapper.Group#GROUP - - -- Start uncontrolled group. - Defender:StartUncontrolled() - - -- Get grouping. - DefenderGrouping = self.uncontrolled[SquadronName][id].grouping - - -- Add defender to squadron. - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - - -- Remove defender from uncontrolled table. - table.remove( self.uncontrolled[SquadronName], id ) - - return Defender, DefenderGrouping - else - return nil, 0 - end - - -- Here we CAP 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 DefenderCAPTemplate = 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 - DefenderCAPTemplate = UTILS.DeepCopy( DefenderTemplate ) - self.DefenderCAPIndex = self.DefenderCAPIndex + 1 - DefenderCAPTemplate.name = SquadronName .. "#" .. self.DefenderCAPIndex .. "#" .. GroupName - DefenderName = DefenderCAPTemplate.name - else - -- Add the unit in the template to the DefenderCAPTemplate. - local DefenderUnitTemplate = DefenderTemplate.units[1] - DefenderCAPTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate - end - DefenderUnitIndex = DefenderUnitIndex + 1 - DefenderSquadron.Resources[TemplateID][GroupName] = nil - if DefenderUnitIndex > DefenderGrouping then - break - end - - end - - if DefenderCAPTemplate then - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local SpawnGroup = GROUP:Register( DefenderName ) - DefenderCAPTemplate.lateActivation = nil - DefenderCAPTemplate.uncontrolled = nil - local Takeoff = self:GetSquadronTakeoff( SquadronName ) - DefenderCAPTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - DefenderCAPTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - local Defender = _DATABASE:Spawn( DefenderCAPTemplate ) - - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - return Defender, DefenderGrouping - end - ]] - - else - - ---------------------------- - --- Squadron not visible --- - ---------------------------- - - 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 - - --- On after "CAP" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string SquadronName Name of the squadron. - function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) - - self:F( { SquadronName = SquadronName } ) - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:CanCAP( SquadronName ) - - if DefenderSquadron then - - local Cap = DefenderSquadron.Cap - - if Cap then - - local DefenderCAP, DefenderGrouping = self:ResourceActivate( DefenderSquadron ) - - if DefenderCAP then - - local AI_A2A_Fsm = AI_A2A_CAP:New2( DefenderCAP, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.EngageFloorAltitude, Cap.EngageCeilingAltitude, Cap.EngageAltType, Cap.Zone, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.PatrolFloorAltitude, Cap.PatrolCeilingAltitude, Cap.PatrolAltType ) - AI_A2A_Fsm:SetDispatcher( self ) - AI_A2A_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2A_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2A_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2A_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2A_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then - AI_A2A_Fsm:SetRaceTrackPattern( DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, - DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, - DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, - DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, - DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, - DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, - DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates ) - end - AI_A2A_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", AI_A2A_Fsm ) - - function AI_A2A_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - -- Issue GetCallsign() returns nil, see https://github.com/FlightControl-Master/MOOSE/issues/1228 - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP Takeoff", DefenderGroup:GetName() } ) - -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2A_Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) - end - AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling - end - end - end - - function AI_A2A_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP PatrolRoute", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) - end - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - end - - function AI_A2A_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP RTB", DefenderGroup:GetName() } ) - - self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - end - - --- AI_A2A_Fsm:onafterHome - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_Fsm:onafterHome( Defender, From, Event, To, Action ) - if Defender and Defender:IsAlive() then - self:F( { "CAP Home", Defender:GetName() } ) - self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ParkDefender( Squadron ) - end - end - end - end - end - end - - end - - --- On after "ENGAGE" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) - self:F( "ENGAGING Detection ID=" .. tostring( AttackerDetection.ID ) ) - - if Defenders then - - for DefenderID, Defender in pairs( Defenders ) do - - local Fsm = self:GetDefenderTaskFsm( Defender ) - - Fsm:EngageRoute( AttackerDetection.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( Defender, AttackerDetection ) - - end - - end - end - - --- On after "GCI" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) - - self:F( "GCI Detection ID=" .. tostring( AttackerDetection.ID ) ) - - self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) - - local AttackerSet = AttackerDetection.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 - - local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:__EngageRoute( 0.1, AttackerSet ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( DefenderGroup, AttackerDetection ) - - DefenderCount = DefenderCount + DefenderGroup:GetSize() - 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 - - self:F( { GCI = DefenderSquadron.Gci } ) - - for InterceptID, Intercept in pairs( DefenderSquadron.Gci or {} ) do - - self:F( { DefenderSquadron } ) - local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local AttackerCoord = AttackerUnit:GetCoordinate() - local InterceptCoord = AttackerDetection.InterceptCoord - self:F( { InterceptCoord = InterceptCoord } ) - if InterceptCoord then - local InterceptDistance = SpawnCoord:Get2DDistance( InterceptCoord ) - local AirbaseDistance = SpawnCoord: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.GciRadius then - ClosestDistance = InterceptDistance - ClosestDefenderSquadronName = SquadronName - end - end - end - end - end - - if ClosestDefenderSquadronName then - - local DefenderSquadron = self:CanGCI( ClosestDefenderSquadronName ) - - if DefenderSquadron then - - local Gci = self.DefenderSquadrons[ClosestDefenderSquadronName].Gci - - if Gci 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 } ) - - -- 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 - - local DefenderGCI, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - DefendersNeeded = DefendersNeeded - DefenderGrouping - - if DefenderGCI then - - DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - - local Fsm = AI_A2A_GCI:New2( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed, Gci.EngageFloorAltitude, Gci.EngageCeilingAltitude, Gci.EngageAltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() - - self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection ) - - function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F( { "GCI Birth", DefenderGroup:GetName() } ) - -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) - - if DefenderTarget then - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup ) - end - -- Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit - Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { "GCI Route", DefenderGroup:GetName() } ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехватывая боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - end - end - self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function Fsm:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { "GCI Engage", DefenderGroup:GetName() } ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", задействуя боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - end - end - self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F( { "GCI RTB", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращение на базу.", DefenderGroup ) - end - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - --- function Fsm:onafterLostControl - -- @param #AI_A2A_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F( { "GCI LostControl", Defender:GetName() } ) - self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - end - - --- function Fsm:onafterHome - -- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F( { "GCI Home", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", посадка на базу.", DefenderGroup ) - end - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ParkDefender( Squadron ) - end - end - - end -- if DefenderGCI then - end -- while ( DefendersNeeded > 0 ) do - end - 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 ENGAGE task when there are human friendlies airborne near the targets. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil. - function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = self:CountDefendersEngaged( DetectedItem ) - local DefenderGroups = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - - self:F( { DefenderCount = DefenderCount } ) - - -- Only allow ENGAGE when: - -- 1. There are friendly units near the detected attackers. - -- 2. There is sufficient fuel - -- 3. There is sufficient ammo - -- 4. The plane is not damaged - if DefenderGroups and DetectedItem.IsDetected == true then - return DefenderGroups - end - - return nil - end - - --- Creates an GCI task when there are targets for it. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil if there are no targets to be set. - -- @return #table Table of friendly groups. - function AI_A2A_DISPATCHER:EvaluateGCI( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set - local AttackerCount = AttackerSet:Count() - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = self:CountDefendersEngaged( DetectedItem ) - local DefendersMissing = AttackerCount - DefenderCount - self:F( { AttackerCount = AttackerCount, DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - - local Friendlies = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - - if DetectedItem.IsDetected == true then - - return DefendersMissing, Friendlies - end - - return nil, nil - end - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:Order( DetectedItem ) - - local detection = self.Detection -- Functional.Detection#DETECTION_AREAS - - local ShortestDistance = 999999999 - - -- Get coordinate (or nil). - local AttackCoordinate = detection:GetDetectedItemCoordinate( DetectedItem ) - - -- Issue https://github.com/FlightControl-Master/MOOSE/issues/1232 - if AttackCoordinate then - - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - - self:T( { DefenderSquadron = DefenderSquadron.Name } ) - - local Airbase = DefenderSquadron.Airbase - local AirbaseCoordinate = Airbase:GetCoordinate() - - local EvaluateDistance = AttackCoordinate:Get2DDistance( AirbaseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - end - - return ShortestDistance - end - - --- Shows the tactical display. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - function AI_A2A_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local Report = REPORT:New( "Tactical Overview:" ) - - local DefenderGroupCount = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) - return self:Order( t[a] ) < self:Order( t[b] ) - end ) 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 - - -- Show tactical situation - Report:Add( string.format( "\n- Target %s (%s): (#%d) %s", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender and Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end - - 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*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - 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 ) ) - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - - return true - - end - - --- Assigns A2A AI Tasks in relation to the detected items. - -- @param #AI_A2A_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_A2A_DISPATCHER:ProcessDetected( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - local AIGroup = AIGroup -- Wrapper.Group#GROUP - if not AIGroup:IsAlive() then - local DefenderTaskFsm = self:GetDefenderTaskFsm( AIGroup ) - self:F( { Defender = AIGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } ) - if not DefenderTaskFsm:Is( "Started" ) then - self:ClearDefenderTask( AIGroup ) - 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( AIGroup ) - else - if DefenderTask.Target.Set then - local AttackerCount = DefenderTask.Target.Set:Count() - if AttackerCount == 0 then - self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } ) - self:ClearDefenderTaskTarget( AIGroup ) - end - end - end - end - end - end - - local Report = REPORT:New( "Tactical Overviews" ) - - local DefenderGroupCount = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - -- Closest detected targets to be considered first! - -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) - return self:Order( t[a] ) < self:Order( t[b] ) - end ) 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 - - do - local Friendlies = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be GCIed... - if Friendlies then - self:F( { AIGroups = Friendlies } ) - self:ENGAGE( DetectedItem, Friendlies ) - end - end - - do - local DefendersMissing, Friendlies = self:EvaluateGCI( DetectedItem ) - if DefendersMissing and DefendersMissing > 0 then - self:F( { DefendersMissing = DefendersMissing } ) - self:GCI( DetectedItem, DefendersMissing, Friendlies ) - end - end - end - - if self.TacticalDisplay then - self:ShowTacticalDisplay( Detection ) - end - - return true - end - -end - -do - - --- Calculates which HUMAN friendlies are nearby the area. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem 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_A2A_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_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem 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_A2A_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 CAP for the given SquadronName. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName ) - self:CAP( SquadronName ) - end - - --- Add resources to a Squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to add. - function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - - --- Remove resources from a Squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to remove. - function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - -end - -do - - -- @type AI_A2A_GCICAP - -- @extends #AI_A2A_DISPATCHER - - --- Create an automatic air defence system for a coalition setting up GCI and CAP air defenses. - -- The class derives from @{#AI_A2A_DISPATCHER} and thus, all the methods that are defined in the @{#AI_A2A_DISPATCHER} class, can be used also in AI\_A2A\_GCICAP. - -- - -- === - -- - -- # Demo Missions - -- - -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) - -- - -- === - -- - -- # YouTube Channel - -- - -- ### [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) - -- - -- === - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) - -- - -- AI\_A2A\_GCICAP includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy - -- air movements that are detected by an airborne or ground based radar network. - -- - -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. - -- - -- The AI_A2A_GCICAP provides a lightweight configuration method using the mission editor. Within a very short time, and with very little coding, - -- the mission designer is able to configure a complete A2A defense system for a coalition using the DCS Mission Editor available functions. - -- Using the DCS Mission Editor, you define borders of the coalition which are guarded by GCICAP, - -- configure airbases to belong to the coalition, define squadrons flying certain types of planes or payloads per airbase, and define CAP zones. - -- **Very little lua needs to be applied, a one liner**, which is fully explained below, which can be embedded - -- right in a DO SCRIPT trigger action or in a larger DO SCRIPT FILE trigger action. - -- - -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept - -- detected enemy aircraft or they run short of fuel and must return to base (RTB). - -- - -- When a CAP flight leaves their zone to perform a GCI or return to base a new CAP flight will spawn to take its place. - -- If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. - -- - -- In short it is a plug in very flexible and configurable air defence module for DCS World. - -- - -- === - -- - -- # The following actions need to be followed when using AI\_A2A\_GCICAP in your mission: - -- - -- ## 1) Configure a working AI\_A2A\_GCICAP defense system for ONE coalition. - -- - -- ### 1.1) Define which airbases are for which coalition. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_1.JPG) - -- - -- Color the airbases red or blue. You can do this by selecting the airbase on the map, and select the coalition blue or red. - -- - -- ### 1.2) Place groups of units given a name starting with a **EWR prefix** of your choice to build your EWR network. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_2.JPG) - -- - -- **All EWR groups starting with the EWR prefix (text) will be included in the detection system.** - -- - -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. - -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! - -- Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. - -- The position of these units is very important as they need to provide enough coverage - -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- - -- EWR networks are **dynamically maintained**. By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, - -- increasing or decreasing the radar coverage of the Early Warning System. - -- - -- ### 1.3) Place Airplane or Helicopter Groups with late activation switched on above the airbases to define Squadrons. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_3.JPG) - -- - -- These are **templates**, with a given name starting with a **Template prefix** above each airbase that you wanna have a squadron. - -- These **templates** need to be within 1.5km from the airbase center. They don't need to have a slot at the airplane, they can just be positioned above the airbase, - -- without a route, and should only have ONE unit. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_4.JPG) - -- - -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** - -- - -- ### 1.4) Place floating helicopters to create the CAP zones defined by its route points. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_5.JPG) - -- - -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** - -- - -- The helicopter indicates the start of the CAP zone. - -- The route points define the form of the CAP zone polygon. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_6.JPG) - -- - -- **The place of the helicopter is important, as the airbase closest to the helicopter will be the airbase from where the CAP planes will take off for CAP.** - -- - -- ## 2) There are a lot of defaults set, which can be further modified using the methods in @{#AI_A2A_DISPATCHER}: - -- - -- ### 2.1) Planes are taking off in the air from the airbases. - -- - -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiing airplanes, - -- resulting in the airbase to halt operations. - -- - -- You can change the way how planes take off by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- ### 2.2) Planes return near the airbase or will land if damaged. - -- - -- When damaged airplanes return to the airbase, they will be routed and will disappear in the air when they are near the airbase. - -- There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land. - -- - -- You can change the way how planes land by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- ### 2.3) CAP operations setup for specific airbases, will be executed with the following parameters: - -- - -- * The altitude will range between 6000 and 10000 meters. - -- * The CAP speed will vary between 500 and 800 km/h. - -- * The engage speed between 800 and 1200 km/h. - -- - -- You can change or add a CAP zone by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronCap}() defines a CAP execution for a squadron. - -- - -- Setting-up a CAP zone also requires specific parameters: - -- - -- * The minimum and maximum altitude - -- * The minimum speed and maximum patrol speed - -- * The minimum and maximum engage speed - -- * The type of altitude measurement - -- - -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. - -- - -- It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system. - -- - -- For example, the following setup will create a CAP for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ### 2.4) Each airbase will perform GCI when required, with the following parameters: - -- - -- * The engage speed is between 800 and 1200 km/h. - -- - -- You can change or add a GCI parameters by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. - -- - -- Setting-up a GCI readiness also requires specific parameters: - -- - -- * The minimum speed and maximum patrol speed - -- - -- Essentially this controls how many flights of GCI aircraft can be active at any time. - -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. - -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have already passed the ideal interception point! - -- - -- For example, the following setup will create a GCI for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- - -- ### 2.5) Grouping or detected targets. - -- - -- Detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. - -- - -- Targets will be grouped within a radius of 30km by default. - -- - -- The radius indicates that detected targets need to be grouped within a radius of 30km. - -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. - -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- - -- ## 3) Additional notes: - -- - -- In order to create a two way A2A defense system, **two AI\_A2A\_GCICAP defense systems must need to be created**, for each coalition one. - -- Each defense system needs its own EWR network setup, airplane templates and CAP configurations. - -- - -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. - -- - -- ## 4) Coding examples how to use the AI\_A2A\_GCICAP class: - -- - -- ### 4.1) An easy setup: - -- - -- -- Setup the AI_A2A_GCICAP dispatcher for one coalition, and initialize it. - -- GCI_Red = AI_A2A_GCICAP:New( "EWR CCCP", "SQUADRON CCCP", "CAP CCCP", 2 ) - -- -- - -- The following parameters were given to the :New method of AI_A2A_GCICAP, and mean the following: - -- - -- * `"EWR CCCP"`: Groups of the blue coalition are placed that define the EWR network. These groups start with the name `EWR CCCP`. - -- * `"SQUADRON CCCP"`: Late activated Groups objects of the red coalition are placed above the relevant airbases that will contain these templates in the squadron. - -- These late activated Groups start with the name `SQUADRON CCCP`. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level. - -- * `"CAP CCCP"`: CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. - -- These Helicopter Group objects start with the name `CAP CCCP`, and will be the locations wherein CAP will be performed. - -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. - -- - -- ### 4.2) A more advanced setup: - -- - -- -- Setup the AI_A2A_GCICAP dispatcher for the blue coalition. - -- - -- A2A_GCICAP_Blue = AI_A2A_GCICAP:New( { "BLUE EWR" }, { "104th", "105th", "106th" }, { "104th CAP" }, 4 ) - -- - -- The following parameters for the :New method have the following meaning: - -- - -- * `{ "BLUE EWR" }`: An array of the group name prefixes of the groups of the blue coalition are placed that define the EWR network. These groups start with the name `BLUE EWR`. - -- * `{ "104th", "105th", "106th" } `: An array of the group name prefixes of the Late activated Groups objects of the blue coalition are - -- placed above the relevant airbases that will contain these templates in the squadron. - -- These late activated Groups start with the name `104th` or `105th` or `106th`. - -- * `{ "104th CAP" }`: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, - -- where the route points define the route of the polygon of the CAP Zone. - -- These Helicopter Group objects start with the name `104th CAP`, and will be the locations wherein CAP will be performed. - -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. - -- - -- @field #AI_A2A_GCICAP - AI_A2A_GCICAP = { - ClassName = "AI_A2A_GCICAP", - Detection = nil, - } - - --- AI_A2A_GCICAP constructor. - -- @param #AI_A2A_GCICAP self - -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. - -- @param #string TemplatePrefixes A list of template prefixes. - -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). - -- @param #number CapLimit A number of how many CAP maximum will be spawned. - -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. - -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. - -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. - -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. - -- @param #number ResourceCount The amount of resources that will be allocated to each squadron. - -- @return #AI_A2A_GCICAP - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is DF CCCP. All groups starting with DF CCCP will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is nil. No CAP is created. - -- -- The CAP Limit is nil. - -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. - -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 ) - -- - function AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - local EWRSetGroup = SET_GROUP:New() - EWRSetGroup:FilterPrefixes( EWRPrefixes ) - EWRSetGroup:FilterStart() - - local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) - - local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_GCICAP - - self:SetEngageRadius( EngageRadius ) - self:SetGciRadius( GciRadius ) - - -- Determine the coalition of the EWRNetwork, this will be the coalition of the GCICAP. - local EWRFirst = EWRSetGroup:GetFirst() -- Wrapper.Group#GROUP - local EWRCoalition = EWRFirst:GetCoalition() - - -- Determine the airbases belonging to the coalition. - local AirbaseNames = {} -- #list<#string> - for AirbaseID, AirbaseData in pairs( _DATABASE.AIRBASES ) do - local Airbase = AirbaseData -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - if Airbase:GetCoalition() == EWRCoalition then - table.insert( AirbaseNames, AirbaseName ) - end - end - - self.Templates = SET_GROUP:New():FilterPrefixes( TemplatePrefixes ):FilterOnce() - - -- Setup squadrons - - self:T( { Airbases = AirbaseNames } ) - - self:T( "Defining Templates for Airbases ..." ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local AirbaseCoord = Airbase:GetCoordinate() - local AirbaseZone = ZONE_RADIUS:New( "Airbase", AirbaseCoord:GetVec2(), 3000 ) - local Templates = nil - self:T( { Airbase = AirbaseName } ) - for TemplateID, Template in pairs( self.Templates:GetSet() ) do - local Template = Template -- Wrapper.Group#GROUP - local TemplateCoord = Template:GetCoordinate() - if AirbaseZone:IsVec2InZone( TemplateCoord:GetVec2() ) then - Templates = Templates or {} - table.insert( Templates, Template:GetName() ) - self:T( { Template = Template:GetName() } ) - end - end - if Templates then - self:SetSquadron( AirbaseName, AirbaseName, Templates, ResourceCount ) - end - end - - -- Setup CAP. - -- Find for each CAP the nearest airbase to the (start or center) of the zone. - -- CAP will be launched from there. - - self.CAPTemplates = SET_GROUP:New() - self.CAPTemplates:FilterPrefixes( CapPrefixes ) - self.CAPTemplates:FilterOnce() - - self:T( "Setting up CAP ..." ) - for CAPID, CAPTemplate in pairs( self.CAPTemplates:GetSet() ) do - local CAPZone = ZONE_POLYGON:New( CAPTemplate:GetName(), CAPTemplate ) - -- Now find the closest airbase from the ZONE (start or center) - local AirbaseDistance = 99999999 - local AirbaseClosest = nil -- Wrapper.Airbase#AIRBASE - self:T( { CAPZoneGroup = CAPID } ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local AirbaseCoord = Airbase:GetCoordinate() - local Squadron = self.DefenderSquadrons[AirbaseName] - if Squadron then - local Distance = AirbaseCoord:Get2DDistance( CAPZone:GetCoordinate() ) - self:T( { AirbaseDistance = Distance } ) - if Distance < AirbaseDistance then - AirbaseDistance = Distance - AirbaseClosest = Airbase - end - end - end - if AirbaseClosest then - self:T( { CAPAirbase = AirbaseClosest:GetName() } ) - self:SetSquadronCap( AirbaseClosest:GetName(), CAPZone, 6000, 10000, 500, 800, 800, 1200, "RADIO" ) - self:SetSquadronCapInterval( AirbaseClosest:GetName(), CapLimit, 300, 600, 1 ) - end - end - - -- Setup GCI. - -- GCI is setup for all Squadrons. - self:T( "Setting up GCI ..." ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local Squadron = self.DefenderSquadrons[AirbaseName] - self:F( { Airbase = AirbaseName } ) - if Squadron then - self:T( { GCIAirbase = AirbaseName } ) - self:SetSquadronGci( AirbaseName, 800, 1200 ) - end - end - - self:__Start( 5 ) - - 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 ) - - return self - end - - --- AI_A2A_GCICAP constructor with border. - -- @param #AI_A2A_GCICAP self - -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. - -- @param #string TemplatePrefixes A list of template prefixes. - -- @param #string BorderPrefix A Border Zone Prefix. - -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). - -- @param #number CapLimit A number of how many CAP maximum will be spawned. - -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. - -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. - -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. - -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. - -- @param #number ResourceCount The amount of resources that will be allocated to each squadron. - -- @return #AI_A2A_GCICAP - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is nil. No CAP is created. - -- -- The CAP Limit is nil. - -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. - -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 ) - -- - function AI_A2A_GCICAP:NewWithBorder( EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - local self = AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - if BorderPrefix then - self:SetBorderZone( ZONE_POLYGON:New( BorderPrefix, GROUP:FindByName( BorderPrefix ) ) ) - end - - return self - - end - -end diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua deleted file mode 100644 index 3d56e0227..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ /dev/null @@ -1,149 +0,0 @@ ---- **AI** - Models the process of Ground Controlled Interception (GCI) for airplanes. --- --- This is a class used in the @{AI.AI_A2A_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Gci --- @image AI_Ground_Control_Intercept.JPG - - - --- @type AI_A2A_GCI --- @extends AI.AI_A2A#AI_A2A - - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- ![Banner Image](..\Images\deprecated.png) --- --- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event. --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ## 1. AI_A2A_GCI constructor --- --- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object. --- --- ## 2. AI_A2A_GCI is a FSM --- --- ![Process](..\Presentations\AI_GCI\Dia2.JPG) --- --- ### 2.1 AI_A2A_GCI States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2 AI_A2A_GCI Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_A2A_GCI.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_A2A_GCI.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2A_GCI -AI_A2A_GCI = { - ClassName = "AI_A2A_GCI", -} - - - ---- Creates a new AI_A2A_GCI object --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_A2A_GCI -function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local AI_Air = AI_AIR:New( AIIntercept ) - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - return self -end - ---- Creates a new AI_A2A_GCI object --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_A2A_GCI -function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) -end - ---- onafter State Transition for Event Patrol. --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To ) - - self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To ) -end - - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2A_GCI self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2A_GCI self -function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() - -- Maybe the detected set also contains - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua deleted file mode 100644 index 6c7ce68cc..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ /dev/null @@ -1,411 +0,0 @@ ---- **AI** - Models the process of air patrol of airplanes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Patrol --- @image AI_Air_Patrolling.JPG - - --- @type AI_A2A_PATROL --- @extends AI.AI_A2A#AI_A2A - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) --- --- ## 1. AI_A2A_PATROL constructor --- --- * @{#AI_A2A_PATROL.New}(): Creates a new AI_A2A_PATROL object. --- --- ## 2. AI_A2A_PATROL is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 2.1. AI_A2A_PATROL States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 2.2. AI_A2A_PATROL Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set or Get the AI controllable --- --- * @{#AI_A2A_PATROL.SetControllable}(): Set the AIControllable. --- * @{#AI_A2A_PATROL.GetControllable}(): Get the AIControllable. --- --- ## 4. Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_A2A_PATROL.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_A2A_PATROL.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 5. Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_A2A_PATROL.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targeted to the AI_A2A_PATROL. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place. --- --- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL --- --- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on. --- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2A_PATROL -AI_A2A_PATROL = { - ClassName = "AI_A2A_PATROL", -} - ---- Creates a new AI_A2A_PATROL object --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The patrol group object. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to BARO --- @return #AI_A2A_PATROL self --- @usage --- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIPatrol ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "BARO" if not specified - self.PatrolAltType = PatrolAltType or "BARO" - - self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" ) - ---- OnBefore Transition Handler for Event Patrol. --- @function [parent=#AI_A2A_PATROL] OnBeforePatrol --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Patrol. --- @function [parent=#AI_A2A_PATROL] OnAfterPatrol --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Patrol. --- @function [parent=#AI_A2A_PATROL] Patrol --- @param #AI_A2A_PATROL self - ---- Asynchronous Event Trigger for Event Patrol. --- @function [parent=#AI_A2A_PATROL] __Patrol --- @param #AI_A2A_PATROL self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Patrolling. --- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_A2A_PATROL] OnBeforeRoute --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Route. --- @function [parent=#AI_A2A_PATROL] OnAfterRoute --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Route. --- @function [parent=#AI_A2A_PATROL] Route --- @param #AI_A2A_PATROL self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_A2A_PATROL] __Route --- @param #AI_A2A_PATROL self --- @param #number Delay The delay in seconds. - - - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL. - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_A2A_PATROL self --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_A2A_PATROL self --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_A2A_PATROL self --- @return #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To ) - self:F2() - - self:ClearTargetDistance() - - self:__Route( 1 ) - - AIPatrol:OnReSpawn( - function( PatrolGroup ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) -end - - ---- This static method is called from the route path within the last task at the last waypoint of the AIPatrol. --- Note that this method is required, as triggers the next route when patrolling for the AIPatrol. --- @param Wrapper.Group#GROUP AIPatrol The AI group. --- @param #AI_A2A_PATROL Fsm The FSM. -function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } ) - - if AIPatrol and AIPatrol:IsAlive() then - Fsm:Route() - end - -end - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - - if AIPatrol and AIPatrol:IsAlive() then - - local PatrolRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIPatrol:GetCoordinate() - - -- Random altitude. - local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude) - - -- Random speed in km/h. - local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed) - - -- First waypoint is current position. - PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current") - - if self.racetrack then - - -- Random heading. - local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) - - -- Random leg length. - local leg=math.random(self.racetracklegmin, self.racetracklegmax) - - -- Random duration if any. - local duration = self.racetrackdurationmin - if self.racetrackdurationmax then - duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) - end - - -- CAP coordinate. - local c0=self.PatrolZone:GetRandomCoordinate() - if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then - c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] - end - - -- Race track points. - local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE - local c2=c1:Translate(leg, heading):SetAltitude(altitude) - - self:SetTargetDistance(c0) -- For RTB status check - - -- Debug: - self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) - --c1:MarkToAll("Race track c1") - --c2:MarkToAll("Race track c2") - - -- Task to orbit. - local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) - - -- Task function to redo the patrol at other random position. - local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self) - - -- Controlled task with task condition. - local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) - local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) - - -- Second waypoint - PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") - - else - - -- Target coordinate. - local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE - ToTargetCoord:SetAltitude(altitude) - - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - - local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self ) - - PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point") - - end - - -- ROE - AIPatrol:OptionROEReturnFire() - AIPatrol:OptionROTEvadeFire() - - -- Patrol. - AIPatrol:Route( PatrolRoute, 0.5) - end - -end - diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua deleted file mode 100644 index 8a39ad0d2..000000000 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ /dev/null @@ -1,98 +0,0 @@ ---- **AI** - Models the process of air to ground BAI engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_BAI --- @image AI_Air_To_Ground_Engage.JPG - --- @type AI_A2G_BAI --- @extends AI.AI_A2A_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- ![Banner Image](..\Images\deprecated.png) --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2G_BAI -AI_A2G_BAI = { - ClassName = "AI_A2G_BAI", -} - ---- Creates a new AI_A2G_BAI object --- @param #AI_A2G_BAI self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_BAI -function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) - - return self -end - ---- Creates a new AI_A2G_BAI object --- @param #AI_A2G_BAI self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_BAI -function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType) -end - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2G_BAI self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2G_BAI self -function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "BAI Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua deleted file mode 100644 index d4c0fe417..000000000 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ /dev/null @@ -1,98 +0,0 @@ ---- **AI** - Models the process of air to ground engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_CAS --- @image AI_Air_To_Ground_Engage.JPG - --- @type AI_A2G_CAS --- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL TODO: Documentation. This class does not exist, unable to determine what it extends. - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- # Developer Note --- --- ![Banner Image](..\Images\deprecated.png) --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2G_CAS -AI_A2G_CAS = { - ClassName = "AI_A2G_CAS", -} - ---- Creates a new AI_A2G_CAS object --- @param #AI_A2G_CAS self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_CAS -function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) - - return self -end - ---- Creates a new AI_A2G_CAS object --- @param #AI_A2G_CAS self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_CAS -function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType) -end - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2G_CAS self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2G_CAS self -function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "CAS Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua deleted file mode 100644 index 5aff7ad4f..000000000 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ /dev/null @@ -1,4794 +0,0 @@ ---- **AI** - Create an automated A2G defense system with reconnaissance units, coordinating SEAD, BAI and CAS operations. --- --- === --- --- Features: --- --- * Setup quickly an A2G defense system for a coalition. --- * Setup multiple defense zones to defend specific coordinates in your battlefield. --- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. --- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. --- * Define and use a detection network controlled by recce. --- * Define A2G defense squadrons at airbases, FARPs and carriers. --- * Enable airbases for A2G defenses. --- * Add different planes and helicopter templates to squadrons. --- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, FARPs or carriers. --- * Define different ranges to engage upon. --- * Establish an automatic in air refuel process for planes using refuel tankers. --- * Setup default settings for all squadrons and A2G defenses. --- * Setup specific settings for specific squadrons. --- --- === --- --- ## Missions: --- --- [AID-A2G - AI A2G Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2G_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - A2G DISPATCHER - Build an automatic A2G Defense System - Introduction](https://www.youtube.com/watch?v=zwSxWRAGVH8) --- --- === --- --- # QUICK START GUIDE --- --- ![Banner Image](..\Images\deprecated.png) --- --- The following class is available to model an A2G defense system. --- --- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system. --- --- Before you start using the AI_A2G_DISPATCHER, ask yourself the following questions: --- --- --- ## 1. Which coalition am I modeling an A2G defense system for? Blue or red? --- --- One AI_A2G_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_A2G_DISPATCHER **objects**, --- each governing their defense system for one coalition. --- --- --- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units --- and reporting them to the head quarters. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple recce as one co-operating entity. --- * Communicate with a @{Tasking.CommandCenter}, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- --- ## 3. Which recce units can be used as part of the detection system? Only ground based, or also airborne? --- --- Depending on the type of mission you want to achieve, different types of units can be engaged to perform ground enemy targets reconnaissance. --- Ground recce (FAC) are very useful units to determine the position of enemy ground targets when they spread out over the battlefield at strategic positions. --- Using their varying detection technology, and especially those ground units which have spotting technology, can be extremely effective at --- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. --- Unfortunately, they lack sometimes the visibility to detect targets at greater range, or when scenery is preventing line of sight. --- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then --- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! --- --- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, --- so you need air superiority to make them effective. --- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective --- compared to air units having only visual detection capabilities. --- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. --- --- Typically, don't want these recce units to engage with the enemy, you want to keep them at position. Therefore, it is a good practice --- to set the ROE for these recce to hold weapons, and make them invisible from the enemy. --- --- It is not possible to perform a recce function as a player (unit). --- --- --- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? --- --- The A2G dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. --- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. --- --- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. --- --- The A2G dispatcher provides various parameters to setup the **defensiveness**, --- which models the decision **when** a defender will engage with the approaching enemy. --- Defensiveness is calculated by a probability distribution model when to trigger a defense action, --- depending on the distance of the enemy unit from the defense coordinates, and a **defensiveness factor**. --- --- The other parameter considered for defensive action is **where the enemy is located**, thus the distance from a defense coordinate, --- which we call the **reactive distance**. By default, the reactive distance is set to 60km, but can be changed by the mission designer --- using the available method explained further below. --- The combination of the defensiveness and reactivity results in a model that, the closer the attacker is to the defense point, --- the higher the probability will be that a defense action will be launched! --- --- --- ## 5. Are defense coordinates and defense reactivity the only parameters? --- --- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is --- detected, even when both enemies are at the same distance from a defense coordinate. --- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. --- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. --- --- --- ## 6. Which Squadrons will I create and which name will I give each Squadron? --- --- The A2G defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful --- for your mission, and remember that squadron names are used for communication to the players of your mission. --- --- There are mainly 3 types of defenses: **SEAD**, **BAI**, and **CAS**. --- --- Suppression of Air Defenses (SEAD) are effective against radar emitters. --- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. --- Close Air Support (CAS) is launched when the enemy is close near friendly units. --- --- Depending on the defense type, different payloads will be needed. See further points on squadron definition. --- --- --- ## 7. Where will the Squadrons be located? On Airbases? On Carriers? On FARPs? --- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **FARP**. --- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. --- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. --- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! --- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, FARP, or carrier can act as the launching platform for A2G defenses. --- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, --- or your air units will not return for landing at the airbase! --- --- --- ## 8. Which helicopter or plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple helicopter or plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The A2G defense system will select from the given templates a random template to spawn a new plane (group). --- --- A squadron will perform specific task types (SEAD, BAI or CAS). So, squadrons will require specific templates for the --- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. --- --- --- ## 9. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The A2G defense system will select from the given templates a random template to spawn a new plane (group). --- --- --- ## 10. How do squadrons engage in a defensive action? --- --- There are two ways how squadrons engage and execute your A2G defenses. --- Squadrons can start the defense directly from the airbase, FARP or carrier. When a squadron launches a defensive group, that group --- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. --- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne --- A2G defenses can come immediately into action. --- --- --- ## 11. For each Squadron doing a patrol, which zone types will I create? --- --- Per zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- --- ## 12. Are moving defense coordinates possible? --- --- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. --- --- --- ## 13. How many defense coordinates do I need to create? --- --- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too many defense coordinates, the performance of your mission may decrease. For each defined defense coordinate, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the associated defense radius. --- The default defense radius is about 60km. Depending on the defense reactivity, defenses will be launched when the enemy is at a --- closer distance from the defense coordinate than the defense radius. --- --- --- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? --- --- For each patrol: --- --- * **How many** patrols you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new patrol? --- --- Other considerations: --- --- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! --- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? --- --- --- ## 15. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. --- --- --- ## 16. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is to despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid aircraft clutter at airbases, but it is the least realistic one. --- --- --- ## 19. For each Squadron, which **defense overhead** will I use? --- --- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? --- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of aircraft (era), payload, fuel levels, skills etc. --- But the most important factor is the payload, which is the amount of A2G weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 Vikhrs, this means that it potentially can destroy at least 8 ground units without a reload of ammunition. --- That means, that one defender can destroy more enemy ground units. --- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, --- one defender for each 4 detected ground enemy units. ** --- --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defenses be grouped when multiple defense air units are spawned for multiple enemy ground units? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- But you can specify a number between 1 and 4, so that the defenders will act as a group. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- ### Author: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). --- --- @module AI.AI_A2G_Dispatcher --- @image AI_Air_To_Ground_Dispatching.JPG - - -do -- AI_A2G_DISPATCHER - - --- AI_A2G_DISPATCHER class. - -- @type AI_A2G_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. - -- - -- === - -- - -- When your mission is in the need to take control of the AI to automate and setup a process of air to ground defenses, this is the module you need. - -- The defense system work through the definition of defense coordinates, which are points in your friendly area within the battle field, that your mission need to have defended. - -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. - -- The A2G dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate - -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. - -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly A2G units. - -- - -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. - -- It also requires a little study to ensure that you get a good understanding of the defense mechanisms, to ensure a strong - -- defense for your missions. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- - -- ## 1. AI\_A2G\_DISPATCHER constructor: - -- - -- - -- The @{#AI_A2G_DISPATCHER.New}() method creates a new AI_A2G_DISPATCHER instance. - -- - -- - -- ### 1.1. Define the **reconnaissance network**: - -- - -- As part of the AI_A2G_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. - -- A reconnaissance network is provided by passing a @{Functional.Detection} object. - -- The most effective reconnaissance for the A2G dispatcher would be to use the @{Functional.Detection#DETECTION_AREAS} object. - -- - -- A reconnaissance network, is used to detect enemy ground targets, - -- potentially group them into areas, and to understand the position, level of threat of the enemy. - -- - -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. - -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. - -- Recce are very useful to acquire the position of enemy ground targets when spread out over the battlefield at strategic positions. - -- Ground units also have varying detectors, and especially the ground units which have laser guiding missiles can be extremely effective at - -- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. - -- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then - -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! - -- - -- Beside ground level units to use for reconnaissance, air units are also very effective. The are capable of patrolling at great speed - -- covering a large terrain. However, airborne recce can be vulnerable to air to ground attacks, and you need air superiority to make then - -- effective. Also the instruments available at the air units play a big role in the effectiveness of the reconnaissance. - -- Air units which have ground detection capabilities will be much more effective than air units with only visual detection capabilities. - -- For the red coalition, the Mi-28N and for the blue side, the reaper are such effective reconnaissance airborne units. - -- - -- Reconnaissance networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection} instance that is given as the first parameter to the A2G dispatcher. - -- By defining in a **smart way the names or name prefixes of the reconnaissance groups**, these groups will be **automatically added or removed** to or from the reconnaissance network, - -- when these groups are spawned in or destroyed during the ongoing battle. - -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously - -- alerted of new enemy ground targets. - -- - -- The following is an example defense of a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. - -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Define a set of group objects, called DetectionSetGroup. - -- - -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, - -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- - -- -- This command defines the reconnaissance network. - -- -- It will group any detected ground enemy targets within a radius of 1km. - -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) - -- - -- -- Setup the A2G dispatcher, and initialize it. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. - -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. - -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. - -- - -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network - -- configuration and setup the A2G defense detection mechanism. - -- - -- - -- ### 1.2. Setup the A2G dispatcher for both a red and blue coalition. - -- - -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate A2G dispatcher. - -- Ensure that while doing so, that you name the objects differently both for red and blue coalition. - -- - -- For example like this for the red coalition: - -- - -- DetectionRed = DETECTION_AREAS:New( DetectionSetGroupRed, 1000 ) - -- A2GDispatcherRed = AI_A2G_DISPATCHER:New( DetectionRed ) - -- - -- And for the blue coalition: - -- - -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) - -- A2GDispatcherBlue = AI_A2G_DISPATCHER:New( DetectionBlue ) - -- - -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! - -- - -- - -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: - -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method - -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. - -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. - -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher, - -- so don't make this value too small! Again, about 1km, or 1000 meters, is recommended. - -- - -- - -- ## 2. Setup (a) **Defense Coordinate(s)**. - -- - -- As explained above, defense coordinates are the center of your defense operations. - -- The more threat to the defense coordinate, the higher it is likely a defensive action will be launched. - -- - -- Find below an example how to add defense coordinates: - -- - -- -- Add defense coordinates. - -- A2GDispatcher:AddDefenseCoordinate( "HQ", GROUP:FindByName( "HQ" ):GetCoordinate() ) - -- - -- In this example, the coordinate of a group called `"HQ"` is retrieved, using `:GetCoordinate()` - -- This returns a COORDINATE object, pointing to the first unit within the GROUP object. - -- - -- The method @{#AI_A2G_DISPATCHER.AddDefenseCoordinate}() adds a new defense coordinate to the `A2GDispatcher` object. - -- The first parameter is the key of the defense coordinate, the second the coordinate itself. - -- - -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an A2G dispatcher. - -- - -- **REMEMBER!** - -- - -- - **Defense coordinates are the center of the A2G dispatcher defense system!** - -- - **You can define more defense coordinates to defend a larger area.** - -- - **Detected enemy ground targets are not immediately engaged, but are engaged with a reactivity or probability calculation!** - -- - -- But, there is more to it ... - -- - -- - -- ### 2.1. The **Defense Radius**. - -- - -- The defense radius defines the maximum radius that a defense will be initiated around each defense coordinate. - -- So even when there are targets further away than the defense radius, then these targets won't be engaged upon. - -- By default, the defense radius is set to 100km (100.000 meters), but can be changed using the @{#AI_A2G_DISPATCHER.SetDefenseRadius}() method. - -- Note that the defense radius influences the defense reactivity also! The larger the defense radius, the more reactive the defenses will be. - -- - -- For example: - -- - -- A2GDispatcher:SetDefenseRadius( 30000 ) - -- - -- This defines an A2G dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. - -- Note that the defense radius **applies to all defense coordinates** defined within the A2G dispatcher. - -- - -- - -- ### 2.2. The **Defense Reactivity**. - -- - -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is - -- also determined by the distance of the enemy ground target to the defense coordinate. - -- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then - -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods - -- @{#AI_A2G_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_A2G_DISPATCHER.SetDefenseReactivityHigh}() respectively. - -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, - -- the less reactive the defenses will be in terms of distance to enemy ground targets! - -- - -- For example: - -- - -- A2GDispatcher:SetDefenseReactivityHigh() - -- - -- This defines an A2G dispatcher with high defense reactivity. - -- - -- - -- ## 3. **Squadrons**. - -- - -- The A2G dispatcher works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, FARP or carrier, - -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. - -- - -- **Multiple squadrons** can be defined within one A2G dispatcher, each having specific defense tasks and defense parameter settings! - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or **key** of the squadron. - -- * Have specific helicopter or plane **templates**. - -- * Are located at **one** airbase, farp or carrier. - -- * 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 all `A2GDispatcher:SetSquadron...()` or `A2GDispatcher:GetSquadron...()` methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). - -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, - -- the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- The method @{#AI_A2G_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. - -- - -- For example, this defines 3 new squadrons: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50" }, 10 ) - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50" }, 10 ) - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50" }, 10 ) - -- - -- The latter 2 will depart from FARPs, which bare the name `"CAS"` and `"BAI"`. - -- - -- - -- ### 3.1. Squadrons **Tasking**. - -- - -- Squadrons can be commanded to execute 3 types of tasks, as explained above: - -- - -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the front-line. - -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - -- You need to configure each squadron which task types you want it to perform. Read on ... - -- - -- - -- ### 3.2. Squadrons enemy ground target **engagement types**. - -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, FARP or carrier, or through a **patrol**. - -- - -- Patrols are extremely handy, as these will get your helicopters or airplanes airborne in advance. They will patrol in defined zones outlined, - -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required - -- to engage is heavily minimized! - -- - -- However; patrols come with a side effect: since your resources are airborne, they will be vulnerable to incoming air attacks from the enemy. - -- - -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. - -- - -- - -- ### 3.3. Squadron **on call** engagement. - -- - -- So to make squadrons engage targets from the airfields, use the following methods: - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSead}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBai}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. - -- - -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. - -- Especially the payload (weapons configuration) is important to get right. - -- - -- For example, the following will define for the squadrons different tasks: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- A2GDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) - -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) - -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) - -- - -- - -- ### 3.4. Squadron **on patrol engagement**. - -- - -- Squadrons can be setup to patrol in the air near the engagement hot zone. - -- When needed, the A2G defense units will be close to the battle area, and can engage quickly. - -- - -- So to make squadrons engage targets from a patrol zone, use the following methods: - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrol}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. - -- - -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrolInterval}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. - -- - -- Here an example to setup patrols of various task types: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) - -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) - -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- - -- - -- ### 3.5. Set squadron takeoff methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. - -- - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default landing method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft takeoff or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the takeoff methods - -- - -- -- Set the default takeoff method - -- A2GDispatcher:SetDefaultTakeoffFromRunway() - -- - -- -- Set the individual squadrons takeoff method - -- A2GDispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) - -- A2GDispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2GDispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- - -- ### 3.5.2. Set Squadron takeoff interval. - -- - -- The different types of available airfields have different amounts of available launching platforms: - -- - -- - Airbases typically have a lot of platforms. - -- - FARPs have 4 platforms. - -- - Ships have 2 to 4 platforms. - -- - -- Depending on the demand of requested takeoffs by the A2G dispatcher, an airfield can become overloaded. Too many aircraft need to be taken - -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, - -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. - -- - -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInterval}() can be used to specify the takeoff intervals of - -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. - -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time - -- has been reached, a new group will be spawned or activated for takeoff. - -- - -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and - -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. - -- - -- See the underlying example: - -- - -- -- Imagine a squadron launched from a FARP, with a grouping of 4. - -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. - -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes for helicopters to takeoff from FARPs in cold start. - -- A2GDispatcher:SetSquadronTakeoffInterval( "Mineralnye", 60 * 4 ) - -- - -- - -- ### 3.6. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_A2G_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2G defense system, as no new SEAD, BAI or CAS planes can takeoff. - -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2GDispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2GDispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2GDispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2GDispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2GDispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 3.7. Set squadron **grouping**. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. - -- - -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. - -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, - -- an additional on call flight needs to be started. - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the A2G defenses. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_A2G_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. - -- - -- For example, a A-10C with full long-distance A2G missiles payload, may still be less effective than a Su-23 with short range A2G missiles... - -- So in this case, one may want to use the @{#AI_A2G_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: - -- - -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. - -- - -- Typically, for A2G defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: - -- - -- - A-10C: 0.15 - -- - Su-34: 0.15 - -- - A-10A: 0.25 - -- - SU-25T: 0.10 - -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, - -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **engage limit**. - -- - -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. - -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. - -- - -- ## 4. Set the **fuel threshold**. - -- - -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, there are two possible actions that can be taken: - -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. - -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. - -- - -- ## 6. Other configuration options - -- - -- ### 6.1. Set a tactical display panel. - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_A2G_DISPATCHER. - -- Use the method @{#AI_A2G_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Default settings. - -- - -- Default settings configure the standard behaviour of the squadrons. - -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. - -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. - -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. - -- - -- ## 10.1. Default **takeoff** behaviour. - -- - -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing behaviour. - -- - -- The default landing behaviour is set to **near the airbase**, which means that returning aircraft will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_A2G_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default **overhead**. - -- - -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_A2G_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default **grouping**. - -- - -- The default grouping is set to **one aircraft**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned aircraft for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned aircraft for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an aircraft is **damaged** to a certain %, which is **40% (0.40)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned aircraft for all squadrons. - -- - -- ## 10.7. Default settings for **patrol**. - -- - -- ### 10.7.1. Default **patrol time Interval**. - -- - -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. - -- - -- The default patrol time interval is between **180** and **600** seconds. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. - -- - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ### 10.7.2. Default **patrol limit**. - -- - -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. - -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. - -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. - -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS operations. - -- - -- Instead of sending SEAD, BAI and CAS aircraft to RTB when out of fuel, you can let SEAD, BAI and CAS aircraft refuel in mid air using a tanker. - -- This greatly increases the efficiency of your SEAD, BAI and CAS operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender aircraft tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2GDispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is when there is an enemy unit near the airbase and there are no friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, further SEAD, BAI, and CAS operations from that airbase will stop. - -- However, the squadron will still stay alive. Any aircraft that is airborne will continue its operations until all airborne aircraft - -- of the squadron are destroyed. This is to keep consistency of air operations and avoid confusing players. - -- - -- - -- - -- - -- @field #AI_A2G_DISPATCHER - AI_A2G_DISPATCHER = { - ClassName = "AI_A2G_DISPATCHER", - Detection = nil, - } - - --- Definition of a Squadron. - -- @type AI_A2G_DISPATCHER.Squadron - -- @field #string Name The Squadron name. - -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase. - -- @field #string AirbaseName The name of the home airbase. - -- @field Core.Spawn#SPAWN Spawn The spawning object. - -- @field #number ResourceCount The number of resources available. - -- @field #list<#string> TemplatePrefixes The list of template prefixes. - -- @field #boolean Captured true if the squadron is captured. - -- @field #number Overhead The overhead for the squadron. - - --- List of defense coordinates. - -- @type AI_A2G_DISPATCHER.DefenseCoordinates - -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. - - -- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates - AI_A2G_DISPATCHER.DefenseCoordinates = {} - - --- Enumerator for spawns at airbases. - -- @type AI_A2G_DISPATCHER.Takeoff - -- @extends Wrapper.Group#GROUP.Takeoff - - -- @field #AI_A2G_DISPATCHER.Takeoff Takeoff - AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff - - --- Defines Landing location. - -- @field #AI_A2G_DISPATCHER.Landing - AI_A2G_DISPATCHER.Landing = { - NearAirbase = 1, - AtRunway = 2, - AtEngineShutdown = 3, - } - - --- A defense queue item description. - -- @type AI_A2G_DISPATCHER.DefenseQueueItem - -- @field Squadron - -- @field #AI_A2G_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. - -- @field DefendersNeeded - -- @field Defense - -- @field DefenseTaskType - -- @field Functional.Detection#DETECTION_BASE AttackerDetection - -- @field DefenderGrouping - -- @field #string SquadronName The name of the squadron. - - --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched from FARPs, in the air, from airbases, or from carriers. - -- And some of these platforms have very limited amount of "launching" platforms. - -- Therefore, this queue concept is introduced that queues each defender request. - -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. - -- This guarantees that launched defenders are also directly existing ... - -- @type AI_A2G_DISPATCHER.DefenseQueue - -- @list<#AI_A2G_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... - - -- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue - AI_A2G_DISPATCHER.DefenseQueue = {} - - --- Defense approach types. - -- @type AI_A2G_DISPATCHER.DefenseApproach - AI_A2G_DISPATCHER.DefenseApproach = { - Random = 1, - Distance = 2, - } - - --- AI_A2G_DISPATCHER constructor. - -- This is defining the A2G DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @return #AI_A2G_DISPATCHER self - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- - function AI_A2G_DISPATCHER:New( Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_A2G_DISPATCHER - - self.Detection = Detection -- Functional.Detection#DETECTION_AREAS - - self.Detection:FilterCategories( Unit.Category.GROUND_UNIT ) - - -- 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. - - -- TODO: Check detection through radar. --- self.Detection:FilterCategories( { Unit.Category.GROUND } ) --- self.Detection:InitDetectRadar( false ) --- self.Detection:InitDetectVisual( true ) --- self.Detection:SetRefreshTimeInterval( 30 ) - - self.SetSendPlayerMessages = false --flash messages to players - - self:SetDefenseRadius() - self:SetDefenseLimit( nil ) - self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random ) - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above ground level (AGL). - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.NearAirbase ) - self:SetDefaultOverhead( 1 ) - self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the aircraft 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_A2G_DISPATCHER] OnAfterAssign - -- @param #AI_A2G_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_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforePatrol - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Patrol Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterPatrol - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Patrol Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Patrol - -- @param #AI_A2G_DISPATCHER self - - --- Patrol Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Patrol - -- @param #AI_A2G_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Defend", "*" ) - - --- Defend Handler OnBefore for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforeDefend - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Defend Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterDefend - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Defend Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Defend - -- @param #AI_A2G_DISPATCHER self - - --- Defend Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Defend - -- @param #AI_A2G_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Engage", "*" ) - - --- Engage Handler OnBefore for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforeEngage - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Engage Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterEngage - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Engage Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Engage - -- @param #AI_A2G_DISPATCHER self - - --- Engage Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Engage - -- @param #AI_A2G_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:SetDefenseReactivityMedium() - - self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - - self:__Start( 1 ) - - return self - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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 - self:T( "Parked resources for squadron " .. DefenderSquadron.Name ) - end - - end - - - --- Locks the DefenseItem from being defended. - -- @param #AI_A2G_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_A2G_DISPATCHER:Lock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Locked = DetectedItem } ) - self.Detection:LockDetectedItem( DetectedItem ) - end - end - - - --- Unlocks the DefenseItem from being defended. - -- @param #AI_A2G_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_A2G_DISPATCHER:Unlock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - self:F( { Index = self.Detection.DetectedItemsByIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Unlocked = DetectedItem } ) - self.Detection:UnlockDetectedItem( DetectedItem ) - end - end - - - --- Sets maximum zones to be engaged at one time by defenders. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseLimit The maximum amount of detected items to be engaged at the same time. - function AI_A2G_DISPATCHER:SetDefenseLimit( DefenseLimit ) - self:F( { DefenseLimit = DefenseLimit } ) - - self.DefenseLimit = DefenseLimit - end - - - --- Sets the method of the tactical approach of the defenses. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseApproach Use the structure AI_A2G_DISPATCHER.DefenseApproach to set the defense approach. - -- The default defense approach is AI_A2G_DISPATCHER.DefenseApproach.Random. - function AI_A2G_DISPATCHER:SetDefenseApproach( DefenseApproach ) - self:F( { DefenseApproach = DefenseApproach } ) - - self._DefenseApproach = DefenseApproach - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventBaseCaptured( EventData ) - - local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. - - self:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize 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:T( "Squadron " .. SquadronName .. " captured." ) - end - end - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData ) - self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_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_A2G_DISPATCHER.Landing.AtRunway then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ResourcePark( Squadron ) - return - end - if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then - -- Damaged units cannot be repaired anymore. - DefenderUnit:Destroy() - return - end - end - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_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_A2G_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 ) - end - end - end - - - do -- Manage the defensive behaviour - - -- @param #AI_A2G_DISPATCHER self - -- @param #string DefenseCoordinateName The name of the coordinate to be defended by A2G defenses. - -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by A2G defenses. - function AI_A2G_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) - self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityLow() - self.DefenseReactivity = 0.05 - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityMedium() - self.DefenseReactivity = 0.15 - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityHigh() - self.DefenseReactivity = 0.5 - end - - end - - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- A2GDispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- A2GDispatcher:SetDisengageRadius() -- 300000 is the default value. - -- - function AI_A2G_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, BAI, or CAS for defense. - -- When targets are detected that are still really far off, you don't want the AI_A2G_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 **40000** 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_A2G_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons, - -- **the Defense Radius is defined for ALL squadrons which are operational.** - -- - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseRadius (Optional, Default = 20000) The defense radius to engage detected targets from the nearest capable and available squadron airbase. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_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_A2G_DISPATCHER:SetDefenseRadius( DefenseRadius ) - - self.DefenseRadius = DefenseRadius or 40000 - - 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 @{Core.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_A2G_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_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- A2GDispatcher:SetTacticalDisplay( true ) - -- - function AI_A2G_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold is by default set to 40%, which means that when the aircraft is 40% damaged, it will go RTB. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of damage when the aircraft will go RTB. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the aircraft is 90% damaged. - -- - function AI_A2G_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_A2G_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_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol time interval. - -- A2GDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. - -- - function AI_A2G_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_A2G_DISPATCHER self - -- @param #number PatrolLimit The maximum amount of Patrol that can be airborne at the same time for the squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron. - -- - function AI_A2G_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 eatrol group maximum per squadron. - -- @param #AI_A2G_DISPATCHER self - -- @param #number EngageLimit The maximum engages that can be done at the same time per squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron. - -- - function AI_A2G_DISPATCHER:SetDefaultEngageLimit( EngageLimit ) - - self.DefenderDefault.EngageLimit = EngageLimit - - return self - end - - - function AI_A2G_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_A2G_DISPATCHER self - -- @param DetectedItem - -- @return #table A list of the defender friendlies nearby, sorted by distance. - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTasks() - return self.DefenderTasks or {} - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTask( Defender ) - return self.DefenderTasks[Defender] - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskFsm( Defender ) - return self:GetDefenderTask( Defender ).Fsm - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskTarget( Defender ) - return self:GetDefenderTask( Defender ).Target - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskSquadronName( Defender ) - return self:GetDefenderTask( Defender ).SquadronName - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - -- @param Wrapper.Group#GROUP AIGroup - function AI_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER - function AI_A2G_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_A2G_DISPATCHER self - -- @return #table - function AI_A2G_DISPATCHER:GetSquadron( SquadronName ) - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron:" .. SquadronName ) - end - - return DefenderSquadron - end - - --- Get a resource count from a specific squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron Name of the squadron. - -- @return #number Number of airframes available or nil if the squadron does not exist - function AI_A2G_DISPATCHER:QuerySquadron(Squadron) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) - return Squadron.ResourceCount - end - self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - return nil - 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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- A2GDispatcher:SetSquadronVisible( "Mineralnye" ) - -- - -- TODO: disabling because of bug in queueing. --- function AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2GDispatcher:IsSquadronVisible( "Mineralnye" ) - -- - function AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. - -- @usage - -- - -- -- Set the Squadron Takeoff interval every 60 seconds for squadron "SQ50", which is good for a FARP cold start. - -- A2GDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffInterval( SquadronName, TakeoffInterval ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - DefenderSquadron.TakeoffInterval = TakeoffInterval or 0 - DefenderSquadron.TakeoffTime = 0 - end - - end - - - --- Set the squadron patrol parameters for a specific task type. - -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. - -- - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for SEAD tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for CAS tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for BAI tasks. - -- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that each Patrol is a group, and can consist of 1 to 4 aircraft. 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_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum ttime in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_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_A2G_DISPATCHER:SetSquadronBaiPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "BAI" ) - - end - - - --- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2G_DISPATCHER - function AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2G_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_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for SEAD tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for CAS tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for BAI tasks. - -- - -- @param #AI_A2G_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_A2G_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_A2G_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 - - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. - -- @param #AI_A2G_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. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- SEAD Squadron execution. - -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - 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.EngageAltType = EngageAltType - Sead.Defend = true - - self:T( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. - -- @param #AI_A2G_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, 4000, 5000 ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron SEAD engage limit. - -- @param #AI_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will 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 PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @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 EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Sead Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local SeadPatrol = DefenderSquadron.SEAD - SeadPatrol.Name = SquadronName - SeadPatrol.Zone = Zone - SeadPatrol.PatrolFloorAltitude = PatrolFloorAltitude - SeadPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - SeadPatrol.EngageFloorAltitude = EngageFloorAltitude - SeadPatrol.EngageCeilingAltitude = EngageCeilingAltitude - SeadPatrol.PatrolMinSpeed = PatrolMinSpeed - SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed - SeadPatrol.EngageMinSpeed = EngageMinSpeed - SeadPatrol.EngageMaxSpeed = EngageMaxSpeed - SeadPatrol.PatrolAltType = PatrolAltType - SeadPatrol.EngageAltType = EngageAltType - SeadPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) - - self:T( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - 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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.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_A2G_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_A2G_DISPATCHER:SetSquadronSeadPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Set a squadron to engage for close air support, when a defense point is under attack. - -- @param #AI_A2G_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. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- CAS Squadron execution. - -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - 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.EngageAltType = EngageAltType - Cas.Defend = true - - self:T( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for close air support, when a defense point is under attack. - -- @param #AI_A2G_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, 4000, 5000 ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron CAS engage limit. - -- @param #AI_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will 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 PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @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 EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Cas Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local CasPatrol = DefenderSquadron.CAS - CasPatrol.Name = SquadronName - CasPatrol.Zone = Zone - CasPatrol.PatrolFloorAltitude = PatrolFloorAltitude - CasPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - CasPatrol.EngageFloorAltitude = EngageFloorAltitude - CasPatrol.EngageCeilingAltitude = EngageCeilingAltitude - CasPatrol.PatrolMinSpeed = PatrolMinSpeed - CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed - CasPatrol.EngageMinSpeed = EngageMinSpeed - CasPatrol.EngageMaxSpeed = EngageMaxSpeed - CasPatrol.PatrolAltType = PatrolAltType - CasPatrol.EngageAltType = EngageAltType - CasPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) - - self:T( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - 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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.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_A2G_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_A2G_DISPATCHER:SetSquadronCasPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. - -- @param #AI_A2G_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. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- BAI Squadron execution. - -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - 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.EngageAltType = EngageAltType - Bai.Defend = true - - self:T( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. - -- @param #AI_A2G_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, 4000, 5000 ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron BAI engage limit. - -- @param #AI_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will 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 PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @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 EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Bai Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local BaiPatrol = DefenderSquadron.BAI - BaiPatrol.Name = SquadronName - BaiPatrol.Zone = Zone - BaiPatrol.PatrolFloorAltitude = PatrolFloorAltitude - BaiPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - BaiPatrol.EngageFloorAltitude = EngageFloorAltitude - BaiPatrol.EngageCeilingAltitude = EngageCeilingAltitude - BaiPatrol.PatrolMinSpeed = PatrolMinSpeed - BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed - BaiPatrol.EngageMinSpeed = EngageMinSpeed - BaiPatrol.EngageMaxSpeed = EngageMaxSpeed - BaiPatrol.PatrolAltType = PatrolAltType - BaiPatrol.EngageAltType = EngageAltType - BaiPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) - - self:T( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - 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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.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_A2G_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_A2G_DISPATCHER:SetSquadronBaiPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Defines the default amount of extra planes that will takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Overhead The % 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_A2G_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_A2G_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_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultOverhead( Overhead ) - - self.DefenderDefault.Overhead = Overhead - - return self - end - - - --- Defines the amount of extra planes that will takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The % 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_A2G_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_A2G_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_A2G_DISPATCHER - function AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number The % 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_A2G_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_A2G_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_A2G_DISPATCHER - function AI_A2G_DISPATCHER:GetSquadronOverhead( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Overhead or self.DefenderDefault.Overhead - end - - - --- Sets the default grouping of new aircraft spawned. - -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied for the Patrol. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 aircraft. - -- A2GDispatcher:SetDefaultGrouping( 2 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultGrouping( Grouping ) - - self.DefenderDefault.Grouping = Grouping - - return self - end - - - --- Sets the Squadron grouping of new aircraft spawned. - -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied for a Patrol from the Squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set a Squadron specific grouping per 2 aircraft. - -- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Grouping = Grouping - - return self - end - - - --- Sets the engage probability if the squadron will engage on a detected target. - -- This can be configured per squadron, to ensure that each squadron as a specific defensive probability setting. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number EngageProbability The probability when the squadron will consider to engage the detected target. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set an defense probability for squadron SquadronName of 50%. - -- -- This will result that this squadron has 50% chance to engage on a detected target. - -- A2GDispatcher:SetSquadronEngageProbability( "SquadronName", 0.5 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronEngageProbability( SquadronName, EngageProbability ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.EngageProbability = EngageProbability - - return self - end - - - --- Defines the default method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default takeoff from the runway. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default takeoff from the airbase hot. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default takeoff from the airbase cold. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoff( Takeoff ) - - self.DefenderDefault.Takeoff = Takeoff - - return self - end - - --- Defines the method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_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_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights takeoff from the runway. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights takeoff from the airbase hot. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights takeoff from the airbase cold. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_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 takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff() - -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetDefaultTakeoff( ) - - return self.DefenderDefault.Takeoff - end - - --- Gets the method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_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_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetSquadronTakeoff( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff - end - - - --- Sets flights to default takeoff in the air, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- A2GDispatcher:SetDefaultTakeoffInAir() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - - return self - end - - - --- Sets flights to takeoff in the air, as part of the defense system. - -- @param #AI_A2G_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_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Air ) - - if TakeoffAltitude then - self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - end - - return self - end - - - --- Sets flights by default to takeoff from the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff from the runway. - -- A2GDispatcher:SetDefaultTakeoffFromRunway() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights to takeoff from the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from the runway. - -- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights by default to takeoff from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff at a hot parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingHot() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Hot ) - - return self - end - - --- Sets flights to takeoff from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Hot ) - - return self - end - - - --- Sets flights to by default takeoff from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from a cold parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingCold() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Sets flights to takeoff from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from a cold parking spot. - -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. - -- @param #AI_A2G_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_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_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) - - self.DefenderDefault.TakeoffAltitude = TakeoffAltitude - - return self - end - - --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes aircraft start at 2000 meters above ground level (AGL). - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_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_A2G_DISPATCHER self - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_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_A2G_DISPATCHER - function AI_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER - function AI_A2G_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_A2G_DISPATCHER self - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetDefaultLanding() - -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2G_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_A2G_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_A2G_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_A2G_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_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- A2GDispatcher:SetDefaultLandingNearAirbase() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase() - - self:SetDefaultLanding( AI_A2G_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_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- A2GDispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_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_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- A2GDispatcher:SetDefaultLandingAtRunway() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway() - - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.AtRunway ) - - return self - end - - - --- Sets flights to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- A2GDispatcher:SetSquadronLandingAtRunway( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_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_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- A2GDispatcher:SetDefaultLandingAtEngineShutdown() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown() - - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Set the default fuel threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. - -- @param #AI_A2G_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2G_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) - - self.DefenderDefault.FuelThreshold = FuelThreshold - - return self - end - - - --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2G_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_A2G_DISPATCHER self - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- 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_A2G_DISPATCHER:SetDefaultTanker( TankerName ) - - self.DefenderDefault.TankerName = TankerName - - return self - end - - - --- Set the squadron tanker where defenders will Refuel in the air. - -- @param #AI_A2G_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_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- 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_A2G_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TankerName = TankerName - - return self - end - - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_A2G_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.RadioFrequency = RadioFrequency - DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM - DefenderSquadron.RadioPower = RadioPower or 100 - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:Stop() - end - - DefenderSquadron.RadioQueue = nil - - DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) - DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower - DefenderSquadron.RadioQueue:Start( 0.5 ) - - DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER:GetSquadronFromDefender( Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - - -- First, count the active AIGroups Units, targeting 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_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceQueue( Patrol, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F( { DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName } ) - - local DefenseQueueItem = {} -- #AI_A2G_DISPATCHER.DefenderQueueItem - - - DefenseQueueItem.Patrol = Patrol - DefenseQueueItem.DefenderSquadron = DefenderSquadron - DefenseQueueItem.DefendersNeeded = DefendersNeeded - DefenseQueueItem.Defense = Defense - DefenseQueueItem.DefenseTaskType = DefenseTaskType - DefenseQueueItem.AttackerDetection = AttackerDetection - DefenseQueueItem.SquadronName = SquadronName - - table.insert( self.DefenseQueue, DefenseQueueItem ) - self:F( { QueueItems = #self.DefenseQueue } ) - - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceTakeoff() - - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - self:F( { DefenseQueueID } ) - end - - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - - if #self.DefenseQueue > 0 then - - self:F( { SquadronName, Squadron.Name, Squadron.TakeoffTime, Squadron.TakeoffInterval, timer.getTime() } ) - - local DefenseQueueItem = self.DefenseQueue[1] - self:F( {DefenderSquadron=DefenseQueueItem.DefenderSquadron} ) - - if DefenseQueueItem.SquadronName == SquadronName then - - if Squadron.TakeoffTime + Squadron.TakeoffInterval < timer.getTime() then - Squadron.TakeoffTime = timer.getTime() - - if DefenseQueueItem.Patrol == true then - self:ResourcePatrol( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - else - self:ResourceEngage( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - end - table.remove( self.DefenseQueue, 1 ) - end - end - end - - end - - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourcePatrol( DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, AttackerDetection, SquadronName ) - - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Patrol=Patrol}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New2( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.EngageAltType, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.PatrolAltType ) - AI_A2G_Fsm:SetDispatcher( self ) - AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2G_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - AI_A2G_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, nil, DefenderGrouping ) - - function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"Takeoff", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() -- #string - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) - end - AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit - end - end - - function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) - self:F({"PatrolRoute", DefenderGroup:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) - end - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - - self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local FirstUnit = AttackSetUnit:GetFirst() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"RTB", DefenderGroup:GetName()}) - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) - self:F({"LostControl", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." ) - end - if DefenderGroup:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - end - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ResourcePark( Squadron, DefenderGroup ) - end - end - end - - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceEngage( DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Defense=Defense}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G_ENGAGE = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local AI_A2G_Fsm = AI_A2G_ENGAGE[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude, Defense.EngageAltType ) -- AI.AI_AIR_ENGAGE - AI_A2G_Fsm:SetDispatcher( self ) - AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2G_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, AttackerDetection, DefenderGrouping ) - - function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"Defender Birth", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) - - self:F( { DefenderTarget = DefenderTarget } ) - - if DefenderTarget then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) - end - AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - local FirstUnit = AttackSetUnit:GetRandomSurely() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - else - return - end - end - self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local FirstUnit = AttackSetUnit:GetFirst() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"Defender RTB", DefenderGroup:GetName()}) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - end - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) - self:F({"Defender LostControl", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - end - if DefenderGroup:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"Defender Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - end - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ResourcePark( Squadron, DefenderGroup ) - end - end - end - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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_A2G_DISPATCHER self - function AI_A2G_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 EngageSquadronName = nil - - local BreakLoop = false - - while( DefenderCount > 0 and not BreakLoop ) do - - self:F( { DefenderSquadrons = self.DefenderSquadrons } ) - - for SquadronName, DefenderSquadron in UTILS.rpairs( 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 } ) - - -- 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 - local EngageProbability = ( DefenderSquadron.EngageProbability or 1 ) - local Probability = math.random() - if Probability < EngageProbability then - EngageSquadronName = SquadronName - break - end - end - end - end - end - end - - if EngageSquadronName then - - local DefenderSquadron, Defense = self:CanDefend( EngageSquadronName, 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, EngageSquadronName ) - 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_A2G_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_A2G_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 0, 0, 0 - end - - - --- Creates an CAS task. - -- @param #AI_A2G_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_A2G_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 0, 0, 0 - end - - - --- Evaluates an BAI task. - -- @param #AI_A2G_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_A2G_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 0, 0, 0 - end - - - --- Determine the distance as the keys of reference of the detected items. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:Keys( DetectedItem ) - - self:F( { DetectedItem = DetectedItem } ) - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ShortestDistance = 999999999 - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - return ShortestDistance - end - - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:Order( DetectedItem ) - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ShortestDistance = 999999999 - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - return ShortestDistance - end - - - --- Shows the tactical display. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - 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 - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - 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 } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-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 - - 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_A2G_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 - %s", DefenderSquadronName, DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount) or "n/a" ) ) - end - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - - end - - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_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_A2G_DISPATCHER:ProcessDetected( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - 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 - -- TODO: prio 1, what is this index stuff again, simplify it. - if DefenderTask.Target then - self:F( { TargetIndex = DefenderTask.Target.Index } ) - 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 - --- for DefenderGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do --- DefenseTotal = DefenseTotal + 1 --- 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 - for DetectedDistance, DetectedItem in UTILS.kpairs( Detection:GetDetectedItems(), function( t ) return self:Keys( t ) end, function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - 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 } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - 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 EngageDefenses = nil - - self:F( { DetectedDistance = DetectedDistance, DefenseRadius = self.DefenseRadius } ) - if DetectedDistance <= self.DefenseRadius then - - self:F( { DetectedApproach = self._DefenseApproach } ) - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then - EngageDefenses = true - self:F( { EngageDefenses = EngageDefenses } ) - end - - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then - local DistanceProbability = ( self.DefenseRadius / DetectedDistance * self.DefenseReactivity ) - local DefenseProbability = math.random() - - self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } ) - - if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then - EngageDefenses = true - end - end - - - end - - self:F( { EngageDefenses = EngageDefenses, DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - - -- There needs to be an EngageCoordinate. - -- If self.DefenseLimit is set (thus limit the amount of defenses to one zone), then only start a new defense if the maximum has not been reached. - -- If self.DefenseLimit has not been set, there is an unlimited amount of zones to be defended. - if ( EngageDefenses and ( self.DefenseLimit and DefenseTotal < self.DefenseLimit ) or not self.DefenseLimit ) then - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... - if DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD" ) - 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 > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS" ) - 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 > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI" ) - end - end - end - - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - DefenseTotal = DefenseTotal + 1 - end - end - - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem - if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index == DetectedItem.Index then - DefenseTotal = DefenseTotal + 1 - 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 - 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_A2G_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 - %s", DefenderSquadronName, DefenderSquadron.ResourceCount and tostring(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_A2G_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_A2G_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - - local PlayerTypes = {} - local PlayersCount = 0 - - if PlayersNearBy then - local DetectedThreatLevel = 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 DetectedThreatLevel < 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_A2G_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_A2G_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) - - local FriendlyTypes = {} - local FriendliesCount = 0 - - if FriendlyUnitsNearBy then - local DetectedThreatLevel = 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 DetectedThreatLevel < 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_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - function AI_A2G_DISPATCHER:SchedulerPatrol( SquadronName ) - local PatrolTaskTypes = { "SEAD", "CAS", "BAI" } - local PatrolTaskType = PatrolTaskTypes[math.random(1,3)] - self:Patrol( SquadronName, PatrolTaskType ) - end - - --- Set flashing player messages on or off - -- @param #AI_A2G_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function AI_A2G_DISPATCHER:SetSendMessages( onoff ) - self.SetSendPlayerMessages = onoff - end -end - - --- Add resources to a Squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to add. - function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - - --- Remove resources from a Squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to remove. - function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua deleted file mode 100644 index 4ee600fcf..000000000 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ /dev/null @@ -1,133 +0,0 @@ ---- **AI** - Models the process of air to ground SEAD engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_SEAD --- @image AI_Air_To_Ground_Engage.JPG - - - --- @type AI_A2G_SEAD --- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL - - ---- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders. --- --- ![Banner Image](..\Images\deprecated.png) --- --- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event. --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ## 1. AI_A2G_SEAD constructor --- --- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object. --- --- ## 3. Set the Range of Engagement --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2G_SEAD -AI_A2G_SEAD = { - ClassName = "AI_A2G_SEAD", -} - ---- Creates a new AI_A2G_SEAD object --- @param #AI_A2G_SEAD self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_SEAD -function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) - - return self -end - - ---- Creates a new AI_A2G_SEAD object --- @param #AI_A2G_SEAD self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_SEAD -function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -end - - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2G_SEAD self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2G_SEAD self -function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - local HasRadar = AttackUnit:HasSEAD() - if HasRadar then - self:F( { "SEAD Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - end - - return AttackUnitTasks -end - diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua deleted file mode 100644 index 1b0bf8cac..000000000 --- a/Moose Development/Moose/AI/AI_Air.lua +++ /dev/null @@ -1,841 +0,0 @@ ---- **AI** - Models the process of AI air operations. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air --- @image MOOSE.JPG - ---- --- @type AI_AIR --- @extends Core.Fsm#FSM_CONTROLLABLE - ---- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}. --- --- ![Banner Image](..\Images\deprecated.png) --- --- # 1) AI_AIR constructor --- --- * @{#AI_AIR.New}(): Creates a new AI_AIR object. --- --- # 2) AI_AIR is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- --- ## 2.1) AI_AIR States. --- --- * **Idle**: The process is idle. --- --- ## 2.2) AI_AIR Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_AIR -AI_AIR = { - ClassName = "AI_AIR", -} - -AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI. - ---- Creates a new AI_AIR process. --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process. --- @return #AI_AIR -function AI_AIR:New( AIGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR - - self:SetControllable( AIGroup ) - - self:SetStartState( "Stopped" ) - - self:AddTransition( "*", "Queue", "Queued" ) - - self:AddTransition( "*", "Start", "Started" ) - - --- Start Handler OnBefore for AI_AIR - -- @function [parent=#AI_AIR] OnBeforeStart - -- @param #AI_AIR self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Start Handler OnAfter for AI_AIR - -- @function [parent=#AI_AIR] OnAfterStart - -- @param #AI_AIR self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Start Trigger for AI_AIR - -- @function [parent=#AI_AIR] Start - -- @param #AI_AIR self - - --- Start Asynchronous Trigger for AI_AIR - -- @function [parent=#AI_AIR] __Start - -- @param #AI_AIR self - -- @param #number Delay - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_AIR] OnLeaveStopped --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Stopped. --- @function [parent=#AI_AIR] OnEnterStopped --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_AIR] OnBeforeStop --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Stop. --- @function [parent=#AI_AIR] OnAfterStop --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Stop. --- @function [parent=#AI_AIR] Stop --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_AIR] __Stop --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_AIR] OnBeforeStatus --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Status. --- @function [parent=#AI_AIR] OnAfterStatus --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Status. --- @function [parent=#AI_AIR] Status --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_AIR] __Status --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_AIR] OnBeforeRTB --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event RTB. --- @function [parent=#AI_AIR] OnAfterRTB --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event RTB. --- @function [parent=#AI_AIR] RTB --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_AIR] __RTB --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_AIR] OnLeaveReturning --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Returning. --- @function [parent=#AI_AIR] OnEnterReturning --- @param #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Refuel", "Refuelling" ) - - --- Refuel Handler OnBefore for AI_AIR - -- @function [parent=#AI_AIR] OnBeforeRefuel - -- @param #AI_AIR self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Refuel Handler OnAfter for AI_AIR - -- @function [parent=#AI_AIR] OnAfterRefuel - -- @param #AI_AIR self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Refuel Trigger for AI_AIR - -- @function [parent=#AI_AIR] Refuel - -- @param #AI_AIR self - - --- Refuel Asynchronous Trigger for AI_AIR - -- @function [parent=#AI_AIR] __Refuel - -- @param #AI_AIR self - -- @param #number Delay - - self:AddTransition( "*", "Takeoff", "Airborne" ) - self:AddTransition( "*", "Return", "Returning" ) - self:AddTransition( "*", "Hold", "Holding" ) - self:AddTransition( "*", "Home", "Home" ) - self:AddTransition( "*", "LostControl", "LostControl" ) - self:AddTransition( "*", "Fuel", "Fuel" ) - self:AddTransition( "*", "Damaged", "Damaged" ) - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - self.IdleCount = 0 - - self.RTBSpeedMaxFactor = 0.6 - self.RTBSpeedMinFactor = 0.5 - - return self -end - --- @param Wrapper.Group#GROUP self --- @param Core.Event#EVENTDATA EventData -function GROUP:OnEventTakeoff( EventData, Fsm ) - Fsm:Takeoff() - self:UnHandleEvent( EVENTS.Takeoff ) -end - - - -function AI_AIR:SetDispatcher( Dispatcher ) - self.Dispatcher = Dispatcher -end - -function AI_AIR:GetDispatcher() - return self.Dispatcher -end - -function AI_AIR:SetTargetDistance( Coordinate ) - - local CurrentCoord = self.Controllable:GetCoordinate() - self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate ) - - self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance -end - - -function AI_AIR:ClearTargetDistance() - - self.TargetDistance = nil - self.ClosestTargetDistance = nil -end - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_AIR self --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @return #AI_AIR self -function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - ---- Sets (modifies) the minimum and maximum RTB speed of the patrol. --- @param #AI_AIR self --- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @return #AI_AIR self -function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed ) - self:F( { RTBMinSpeed, RTBMaxSpeed } ) - - self.RTBMinSpeed = RTBMinSpeed - self.RTBMaxSpeed = RTBMaxSpeed -end - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_AIR self --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_AIR self -function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - ---- Sets the home airbase. --- @param #AI_AIR self --- @param Wrapper.Airbase#AIRBASE HomeAirbase --- @return #AI_AIR self -function AI_AIR:SetHomeAirbase( HomeAirbase ) - self:F2( { HomeAirbase } ) - - self.HomeAirbase = HomeAirbase -end - ---- Sets to refuel at the given tanker. --- @param #AI_AIR self --- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned. --- @return #AI_AIR self -function AI_AIR:SetTanker( TankerName ) - self:F2( { TankerName } ) - - self.TankerName = TankerName -end - - ---- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB. --- @param #AI_AIR self --- @param #number DisengageRadius The disengage range. --- @return #AI_AIR self -function AI_AIR:SetDisengageRadius( DisengageRadius ) - self:F2( { DisengageRadius } ) - - self.DisengageRadius = DisengageRadius -end - ---- Set the status checking off. --- @param #AI_AIR self --- @return #AI_AIR self -function AI_AIR:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_AIR. --- Once the time is finished, the old AI will return to the base. --- @param #AI_AIR self --- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_AIR self -function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime ) - - self.FuelThresholdPercentage = FuelThresholdPercentage - self.OutOfFuelOrbitTime = OutOfFuelOrbitTime - - self.Controllable:OptionRTBBingoFuel( false ) - - return self -end - ---- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage threshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. --- @param #AI_AIR self --- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_AIR self -function AI_AIR:SetDamageThreshold( PatrolDamageThreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageThreshold = PatrolDamageThreshold - - return self -end - - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR self --- @return #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR:onafterStart( Controllable, From, Event, To ) - - self:__Status( 10 ) -- Check status status every 30 seconds. - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() -end - ---- Coordinates the approriate returning action. --- @param #AI_AIR self --- @return #AI_AIR self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR:onafterReturn( Controllable, From, Event, To ) - - self:__RTB( self.TaskDelay ) - -end - --- @param #AI_AIR self -function AI_AIR:onbeforeStatus() - - return self.CheckStatus -end - --- @param #AI_AIR self -function AI_AIR:onafterStatus() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - - if not self:Is( "Holding" ) and not self:Is( "Returning" ) then - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - - if DistanceFromHomeBase > self.DisengageRadius then - self:T( self.Controllable:GetName() .. " is too far from home base, RTB!" ) - self:Hold( 300 ) - RTB = false - end - end - --- I think this code is not requirement anymore after release 2.5. --- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then --- if DistanceFromHomeBase < 5000 then --- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" ) --- self:Home( "Destroy" ) --- end --- end - - - if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then - - local Fuel = self.Controllable:GetFuelMin() - - -- If the fuel in the controllable is below the threshold percentage, - -- then send for refuel in case of a tanker, otherwise RTB. - if Fuel < self.FuelThresholdPercentage then - - if self.TankerName then - self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) - self:Refuel() - else - self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) - local OldAIControllable = self.Controllable - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - self:Fuel() - RTB = true - end - else - end - end - - if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then - RTB = true - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - local InitialLife = self.Controllable:GetLife0() - - -- If the group is damaged, then RTB. - -- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue. - -- The damaged unit will RTB due to DCS logic, and the others will continue to engage. - if ( Damage / InitialLife ) < self.PatrolDamageThreshold then - self:T( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) - self:Damaged() - RTB = true - self:SetStatusOff() - end - - -- Check if planes went RTB and are out of control. - -- We only check if planes are out of control, when they are in duty. - if self.Controllable:HasTask() == false then - if not self:Is( "Started" ) and - not self:Is( "Stopped" ) and - not self:Is( "Fuel" ) and - not self:Is( "Damaged" ) and - not self:Is( "Home" ) then - if self.IdleCount >= 10 then - if Damage ~= InitialLife then - self:Damaged() - else - self:T( self.Controllable:GetName() .. " control lost! " ) - - self:LostControl() - end - else - self.IdleCount = self.IdleCount + 1 - end - end - else - self.IdleCount = 0 - end - - if RTB == true then - self:__RTB( self.TaskDelay ) - end - - if not self:Is("Home") then - self:__Status( 10 ) - end - - end -end - - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.RTBRoute( AIGroup, Fsm ) - - AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } ) - - if AIGroup:IsAlive() then - Fsm:RTB() - end - -end - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.RTBHold( AIGroup, Fsm ) - - AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( Fsm.TaskDelay ) - Fsm:Return() - local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) - AIGroup:SetTask( Task ) - end - -end - ---- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6. --- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket). --- A random value in this bracket is then applied in the waypoint routing generation. --- @param #AI_AIR self --- @param #number MinFactor Lower bracket factor. Defaults to 0.5. --- @param #number MaxFactor Upper bracket factor. Defaults to 0.6. --- @return #AI_AIR self -function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor) - self.RTBSpeedMaxFactor = MaxFactor or 0.6 - self.RTBSpeedMinFactor = MinFactor or 0.5 - return self -end - - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterRTB( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - if AIGroup and AIGroup:IsAlive() then - - self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) - - self:ClearTargetDistance() - --AIGroup:ClearTasks() - - AIGroup:OptionProhibitAfterburner(true) - - local EngageRoute = {} - - --- Calculate the target route point. - - local FromCoord = AIGroup:GetCoordinate() - if not FromCoord then return end - - local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!) - - local ToTargetVec3 = ToTargetCoord:GetVec3() - ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground - local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 ) - - if not self.RTBMinSpeed or not self.RTBMaxSpeed then - local RTBSpeedMax = AIGroup:GetSpeedMax() - local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6 - local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5 - self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor) - end - - local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) - --local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) ) - - local Distance = FromCoord:Get2DDistance( ToTargetCoord2 ) - - --local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) - local ToAirbaseCoord = ToTargetCoord2 - - if Distance < 5000 then - self:T( "RTB and near the airbase!" ) - self:Home() - return - end - - if not AIGroup:InAir() == true then - self:T( "Not anymore in the air, considered Home." ) - self:Home() - return - end - - - --- Create a route point of type air. - local FromRTBRoutePoint = FromCoord:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - RTBSpeed, - true - ) - - --- Create a route point of type air. - local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - RTBSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = FromRTBRoutePoint - EngageRoute[#EngageRoute+1] = ToRTBRoutePoint - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self ) - - EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks ) - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - --- NOW ROUTE THE GROUP! - AIGroup:Route( EngageRoute, self.TaskDelay ) - - end - -end - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterHome( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - self:T( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - end - -end - - - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime ) - self:F( { AIGroup, From, Event, To } ) - - self:T( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - local Coordinate = AIGroup:GetCoordinate() - if Coordinate == nil then return end - local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed, Coordinate ) - local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) ) - - local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self ) - - local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed ) - - --AIGroup:SetState( AIGroup, "AI_AIR", self ) - - AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 ) - end - -end - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.Resume( AIGroup, Fsm ) - - AIGroup:T( { "AI_AIR.Resume:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( Fsm.TaskDelay ) - end - -end - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - if AIGroup and AIGroup:IsAlive() then - - -- Get tanker group. - local Tanker = GROUP:FindByName( self.TankerName ) - - if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then - - self:T( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName ) - - local RefuelRoute = {} - - --- Calculate the target route point. - - local FromRefuelCoord = AIGroup:GetCoordinate() - local ToRefuelCoord = Tanker:GetCoordinate() - local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - - --- Create a route point of type air. - local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) - - --- Create a route point of type air. NOT used! - local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) - - self:F( { ToRefuelSpeed = ToRefuelSpeed } ) - - RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - -- Get Class name for .Resume function - local classname=self:GetClassName() - - -- AI_A2A_CAP can call this function but does not have a .Resume function. Try to fix. - if classname=="AI_A2A_CAP" then - classname="AI_AIR_PATROL" - end - - env.info("FF refueling classname="..classname) - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskRefueling() - Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self ) - RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks ) - - AIGroup:Route( RefuelRoute, self.TaskDelay ) - - else - - -- No tanker defined ==> RTB! - self:RTB() - - end - - end - -end - - - --- @param #AI_AIR self -function AI_AIR:onafterDead() - self:SetStatusOff() -end - - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - if #self.Controllable:GetUnits() == 1 then - self:__Crash( self.TaskDelay, EventData ) - end - end -end - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( self.TaskDelay, EventData ) - end -end - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( self.TaskDelay, EventData ) - end -end diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua deleted file mode 100644 index ad309f0c2..000000000 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ /dev/null @@ -1,3241 +0,0 @@ ---- **AI** - Create an automated AIR defense system with reconnaissance units, coordinating SEAD, BAI and CAP operations. --- --- === --- --- Features: --- --- * Setup quickly an AIR defense system for a coalition. --- * Setup multiple defense zones to defend specific coordinates in your battlefield. --- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. --- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. --- * Define and use a detection network controlled by recce. --- * Define AIR defense squadrons at airbases, FARPs and carriers. --- * Enable airbases for AIR defenses. --- * Add different planes and helicopter templates to squadrons. --- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, FARPs or carriers. --- * Define different ranges to engage upon. --- * Establish an automatic in air refuel process for planes using refuel tankers. --- * Setup default settings for all squadrons and AIR defenses. --- * Setup specific settings for specific squadrons. --- --- === --- --- ## Missions: --- --- [AI_A2A_Dispatcher](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - AIR GCICAP - Build an automatic AIR Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) --- --- === --- --- # QUICK START GUIDE --- --- ![Banner Image](..\Images\deprecated.png) --- --- The following class is available to model an AIR defense system. --- --- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. --- --- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions. --- --- --- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? --- --- One AI_AIR_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_AIR_DISPATCHER **objects**, --- each governing their defense system for one coalition. --- --- --- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units --- and reporting them to the head quarters. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple recce as one co-operating entity. --- * Communicate with a @{Tasking.CommandCenter}, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- --- ## 3. Which recce units can be used as part of the detection system? Only ground based, or also airborne? --- --- Depending on the type of mission you want to achieve, different types of units can be engaged to perform ground enemy targets reconnaissance. --- Ground recce (FAC) are very useful units to determine the position of enemy ground targets when they spread out over the battlefield at strategic positions. --- Using their varying detection technology, and especially those ground units which have spotting technology, can be extremely effective at --- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. --- Unfortunately, they lack sometimes the visibility to detect targets at greater range, or when scenery is preventing line of sight. --- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then --- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! --- --- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, --- so you need air superiority to make them effective. --- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective --- compared to air units having only visual detection capabilities. --- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. --- --- Typically, don't want these recce units to engage with the enemy, you want to keep them at position. Therefore, it is a good practice --- to set the ROE for these recce to hold weapons, and make them invisible from the enemy. --- --- It is not possible to perform a recce function as a player (unit). --- --- --- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? --- --- The AIR dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. --- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. --- --- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. --- --- The AIR dispatcher provides various parameters to setup the **defensiveness**, --- which models the decision **when** a defender will engage with the approaching enemy. --- Defensiveness is calculated by a probability distribution model when to trigger a defense action, --- depending on the distance of the enemy unit from the defense coordinates, and a **defensiveness factor**. --- --- The other parameter considered for defensive action is **where the enemy is located**, thus the distance from a defense coordinate, --- which we call the **reactive distance**. By default, the reactive distance is set to 60km, but can be changed by the mission designer --- using the available method explained further below. --- The combination of the defensiveness and reactivity results in a model that, the closer the attacker is to the defense point, --- the higher the probability will be that a defense action will be launched! --- --- --- ## 5. Are defense coordinates and defense reactivity the only parameters? --- --- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is --- detected, even when both enemies are at the same distance from a defense coordinate. --- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. --- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. --- --- --- ## 6. Which Squadrons will I create and which name will I give each Squadron? --- --- The AIR defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful --- for your mission, and remember that squadron names are used for communication to the players of your mission. --- --- There are mainly 3 types of defenses: **SEAD**, **CAS** and **BAI**. --- --- Suppression of Air Defenses (SEAD) are effective against radar emitters. Close Air Support (CAS) is launched when the enemy is close near friendly units. --- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. --- --- Depending on the defense type, different payloads will be needed. See further points on squadron definition. --- --- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? --- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. --- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. --- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. --- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! --- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, farp or carrier can act as the launching platform for AIR defenses. --- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, --- or your air units will not return for landing at the airbase! --- --- --- ## 8. Which helicopter or plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple helicopter or plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The AIR defense system will select from the given templates a random template to spawn a new plane (group). --- --- A squadron will perform specific task types (SEAD, CAS or BAI). So, squadrons will require specific templates for the --- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. --- --- --- ## 9. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The AIR defense system will select from the given templates a random template to spawn a new plane (group). --- --- --- ## 10. How to squadrons engage in a defensive action? --- --- There are two ways how squadrons engage and execute your AIR defenses. --- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group --- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. --- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne --- AIR defenses can come immediately into action. --- --- --- ## 11. For each Squadron doing a patrol, which zone types will I create? --- --- Per zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- --- ## 12. Are moving defense coordinates possible? --- --- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. --- --- --- ## 13. How much defense coordinates do I need to create? --- --- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too much defense points, the performance of your mission may decrease. Per defense point defined, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the defense radius. --- The default defense radius is about 60km, and depending on the defense reactivity, defenses will be launched when the enemy is at --- close or greater distance from the defense coordinate. --- --- --- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? --- --- For each patrol: --- --- * **How many** patrol you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new patrol? --- --- other considerations: --- --- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! --- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? --- --- --- ## 15. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! --- --- --- ## 16. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! --- --- --- ## 19. For each Squadron, which **defense overhead** will I use? --- --- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? --- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. --- But the most important factor is the payload, which is the amount of AIR weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 vikrs, that means, that it potentially can destroy at least 8 ground units without a reload of ammunition. --- That means, that one defender can destroy more enemy ground units. --- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, --- one defender for each 4 detected ground enemy units. ** --- --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defenses be grouped when multiple defense air units are spawned for multiple enemy ground units? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- But you can specify a number between 1 and 4, so that the defenders will act as a group. --- --- === --- --- ### 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 - - --- Create an automated AIR defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. - -- - -- === - -- - -- When your mission is in the need to take control of the AI to automate and setup a process of air to ground defenses, this is the module you need. - -- The defense system work through the definition of defense coordinates, which are points in your friendly area within the battle field, that your mission need to have defended. - -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. - -- The AIR dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate - -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. - -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly AIR units. - -- - -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. - -- It also requires a little study to ensure that you get a good understanding of the defense mechanisms, to ensure a strong - -- defense for your missions. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- ## 1. AI\_AIR\_DISPATCHER constructor: - -- - -- - -- The @{#AI_AIR_DISPATCHER.New}() method creates a new AI_AIR_DISPATCHER instance. - -- - -- ### 1.1. Define the **reconnaissance network**: - -- - -- As part of the AI_AIR_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. - -- A reconnaissance network is provided by passing a @{Functional.Detection} object. - -- The most effective reconnaissance for the AIR dispatcher would be to use the @{Functional.Detection#DETECTION_AREAS} object. - -- - -- A reconnaissance network, is used to detect enemy ground targets, - -- potentially group them into areas, and to understand the position, level of threat of the enemy. - -- - -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. - -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. - -- Recce are very useful to acquire the position of enemy ground targets when spread out over the battlefield at strategic positions. - -- Ground units also have varying detectors, and especially the ground units which have laser guiding missiles can be extremely effective at - -- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. - -- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then - -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! - -- - -- Beside ground level units to use for reconnaissance, air units are also very effective. The are capable of patrolling at great speed - -- covering a large terrain. However, airborne recce can be vulnerable to air to ground attacks, and you need air superiority to make then - -- effective. Also the instruments available at the air units play a big role in the effectiveness of the reconnaissance. - -- Air units which have ground detection capabilities will be much more effective than air units with only visual detection capabilities. - -- For the red coalition, the Mi-28N and for the blue side, the reaper are such effective reconnaissance airborne units. - -- - -- Reconnaissance networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection} instance that is given as the first parameter to the AIR dispatcher. - -- By defining in a **smart way the names or name prefixes of the reconnaissance groups**, these groups will be **automatically added or removed** to or from the reconnaissance network, - -- when these groups are spawned in or destroyed during the ongoing battle. - -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously - -- alerted of new enemy ground targets. - -- - -- The following example defense a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. - -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Defene a set of group objects, caled DetectionSetGroup. - -- - -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, - -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- - -- -- This command defines the reconnaissance network. - -- -- It will group any detected ground enemy targets within a radius of 1km. - -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. - -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. - -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. - -- - -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network - -- configuration and setup the AIR defense detection mechanism. - -- - -- ### 1.2. Setup the AIR dispatcher for both a red and blue coalition. - -- - -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate AIR dispatcher. - -- Ensure that while doing so, that you name the objects differently both for red and blue coalition. - -- - -- For example like this for the red coalition: - -- - -- DetectionRed = DETECTION_AREAS:New( DetectionSetGroupRed, 1000 ) - -- AIRDispatcherRed = AI_AIR_DISPATCHER:New( DetectionRed ) - -- - -- And for the blue coalition: - -- - -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) - -- AIRDispatcherBlue = AI_AIR_DISPATCHER:New( DetectionBlue ) - -- - -- - -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! - -- - -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: - -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_AIR_DISPATCHER:New() method, - -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. - -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. - -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius, - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher! - -- So don't make this value too small! Again, I advise about 1km or 1000 meters. - -- - -- ## 2. Setup (a) **Defense Coordinate(s)**. - -- - -- As explained above, defense coordinates are the center of your defense operations. - -- The more threat to the defense coordinate, the higher it is likely a defensive action will be launched. - -- - -- Find below an example how to add defense coordinates: - -- - -- -- Add defense coordinates. - -- AIRDispatcher:AddDefenseCoordinate( "HQ", GROUP:FindByName( "HQ" ):GetCoordinate() ) - -- - -- In this example, the coordinate of a group called `"HQ"` is retrieved, using `:GetCoordinate()` - -- This returns a COORDINATE object, pointing to the first unit within the GROUP object. - -- - -- The method @{#AI_AIR_DISPATCHER.AddDefenseCoordinate}() adds a new defense coordinate to the `AIRDispatcher` object. - -- The first parameter is the key of the defense coordinate, the second the coordinate itself. - -- - -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an AIR dispatcher. - -- - -- **REMEMBER!** - -- - -- - **Defense coordinates are the center of the AIR dispatcher defense system!** - -- - **You can define more defense coordinates to defend a larger area.** - -- - **Detected enemy ground targets are not immediately engaged, but are engaged with a reactivity or probability calculation!** - -- - -- But, there is more to it ... - -- - -- - -- ### 2.1. The **Defense Radius**. - -- - -- The defense radius defines the maximum radius that a defense will be initiated around each defense coordinate. - -- So even when there are targets further away than the defense radius, then these targets won't be engaged upon. - -- By default, the defense radius is set to 100km (100.000 meters), but can be changed using the @{#AI_AIR_DISPATCHER.SetDefenseRadius}() method. - -- Note that the defense radius influences the defense reactivity also! The larger the defense radius, the more reactive the defenses will be. - -- - -- For example: - -- - -- AIRDispatcher:SetDefenseRadius( 30000 ) - -- - -- This defines an AIR dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. - -- Note that the defense radius **applies to all defense coordinates** defined within the AIR dispatcher. - -- - -- ### 2.2. The **Defense Reactivity**. - -- - -- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is - -- also determined by the distance of the enemy ground target to the defense coordinate. - -- If you want to have a **low** defense reactivity, that is, the probability that an AIR defense will engage to the enemy ground target, then - -- use the @{#AI_AIR_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods - -- @{#AI_AIR_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_AIR_DISPATCHER.SetDefenseReactivityHigh}() respectively. - -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, - -- the less reactive the defenses will be in terms of distance to enemy ground targets! - -- - -- For example: - -- - -- AIRDispatcher:SetDefenseReactivityHigh() - -- - -- This defines an AIR dispatcher with high defense reactivity. - -- - -- ## 3. **Squadrons**. - -- - -- The AIR dispatcher works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, farp or carrier, - -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. - -- - -- **Multiple squadrons** can be defined within one AIR dispatcher, each having specific defense tasks and defense parameter settings! - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or **key** of the squadron. - -- * Have specific helicopter or plane **templates**. - -- * Are located at **one** airbase, farp or carrier. - -- * 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 all `AIRDispatcher:SetSquadron...()` or `AIRDispatcher:GetSquadron...()` methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). - -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, - -- the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- The method @{#AI_AIR_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. - -- - -- For example, this defines 3 new squadrons: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50" }, 10 ) - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50" }, 10 ) - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50" }, 10 ) - -- - -- The latter 2 will depart from FARPs, which bare the name `"CAS"` and `"BAI"`. - -- - -- - -- ### 3.1. Squadrons **Tasking**. - -- - -- Squadrons can be commanded to execute 3 types of tasks, as explained above: - -- - -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. - -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the frond-line. - -- - -- You need to configure each squadron which task types you want it to perform. Read on ... - -- - -- ### 3.2. Squadrons enemy ground target **engagement types**. - -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. - -- - -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, - -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required - -- to engage is heavily minimized! - -- - -- However; patrols come with a side effect: since your resources are airborne, they will be vulnerable to incoming air attacks from the enemy. - -- - -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. - -- - -- ### 3.3. Squadron **on call** engagement. - -- - -- So to make squadrons engage targets from the airfields, use the following methods: - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSead}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCas}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBai}() method. - -- - -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. - -- Especially the payload (weapons configuration) is important to get right. - -- - -- For example, the following will define for the squadrons different tasks: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- AIRDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) - -- - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- AIRDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) - -- - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- AIRDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) - -- - -- ### 3.4. Squadron **on patrol engagement**. - -- - -- Squadrons can be setup to patrol in the air near the engagement hot zone. - -- When needed, the AIR defense units will be close to the battle area, and can engage quickly. - -- - -- So to make squadrons engage targets from a patrol zone, use the following methods: - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrol}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrol}() method. - -- - -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrolInterval}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrolInterval}() method. - -- - -- Here an example to setup patrols of various task types: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- AIRDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) - -- - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- AIRDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- AIRDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) - -- - -- - -- ### 3.5. Set squadron take-off methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. - -- - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default landing method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_AIR_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- ### 3.5.2. Set Squadron takeoff interval. - -- - -- The different types of available airfields have different amounts of available launching platforms: - -- - -- - Airbases typically have a lot of platforms. - -- - FARPs have 4 platforms. - -- - Ships have 2 to 4 platforms. - -- - -- Depending on the demand of requested takeoffs by the AIR dispatcher, an airfield can become overloaded. Too many aircraft need to be taken - -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, - -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. - -- - -- For this purpose, the method @{#AI_AIR_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of - -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. - -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time - -- has been reached, a new group will be spawned or activated for takeoff. - -- - -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and - -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. - -- - -- See the underlying example: - -- - -- -- Imagine a squadron launched from a FARP, with a grouping of 4. - -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. - -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. - -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) - -- - -- - -- ### 3.6. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_AIR_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_AIR_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 3.7. Set squadron **grouping**. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. - -- - -- In the case of **on call** engagement, the @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. - -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, - -- an additional on call flight needs to be started. - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the AIR defenses. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_AIR_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. - -- - -- For example, a A-10C with full long-distance AIR missiles payload, may still be less effective than a Su-23 with short range AIR missiles... - -- So in this case, one may want to use the @{#AI_AIR_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: - -- - -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. - -- - -- Typically, for AIR defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: - -- - -- - A-10C: 0.15 - -- - Su-34: 0.15 - -- - A-10A: 0.25 - -- - SU-25T: 0.10 - -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, - -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **engage limit**. - -- - -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. - -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. - -- - -- ## 4. Set the **fuel threshold**. - -- - -- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: - -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. - -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. - -- - -- ## 6. Other configuration options - -- - -- ### 6.1. Set a tactical display panel. - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_AIR_DISPATCHER. - -- Use the method @{#AI_AIR_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Default settings. - -- - -- Default settings configure the standard behaviour of the squadrons. - -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. - -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. - -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. - -- - -- ## 10.1. Default **takeoff** behaviour. - -- - -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing behaviour. - -- - -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_AIR_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default **overhead**. - -- - -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_AIR_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default **grouping**. - -- - -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.7. Default settings for **patrol**. - -- - -- ### 10.7.1. Default **patrol time Interval**. - -- - -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. - -- - -- The default patrol time interval is between **180** and **600** seconds. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. - -- - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ### 10.7.2. Default **patrol limit**. - -- - -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. - -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. - -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. - -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- # Developer Note - -- - -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE - -- Therefore, this class is considered to be deprecated - -- - -- @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. - - -- @field #AI_AIR_DISPATCHER.DefenseCoordinates DefenseCoordinates - AI_AIR_DISPATCHER.DefenseCoordinates = {} - - --- 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 #AI_AIR_DISPATCHER.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 = {} - - --- Defense approach types - -- @type AI_AIR_DISPATCHER.DefenseApproach - AI_AIR_DISPATCHER.DefenseApproach = { - Random = 1, - Distance = 2, - } - - --- AI_AIR_DISPATCHER constructor. - -- This is defining the AIR DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @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 - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - 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 - - self.Detection:FilterCategories( Unit.Category.GROUND_UNIT ) - - -- 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. - - -- TODO: Check detection through radar. --- self.Detection:FilterCategories( { Unit.Category.GROUND } ) --- self.Detection:InitDetectRadar( false ) --- self.Detection:InitDetectVisual( true ) --- self.Detection:SetRefreshTimeInterval( 30 ) - - self:SetDefenseRadius() - self:SetDefenseLimit( nil ) - self:SetDefenseApproach( AI_AIR_DISPATCHER.DefenseApproach.Random ) - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - 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: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 AI.AI_Air#AI_AIR 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:SetDefenseReactivityMedium() - - self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - - self:__Start( 1 ) - - 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 - self:T( "Parked resources for squadron " .. DefenderSquadron.Name ) - end - - end - - --- Locks the DefenseItem from being defended. - -- @param #AI_AIR_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_AIR_DISPATCHER:Lock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Locked = DetectedItem } ) - self.Detection:LockDetectedItem( DetectedItem ) - end - end - - - --- Unlocks the DefenseItem from being defended. - -- @param #AI_AIR_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_AIR_DISPATCHER:Unlock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - self:F( { Index = self.Detection.DetectedItemsByIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Unlocked = DetectedItem } ) - self.Detection:UnlockDetectedItem( DetectedItem ) - end - end - - - --- Sets maximum zones to be engaged at one time by defenders. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DefenseLimit The maximum amount of detected items to be engaged at the same time. - function AI_AIR_DISPATCHER:SetDefenseLimit( DefenseLimit ) - self:F( { DefenseLimit = DefenseLimit } ) - - self.DefenseLimit = DefenseLimit - end - - - --- Sets the method of the tactical approach of the defenses. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DefenseApproach Use the structure AI_AIR_DISPATCHER.DefenseApproach to set the defense approach. - -- The default defense approach is AI_AIR_DISPATCHER.DefenseApproach.Random. - function AI_AIR_DISPATCHER:SetDefenseApproach( DefenseApproach ) - self:F( { DefenseApproach = DefenseApproach } ) - - self._DefenseApproach = DefenseApproach - 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:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize 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:T( "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 ) - 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 ) - end - end - end - - do -- Manage the defensive behaviour - - -- @param #AI_AIR_DISPATCHER self - -- @param #string DefenseCoordinateName The name of the coordinate to be defended by AIR defenses. - -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by AIR defenses. - function AI_AIR_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) - self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityLow() - self.DefenseReactivity = 0.05 - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityMedium() - self.DefenseReactivity = 0.15 - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityHigh() - self.DefenseReactivity = 0.5 - end - - end - - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- AIRDispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- AIRDispatcher:SetDisngageRadius() -- 300000 is the default value. - -- - function AI_AIR_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, CAS or BAI for defense. - -- When targets are detected that are still really far off, you don't want the AI_AIR_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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to defend from detected targets from the nearest airbase. - -- AIRDispatcher:SetDefendRadius( 100000 ) - -- - -- -- Set 200km as the radius to defend. - -- AIRDispatcher: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 @{Core.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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Set one ZONE_POLYGON object as the border for the AIR dispatcher. - -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- AIRDispatcher:SetBorderZone( BorderZone ) - -- - -- or - -- - -- -- Set two ZONE_POLYGON objects as the border for the AIR 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. - -- AIRDispatcher: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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- AIRDispatcher:SetTacticalDisplay( true ) - -- - function AI_AIR_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold 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 % of the damage threshold before going RTB. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- AIRDispatcher: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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol time interval. - -- AIRDispatcher: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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- AIRDispatcher: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 eatrol 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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- AIRDispatcher: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\_AIR\_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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = 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... - -- AIRDispatcher: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 AIR 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. - -- AIRDispatcher: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... - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- AIRDispatcher: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. - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - local Squadron = AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - return self:SetSquadron2( Squadron ) - end - - --- This is the new method to define Squadrons programmatically. - -- - -- Define a squadron using the AI_AIR_SQUADRON class. - -- - -- 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\_AIR\_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 AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron The Air Squadron to be set active for the Air Dispatcher. - -- - -- @usage - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = 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... - -- AIRDispatcher: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 AIR 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. - -- AIRDispatcher: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... - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- AIRDispatcher: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. - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadron2( Squadron ) - - local SquadronName = Squadron:GetName() -- Retrieves the Squadron Name. - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - return self - end - - --- Get the @{AI.AI_Air_Squadron} object from the Squadron Name given. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The Squadron Name to search the Squadron object. - -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron object. - function AI_AIR_DISPATCHER:GetSquadron( SquadronName ) - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron for Dispatcher:" .. 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. - -- AIRDispatcher: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 #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = AIRDispatcher: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. - -- AIRDispatcher: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" ) ) - -- AIRDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- AIRDispatcher: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 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" ) ) - -- AIRDispatcher: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 - - - - - - --- 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 % 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 AIR 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 AIRDispatcher = 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. - -- - -- AIRDispatcher: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 % 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 AIR 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 AIRDispatcher = 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. - -- - -- AIRDispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetOverhead( 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 % 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 AIR 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 AIRDispatcher = 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 = AIRDispatcher:GetSquadronOverhead( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:GetSquadronOverhead( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetOverhead() 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 airplanes. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set a grouping per 2 airplanes. - -- AIRDispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetGrouping( Grouping ) - - return self - end - - - --- Sets the engage probability if the squadron will engage on a detected target. - -- This can be configured per squadron, to ensure that each squadron as a specific defensive probability setting. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number EngageProbability The probability when the squadron will consider to engage the detected target. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set an defense probability for squadron SquadronName of 50%. - -- -- This will result that this squadron has 50% chance to engage on a detected target. - -- AIRDispatcher:SetSquadronEngageProbability( "SquadronName", 0.5 ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronEngageProbability( SquadronName, EngageProbability ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetEngageProbability( EngageProbability ) - - 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default take-off from the runway. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default take-off from the airbase hot. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default take-off from the airbase cold. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights take-off from the runway. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights take-off from the airbase hot. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights take-off from the airbase cold. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetTakeoff( 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- local TakeoffMethod = AIRDispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_AIR_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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- local TakeoffMethod = AIRDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_AIR_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetSquadronTakeoff( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetTakeoff() 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off from the runway. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from the runway. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off at a hot parking spot. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights by default despawn after landing land at the runway. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights despawn after landing land at the runway. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtEngineShutdown ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetLanding( 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = AIRDispatcher:GetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_AIR_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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = AIRDispatcher:GetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_AIR_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetSquadronLanding( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetLanding() 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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- AIRDispatcher: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 AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- AIRDispatcher: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 threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold 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 % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher: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 threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold 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 % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher: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:SetFuelThreshold( 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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the default tanker. - -- AIRDispatcher: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 AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the squadron tanker. - -- AIRDispatcher: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:SetTankerName( TankerName ) - - return self - end - - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_AIR_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower, Language ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetRadio( RadioFrequency, RadioModulation, RadioPower, DefenderSquadron.Language ) - end - - - -- TODO: Need to model the resources in a squadron. - - -- @param #AI_AIR_DISPATCHER self - -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron - function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self.Defenders[ DefenderName ] = Squadron - local SquadronResourceCount = Squadron:GetResourceCount() - if SquadronResourceCount then - Squadron:RemoveResources( Size ) - end - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - -- @param #AI_AIR_DISPATCHER self - -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron - function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - local SquadronResourceCount = Squadron:GetResourceCount() - if SquadronResourceCount then - Squadron:AddResources( Defender:GetSize() ) - end - self.Defenders[ DefenderName ] = nil - self:F( { DefenderName = DefenderName, SquadronResourceCount = SquadronResourceCount } ) - end - - -- @param #AI_AIR_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender - -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron. - 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, targeting 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: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 - - - - - --- Shows the tactical display. - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - 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 - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - 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 } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelAIR() - Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-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 - - 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 - -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:CalculateThreatLevelAIR() - 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:CalculateThreatLevelAIR() - 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 - - -end - diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua deleted file mode 100644 index 772f10b2e..000000000 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ /dev/null @@ -1,595 +0,0 @@ ---- **AI** - Models the process of air to ground engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Engage --- @image AI_Air_To_Ground_Engage.JPG - - - ---- @type AI_AIR_ENGAGE --- @extends AI.AI_AIR#AI_AIR - - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- ![Banner Image](..\Images\deprecated.png) --- --- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event. --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ## 1. AI_AIR_ENGAGE constructor --- --- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object. --- --- ## 2. Set the Zone of Engagement --- --- An optional @{Core.Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI.AI_CAP#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone. - -- - -- # Developer Note - -- - -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE - -- Therefore, this class is considered to be deprecated - -- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_AIR_ENGAGE -AI_AIR_ENGAGE = { - ClassName = "AI_AIR_ENGAGE", -} - - - ---- Creates a new AI_AIR_ENGAGE object --- @param #AI_AIR_ENGAGE self --- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM. --- @param Wrapper.Group#GROUP AIGroup The AI group. --- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @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. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_AIR_ENGAGE -function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE - - self.Accomplished = false - self.Engaging = false - - local SpeedMax = AIGroup:GetSpeedMax() - - self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5 - self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75 - self.EngageFloorAltitude = EngageFloorAltitude or 1000 - self.EngageCeilingAltitude = EngageCeilingAltitude or 1500 - self.EngageAltType = EngageAltType or "RADIO" - - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] EngageRoute - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] __EngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] Engage - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] __Engage - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterFired - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] Fired - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] __Fired - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] Destroy - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] __Destroy - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] Abort - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] __Abort - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] Accomplish - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] __Accomplish - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) - - return self -end - ---- onafter event handler for Start event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To ) - - self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To ) - - AIGroup:HandleEvent( EVENTS.Takeoff, nil, self ) - -end - - - ---- onafter event handler for Engage event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To ) - -- TODO: This function is overwritten below! - self:HandleEvent( EVENTS.Dead ) -end - --- todo: need to fix this global function - - ---- onbefore event handler for Engage event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To ) - if self.Accomplished == true then - return false - end - return true -end - ---- onafter event handler for Abort event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To ) - AIGroup:ClearTasks() - self:Return() -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To ) - self.Accomplished = true - --self:SetDetectionOff() -end - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData ) - - if EventData.IniUnit then - self.AttackUnits[EventData.IniUnit] = nil - end -end - --- @param #AI_AIR_ENGAGE self --- @param Core.Event#EVENTDATA EventData -function AI_AIR_ENGAGE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then - self:__Destroy( self.TaskDelay, EventData ) - end - end -end - - --- @param Wrapper.Group#GROUP AIControllable -function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit ) - Fsm:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName()))) - - if AIGroup and AIGroup:IsAlive() then - Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit ) - end -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked. -function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:T( { DefenderGroup, From, Event, To, AttackSetUnit } ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:CountAlive() - - if AttackCount > 0 then - - if DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - -- Determine the distance to the target. - -- If it is less than 10km, then attack without a route. - -- Otherwise perform a route attack. - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3() - - if TargetCoord == nil then - self:Return() - return - end - - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - -- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes! - if TargetDistance <= EngageDistance * 9 then - - --self:T(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000)) - self:__Engage( 0.1, AttackSetUnit ) - - else - - --self:T(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000)) - - local EngageRoute = {} - local AttackTasks = {} - - --- Calculate the target route point. - - local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) - - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - - local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) - - EngageRoute[#EngageRoute+1] = ToWP - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:OptionROEReturnFire() - DefenderGroup:OptionROTEvadeFire() - - DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 ) - end - - end - else - -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! - self:T( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - end -end - - --- @param Wrapper.Group#GROUP AIControllable -function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit ) - - Fsm:T(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName()))) - - if AIGroup and AIGroup:IsAlive() then - local delay=Fsm.TaskDelay or 0.1 - Fsm:__Engage(delay, AttackSetUnit) - end -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked. -function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:CountAlive() - self:T({AttackCount = AttackCount}) - - if AttackCount > 0 then - - if DefenderGroup and DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3() - if not TargetCoord then - self:Return() - return - end - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - local EngageRoute = {} - local AttackTasks = {} - - local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - - local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) - EngageRoute[#EngageRoute+1] = ToWP - - -- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task! - if TargetDistance <= EngageDistance * 9 then - - local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic - - if #AttackUnitTasks == 0 then - self:T( DefenderGroupName .. ": No valid targets found -> Going RTB") - self:Return() - return - else - local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance)) - self:T(text) - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTEvadeFire() - DefenderGroup:OptionKeepWeaponsOnThreat() - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) - end - end - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 ) - - end - else - -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! - self:T( DefenderGroupName .. ": No targets found -> returning.") - self:Return() - return - end -end - --- @param Wrapper.Group#GROUP AIEngage -function AI_AIR_ENGAGE.Resume( AIEngage, Fsm ) - - AIEngage:F( { "Resume:", AIEngage:GetName() } ) - if AIEngage and AIEngage:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay or 0.1 ) - Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit ) - end - -end diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua deleted file mode 100644 index b389d10ff..000000000 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ /dev/null @@ -1,393 +0,0 @@ ---- **AI** - Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Patrol --- @image AI_Air_To_Ground_Patrol.JPG - --- @type AI_AIR_PATROL --- @extends AI.AI_Air#AI_AIR - ---- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1. AI_AIR_PATROL constructor --- --- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object. --- --- ## 2. AI_AIR_PATROL is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_AIR_PATROL States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2 AI_AIR_PATROL Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_AIR_PATROL -AI_AIR_PATROL = { - ClassName = "AI_AIR_PATROL", -} - ---- Creates a new AI_AIR_PATROL object --- @param #AI_AIR_PATROL self --- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM. --- @param Wrapper.Group#GROUP AIGroup The AI group. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO. --- @return #AI_AIR_PATROL -function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL - - local SpeedMax = AIGroup:GetSpeedMax() - - self.PatrolZone = PatrolZone - - self.PatrolFloorAltitude = PatrolFloorAltitude or 1000 - self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500 - self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5 - self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75 - - -- defafult PatrolAltType to "RADIO" if not specified - self.PatrolAltType = PatrolAltType or "RADIO" - - self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" ) - - --- OnBefore Transition Handler for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] OnBeforePatrol - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] OnAfterPatrol - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] Patrol - -- @param #AI_AIR_PATROL self - - --- Asynchronous Event Trigger for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] __Patrol - -- @param #AI_AIR_PATROL self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Patrolling. - -- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Patrolling. - -- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. - - --- OnBefore Transition Handler for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] PatrolRoute - -- @param #AI_AIR_PATROL self - - --- Asynchronous Event Trigger for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] __PatrolRoute - -- @param #AI_AIR_PATROL self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. - - return self -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_AIR_PATROL self --- @param #number EngageRange The Engage Range. --- @return #AI_AIR_PATROL self -function AI_AIR_PATROL:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone. --- @param #AI_AIR_PATROL self --- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m. --- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. --- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. --- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. --- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. --- @return #AI_AIR_PATROL self -function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) - - self.racetrack=true - self.racetracklegmin=LegMin or 10000 - self.racetracklegmax=LegMax or 15000 - self.racetrackheadingmin=HeadingMin or 0 - self.racetrackheadingmax=HeadingMax or 180 - self.racetrackdurationmin=DurationMin - self.racetrackdurationmax=DurationMax - - if self.racetrackdurationmax and not self.racetrackdurationmin then - self.racetrackdurationmin=self.racetrackdurationmax - end - - self.racetrackcapcoordinates=CapCoordinates - -end - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR_PATROL self --- @return #AI_AIR_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To ) - self:F2() - - self:ClearTargetDistance() - - self:__PatrolRoute( self.TaskDelay ) - - AIPatrol:OnReSpawn( - function( PatrolGroup ) - self:__Reset( self.TaskDelay ) - self:__PatrolRoute( self.TaskDelay ) - end - ) -end - ---- This static method is called from the route path within the last task at the last waypoint of the AIPatrol. --- Note that this method is required, as triggers the next route when patrolling for the AIPatrol. --- @param Wrapper.Group#GROUP AIPatrol The AI group. --- @param #AI_AIR_PATROL Fsm The FSM. -function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } ) - - if AIPatrol and AIPatrol:IsAlive() then - Fsm:PatrolRoute() - end - -end - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - if AIPatrol and AIPatrol:IsAlive() then - - local PatrolRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIPatrol:GetCoordinate() - - local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - - local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() - ToTargetCoord:SetAlt( altitude ) - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - local speedkmh=ToTargetSpeed - - local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) - PatrolRoute[#PatrolRoute+1] = FromWP - - if self.racetrack then - - -- Random heading. - local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) - - -- Random leg length. - local leg=math.random(self.racetracklegmin, self.racetracklegmax) - - -- Random duration if any. - local duration = self.racetrackdurationmin - if self.racetrackdurationmax then - duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) - end - - -- CAP coordinate. - local c0=self.PatrolZone:GetRandomCoordinate() - if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then - c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] - end - - -- Race track points. - local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE - local c2=c1:Translate(leg, heading):SetAltitude(altitude) - - self:SetTargetDistance(c0) -- For RTB status check - - -- Debug: - self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) - --c1:MarkToAll("Race track c1") - --c2:MarkToAll("Race track c2") - - -- Task to orbit. - local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) - - -- Task function to redo the patrol at other random position. - local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) - - -- Controlled task with task condition. - local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) - local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) - - -- Second waypoint - PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") - - else - - --- Create a route point of type air. - local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) - PatrolRoute[#PatrolRoute+1] = ToWP - - local Tasks = {} - Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) - PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) - - end - - AIPatrol:OptionROEReturnFire() - AIPatrol:OptionROTEvadeFire() - - AIPatrol:Route( PatrolRoute, self.TaskDelay ) - - end - -end - ---- Resumes the AIPatrol --- @param Wrapper.Group#GROUP AIPatrol --- @param Core.Fsm#FSM Fsm -function AI_AIR_PATROL.Resume( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } ) - if AIPatrol and AIPatrol:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay ) - Fsm:__PatrolRoute( Fsm.TaskDelay ) - end - -end diff --git a/Moose Development/Moose/AI/AI_Air_Squadron.lua b/Moose Development/Moose/AI/AI_Air_Squadron.lua deleted file mode 100644 index 7356c1baa..000000000 --- a/Moose Development/Moose/AI/AI_Air_Squadron.lua +++ /dev/null @@ -1,296 +0,0 @@ ---- **AI** - Models squadrons for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_Air_Dispatcher} and derived dispatcher classes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Squadron --- @image MOOSE.JPG - - - ---- @type AI_AIR_SQUADRON --- @extends Core.Base#BASE - - ---- Implements the core functions modeling squadrons for airplanes and helicopters. --- --- # Developer Note --- --- ![Banner Image](..\Images\deprecated.png) --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_AIR_SQUADRON -AI_AIR_SQUADRON = { - ClassName = "AI_AIR_SQUADRON", -} - - - ---- Creates a new AI_AIR_SQUADRON object --- @param #AI_AIR_SQUADRON self --- @return #AI_AIR_SQUADRON -function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - self:T( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - - local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON - - AI_Air_Squadron.Name = SquadronName - AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName ) - AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName() - if not AI_Air_Squadron.Airbase then - error( "Cannot find airbase with name:" .. AirbaseName ) - end - - AI_Air_Squadron.Spawn = {} - if type( TemplatePrefixes ) == "string" then - local SpawnTemplate = TemplatePrefixes - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - AI_Air_Squadron.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 ) - AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] - end - end - AI_Air_Squadron.ResourceCount = ResourceCount - AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes - AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. - - self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. - - return AI_Air_Squadron -end - ---- Set the Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #string Name The Squadron Name. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetName( Name ) - - self.Name = Name - - return self -end - ---- Get the Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #string The Squadron Name. -function AI_AIR_SQUADRON:GetName() - - return self.Name -end - ---- Set the ResourceCount of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number ResourceCount The Squadron ResourceCount. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetResourceCount( ResourceCount ) - - self.ResourceCount = ResourceCount - - return self -end - ---- Get the ResourceCount of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron ResourceCount. -function AI_AIR_SQUADRON:GetResourceCount() - - return self.ResourceCount -end - ---- Add Resources to the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Resources The Resources to be added. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:AddResources( Resources ) - - self.ResourceCount = self.ResourceCount + Resources - - return self -end - ---- Remove Resources to the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Resources The Resources to be removed. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:RemoveResources( Resources ) - - self.ResourceCount = self.ResourceCount - Resources - - return self -end - ---- Set the Overhead of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Overhead The Squadron Overhead. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetOverhead( Overhead ) - - self.Overhead = Overhead - - return self -end - ---- Get the Overhead of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Overhead. -function AI_AIR_SQUADRON:GetOverhead() - - return self.Overhead -end - ---- Set the Grouping of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Grouping The Squadron Grouping. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetGrouping( Grouping ) - - self.Grouping = Grouping - - return self -end - ---- Get the Grouping of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Grouping. -function AI_AIR_SQUADRON:GetGrouping() - - return self.Grouping -end - ---- Set the FuelThreshold of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number FuelThreshold The Squadron FuelThreshold. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold ) - - self.FuelThreshold = FuelThreshold - - return self -end - ---- Get the FuelThreshold of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron FuelThreshold. -function AI_AIR_SQUADRON:GetFuelThreshold() - - return self.FuelThreshold -end - ---- Set the EngageProbability of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number EngageProbability The Squadron EngageProbability. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability ) - - self.EngageProbability = EngageProbability - - return self -end - ---- Get the EngageProbability of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron EngageProbability. -function AI_AIR_SQUADRON:GetEngageProbability() - - return self.EngageProbability -end - ---- Set the Takeoff of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Takeoff The Squadron Takeoff. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetTakeoff( Takeoff ) - - self.Takeoff = Takeoff - - return self -end - ---- Get the Takeoff of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Takeoff. -function AI_AIR_SQUADRON:GetTakeoff() - - return self.Takeoff -end - ---- Set the Landing of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Landing The Squadron Landing. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetLanding( Landing ) - - self.Landing = Landing - - return self -end - ---- Get the Landing of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Landing. -function AI_AIR_SQUADRON:GetLanding() - - return self.Landing -end - ---- Set the TankerName of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #string TankerName The Squadron Tanker Name. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetTankerName( TankerName ) - - self.TankerName = TankerName - - return self -end - ---- Get the Tanker Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #string The Squadron Tanker Name. -function AI_AIR_SQUADRON:GetTankerName() - - return self.TankerName -end - - ---- Set the Radio of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number RadioFrequency The frequency of communication. --- @param #number RadioModulation The modulation of communication. --- @param #number RadioPower The power in Watts of communication. --- @param #string Language The language of the radio speech. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language ) - - self.RadioFrequency = RadioFrequency - self.RadioModulation = RadioModulation or radio.modulation.AM - self.RadioPower = RadioPower or 100 - - if self.RadioSpeech then - self.RadioSpeech:Stop() - end - - self.RadioSpeech = nil - - self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation ) - self.RadioSpeech.power = RadioPower - self.RadioSpeech:Start( 0.5 ) - - self.RadioSpeech:SetLanguage( Language ) - - return self -end - - diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua deleted file mode 100644 index 46c5c086a..000000000 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ /dev/null @@ -1,653 +0,0 @@ ---- **AI** - Peform Battlefield Area Interdiction (BAI) within an engagement zone. --- --- **Features:** --- --- * Hold and standby within a patrol zone. --- * Engage upon command the assigned targets within an engagement zone. --- * Loop the zone until all targets are eliminated. --- * Trigger different events upon the results achieved. --- * After combat, return to the patrol zone and hold. --- * RTB when commanded or after out of fuel. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_BAI) --- --- === --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Gunterlund**: Test case revision. --- --- === --- --- @module AI.AI_BAI --- @image AI_Battlefield_Air_Interdiction.JPG - - ---- AI_BAI_ZONE class --- @type AI_BAI_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE - ---- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. --- --- ![Banner Image](..\Images\deprecated.png) --- --- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG) --- --- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_BAI\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, --- using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG) --- --- When the AI is commanded to provide BattleGround Air Interdiction (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_BAI\Dia10.JPG) --- --- When the AI has accomplished the Bombing, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG) --- --- # 1. AI_BAI_ZONE constructor --- --- * @{#AI_BAI_ZONE.New}(): Creates a new AI_BAI_ZONE object. --- --- ## 2. AI_BAI_ZONE is a FSM --- --- ![Process](..\Presentations\AI_BAI\Dia2.JPG) --- --- ### 2.1. AI_BAI_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing BOMB. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2. AI_BAI_ZONE Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_BAI_ZONE.Engage}**: Engage the AI to provide BOMB in the Engage Zone, destroying any target it finds. --- * **@{#AI_BAI_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. --- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task. --- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** --- --- Use the method @{#AI_BAI_ZONE.SearchOff}() to specify that the EngageZone is not to be searched for potential targets (UNITs), but that the center of the zone --- is the point where a map object is to be destroyed (like a bridge). --- --- Example: --- --- -- Tell the BAI not to search for potential targets in the BAIEngagementZone, but rather use the center of the BAIEngagementZone as the bombing location. --- AIBAIZone:SearchOff() --- --- Searching can be switched back on with the method @{#AI_BAI_ZONE.SearchOn}(). Use the method @{#AI_BAI_ZONE.SearchOnOff}() to flexibily switch searching on or off. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_BAI_ZONE -AI_BAI_ZONE = { - ClassName = "AI_BAI_ZONE", -} - - - ---- Creates a new AI_BAI_ZONE object --- @param #AI_BAI_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_BAI_ZONE self -function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_BAI_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - self:SearchOn() - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_BAI_ZONE] OnBeforeEngage - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_BAI_ZONE] OnAfterEngage - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_BAI_ZONE] Engage - -- @param #AI_BAI_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_BAI_ZONE] __Engage - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_BAI_ZONE] OnEnterEngaging --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_BAI_ZONE] OnBeforeFired - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_BAI_ZONE] OnAfterFired - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_BAI_ZONE] Fired - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_BAI_ZONE] __Fired - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] OnBeforeDestroy - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] OnAfterDestroy - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] Destroy - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] __Destroy - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_BAI_ZONE] OnBeforeAbort - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_BAI_ZONE] OnAfterAbort - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_BAI_ZONE] Abort - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_BAI_ZONE] __Abort - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] OnBeforeAccomplish - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] OnAfterAccomplish - -- @param #AI_BAI_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] Accomplish - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] __Accomplish - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing BOMB. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_BAI_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing BOMB. --- @return #AI_BAI_ZONE self -function AI_BAI_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - ---- Specifies whether to search for potential targets in the zone, or let the center of the zone be the bombing coordinate. --- AI_BAI_ZONE will search for potential targets by default. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOnOff( Search ) - - self.Search = Search - - return self -end - ---- If Search is Off, the current zone coordinate will be the center of the bombing. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOff() - - self:SearchOnOff( false ) - - return self -end - - ---- If Search is On, BAI will search for potential targets in the zone. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOn() - - self:SearchOnOff( true ) - - return self -end - - ---- onafter State Transition for Event Start. --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_BAI_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - --- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_BAI#AI_BAI_ZONE - EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection ) -end - - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_BAI_ZONE:onafterTarget( Controllable, From, Event, To ) - self:F({"onafterTarget",self.Search,Controllable:IsAlive()}) - - - - if Controllable:IsAlive() then - - local AttackTasks = {} - - if self.Search == true then - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:F( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - else - self:F("Attack zone") - local AttackTask = Controllable:TaskAttackMapObject( - self.EngageZone:GetPointVec2():GetVec2(), - true, - self.EngageWeaponExpend, - self.EngageAttackQty, - self.EngageDirection, - self.EngageAltitude - ) - self.Controllable:PushTask( AttackTask, 1 ) - end - - self:__Target( -10 ) - - end -end - - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_BAI_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - local AttackTasks = {} - - if self.Search == true then - - for DetectedUnitID, DetectedUnitData in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskBombing( - DetectedUnit:GetPointVec2():GetVec2(), - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection, - EngageAltitude - ) - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - else - self:F("Attack zone") - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackMapObject( - self.EngageZone:GetPointVec2():GetVec2(), - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection, - EngageAltitude - ) - end - - EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks ) - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Controllable:WayPointInitialize( EngageRoute ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - Controllable:SetState( Controllable, "EngageZone", self ) - - Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" ) - - --- NOW ROUTE THE GROUP! - Controllable:WayPointExecute( 1 ) - - self:SetRefreshTimeInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start targeting - end -end - - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - --- @param #AI_BAI_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - --- @param #AI_BAI_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_BAI_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end - - diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua deleted file mode 100644 index f11a12c18..000000000 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ /dev/null @@ -1,308 +0,0 @@ ---- **AI** - Balance player slots with AI to create an engaging simulation environment, independent of the amount of players. --- --- **Features:** --- --- * Automatically spawn AI as a replacement of free player slots for a coalition. --- * Make the AI to perform tasks. --- * Define a maximum amount of AI to be active at the same time. --- * Configure the behaviour of AI when a human joins a slot for which an AI is active. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Balancer) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- --- === --- --- @module AI.AI_Balancer --- @image AI_Balancing.JPG - --- @type AI_BALANCER --- @field Core.Set#SET_CLIENT SetClient --- @field Core.Spawn#SPAWN SpawnAI --- @field Wrapper.Group#GROUP Test --- @extends Core.Fsm#FSM_SET - ---- ![Banner Image](..\Images\deprecated.png) --- --- Monitors and manages as many replacement AI groups as there are --- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players. --- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions. --- --- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM). --- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods. --- An explanation about state and event transition methods can be found in the @{Core.Fsm} module documentation. --- --- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following: --- --- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned. --- --- ## 1. AI_BALANCER construction --- --- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method: --- --- ## 2. AI_BALANCER is a FSM --- --- ![Process](..\Presentations\AI_BALANCER\Dia13.JPG) --- --- ### 2.1. AI_BALANCER States --- --- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients. --- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroying** ( Set, AIGroup ): The AI is being destroyed. --- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any. --- --- ### 2.2. AI_BALANCER Events --- --- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set. --- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroy** ( Set, AIGroup ): The AI is being destroyed. --- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. --- --- ## 3. AI_BALANCER spawn interval for replacement AI --- --- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned. --- --- ## 4. AI_BALANCER returns AI to Airbases --- --- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default. --- However, there are 2 additional options that you can use to customize the destroy behaviour. --- When a human player joins a slot, you can configure to let the AI return to: --- --- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}. --- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}. --- --- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return, --- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_BALANCER -AI_BALANCER = { - ClassName = "AI_BALANCER", - PatrolZones = {}, - AIGroups = {}, - Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds. - Latest = 60, -- Latest a new AI can be spawned is in 60 seconds. -} - - - ---- Creates a new AI_BALANCER object --- @param #AI_BALANCER self --- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. --- @return #AI_BALANCER -function AI_BALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER - - -- TODO: Define the OnAfterSpawned event - self:SetStartState( "None" ) - self:AddTransition( "*", "Monitor", "Monitoring" ) - self:AddTransition( "*", "Spawn", "Spawning" ) - self:AddTransition( "Spawning", "Spawned", "Spawned" ) - self:AddTransition( "*", "Destroy", "Destroying" ) - self:AddTransition( "*", "Return", "Returning" ) - - self.SetClient = SetClient - self.SetClient:FilterOnce() - self.SpawnAI = SpawnAI - - self.SpawnQueue = {} - - self.ToNearestAirbase = false - self.ToHomeAirbase = false - - self:__Monitor( 1 ) - - return self -end - ---- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI. --- Provide 2 identical seconds if the interval should be a fixed amount of seconds. --- @param #AI_BALANCER self --- @param #number Earliest The earliest a new AI can be spawned in seconds. --- @param #number Latest The latest a new AI can be spawned in seconds. --- @return self -function AI_BALANCER:InitSpawnInterval( Earliest, Latest ) - - self.Earliest = Earliest - self.Latest = Latest - - return self -end - ---- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. --- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. -function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnThresholdRange = ReturnThresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. -function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange ) - - self.ToHomeAirbase = true - self.ReturnThresholdRange = ReturnThresholdRange -end - ---- AI_BALANCER:onenterSpawning --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string ClientName --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName ) - - -- OK, Spawn a new group from the default SpawnAI object provided. - local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP - if AIGroup then - AIGroup:T( { "Spawning new AIGroup", ClientName = ClientName } ) - --TODO: need to rework UnitName thing ... - - SetGroup:Remove( ClientName ) -- Ensure that the previously allocated AIGroup to ClientName is removed in the Set. - SetGroup:Add( ClientName, AIGroup ) - self.SpawnQueue[ClientName] = nil - - -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. - -- Mission designers can catch this event to bind further actions to the AIGroup. - self:Spawned( AIGroup ) - end -end - ---- AI_BALANCER:onenterDestroying --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup ) - - AIGroup:Destroy() - SetGroup:Flush( self ) - SetGroup:Remove( ClientName ) - SetGroup:Flush( self ) -end - ---- RTB --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string From --- @param #string Event --- @param #string To --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) - - local AIGroupTemplate = AIGroup:GetTemplate() - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - local PointVec2 = COORDINATE:New(AIGroup:GetVec2().x, 0, AIGroup:GetVec2().y) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:RouteRTB(ClosestAirbase) - end - -end - ---- AI_BALANCER:onenterMonitoring --- @param #AI_BALANCER self -function AI_BALANCER:onenterMonitoring( SetGroup ) - - self:T2( { self.SetClient:Count() } ) - --self.SetClient:Flush() - - self.SetClient:ForEachClient( - --- SetClient:ForEachClient - -- @param Wrapper.Client#CLIENT Client - function( Client ) - self:T3(Client.ClientName) - - local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP - if AIGroup then self:T( { AIGroup = AIGroup:GetName(), IsAlive = AIGroup:IsAlive() } ) end - if Client:IsAlive() == true then - - if AIGroup and AIGroup:IsAlive() == true then - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - self:Destroy( Client.UnitName, AIGroup ) - else - -- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange ) - - self:T2( RangeZone ) - - _DATABASE:ForEachPlayerUnit( - --- Nameless function - -- @param Wrapper.Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:T2( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:T2( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- Nameless function - -- @param Core.Zone#ZONE_RADIUS RangeZone - -- @param Wrapper.Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - if PlayerInRange.Value == false then - self:Return( AIGroup ) - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - self.Set:Remove( Client.UnitName ) - end - else - if not AIGroup or not AIGroup:IsAlive() == true then - self:T( "Client " .. Client.UnitName .. " not alive." ) - self:T( { Queue = self.SpawnQueue[Client.UnitName] } ) - if not self.SpawnQueue[Client.UnitName] then - -- Spawn a new AI taking into account the spawn interval Earliest, Latest - self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName ) - self.SpawnQueue[Client.UnitName] = true - self:T( "New AI Spawned for Client " .. Client.UnitName ) - end - end - end - return true - end - ) - - self:__Monitor( 10 ) -end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua deleted file mode 100644 index 8f1f1501b..000000000 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ /dev/null @@ -1,543 +0,0 @@ ---- **AI** - Perform Combat Air Patrolling (CAP) for airplanes. --- --- **Features:** --- --- * Patrol AI airplanes within a given zone. --- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel threshold to RTB on time. --- * Engage the enemy when detected. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAP) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Quax**: Concept, Advice & Testing. --- * **Pikey**: Concept, Advice & Testing. --- * **Gunterlund**: Test case revision. --- * **Whisper**: Testing. --- * **Delta99**: Testing. --- --- === --- --- @module AI.AI_CAP --- @image AI_Combat_Air_Patrol.JPG - --- @type AI_CAP_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1. AI_CAP_ZONE constructor --- --- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object. --- --- ## 2. AI_CAP_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_CAP_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2 AI_CAP_ZONE Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{#AI_CAP_ZONE.SetEngageRange}() to define that range. --- --- ## 4. Set the Zone of Engagement --- --- ![Zone](..\Presentations\AI_CAP\Dia12.JPG) --- --- An optional @{Core.Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{#AI_CAP_ZONE.SetEngageZone}() to define that Zone. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CAP_ZONE -AI_CAP_ZONE = { - ClassName = "AI_CAP_ZONE", -} - ---- Creates a new AI_CAP_ZONE object --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE - - self.Accomplished = false - self.Engaging = false - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnBeforeEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnAfterEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] Engage - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] __Engage - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnEnterEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnBeforeFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnAfterFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] Fired - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] __Fired - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnAfterDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] Destroy - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] __Destroy - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnAfterAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] Abort - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] __Abort - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] Accomplish - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] __Accomplish - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_CAP_ZONE self --- @param #number EngageRange The Engage Range. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- onafter State Transition for Event Start. --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - -end - --- @param AI.AI_CAP#AI_CAP_ZONE --- @param Wrapper.Group#GROUP EngageGroup -function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm ) - - EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } ) - - if EngageGroup:IsAlive() then - Fsm:__Engage( 1 ) - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To ) - - if From ~= "Engaging" then - - local Engage = false - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - Engage = true - break - end - end - - if Engage == true then - self:F( 'Detected -> Engaging' ) - self:__Engage( 1 ) - end - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) - - if Controllable and Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - if not CurrentVec2 then return self end - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToEngageZoneSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTEvadeFire() - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - if self.EngageZone then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Within Zone and Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - if self.EngageRange then - if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then - self:F( {"Within Range and Engaging", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - if #AttackTasks == 0 then - self:F("No targets found -> Going back to Patrolling") - self:__Abort( 1 ) - self:__Route( 1 ) - self:SetDetectionActivated() - else - - AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self ) - EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - self:SetDetectionDeactivated() - end - - Controllable:Route( EngageRoute, 0.5 ) - - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - --- @param #AI_CAP_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua deleted file mode 100644 index 8fec1ca3f..000000000 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ /dev/null @@ -1,572 +0,0 @@ ---- **AI** - Perform Close Air Support (CAS) near friendlies. --- --- **Features:** --- --- * Hold and standby within a patrol zone. --- * Engage upon command the enemies within an engagement zone. --- * Loop the zone until all enemies are eliminated. --- * Trigger different events upon the results achieved. --- * After combat, return to the patrol zone and hold. --- * RTB when commanded or after fuel. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAS) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Quax**: Concept, Advice & Testing. --- * **Pikey**: Concept, Advice & Testing. --- * **Gunterlund**: Test case revision. --- --- === --- --- @module AI.AI_CAS --- @image AI_Close_Air_Support.JPG - ---- AI_CAS_ZONE class --- @type AI_CAS_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE - ---- Implements the core functions to provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. --- --- ![Banner Image](..\Images\deprecated.png) --- --- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG) --- --- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, --- using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) --- --- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_CAS\Dia10.JPG) --- --- When the AI has accomplished the CAS, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) --- --- ## AI_CAS_ZONE constructor --- --- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object. --- --- ## AI_CAS_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAS\Dia2.JPG) --- --- ### 2.1. AI_CAS_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2. AI_CAS_ZONE Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds. --- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. --- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CAS_ZONE -AI_CAS_ZONE = { - ClassName = "AI_CAS_ZONE", -} - - - ---- Creates a new AI_CAS_ZONE object --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnBeforeEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnAfterEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] Engage - -- @param #AI_CAS_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] __Engage - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnEnterEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnBeforeFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnAfterFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] Fired - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] __Fired - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnAfterDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] Destroy - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] __Destroy - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnAfterAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] Abort - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] __Abort - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] Accomplish - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] __Accomplish - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS. --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - - ---- onafter State Transition for Event Start. --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - --- @param AI.AI_CAS#AI_CAS_ZONE --- @param Wrapper.Group#GROUP EngageGroup -function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm ) - - EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } ) - - if EngageGroup:IsAlive() then - Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection ) - end -end - - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) - - if Controllable:IsAlive() then - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:F( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - self:__Target( -10 ) - - end -end - - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit, - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection - ) - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self ) - EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks ) - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - Controllable:Route( EngageRoute, 0.5 ) - - self:SetRefreshTimeInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start targeting - end -end - - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - --- @param #AI_CAS_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end - - diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua deleted file mode 100644 index eac91c668..000000000 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ /dev/null @@ -1,591 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo --- @image Cargo.JPG - ---- @type AI_CARGO --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Base class for the dynamic cargo handling capability for AI groups. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- The derived classes from this module are: --- --- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones. --- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones. --- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_CARGO -AI_CARGO = { - ClassName = "AI_CARGO", - Coordinate = nil, -- Core.Point#COORDINATE, - Carrier_Cargo = {}, -} - ---- Creates a new AI_CARGO object. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier Cargo carrier group. --- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport. --- @return #AI_CARGO self -function AI_CARGO:New( Carrier, CargoSet ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO - - self.CargoSet = CargoSet -- Core.Set#SET_CARGO - self.CargoCarrier = Carrier -- Wrapper.Group#GROUP - - self:SetStartState( "Unloaded" ) - - -- Board - self:AddTransition( "Unloaded", "Pickup", "Unloaded" ) - self:AddTransition( "*", "Load", "*" ) - self:AddTransition( "*", "Reload", "*" ) - self:AddTransition( "*", "Board", "*" ) - self:AddTransition( "*", "Loaded", "Loaded" ) - self:AddTransition( "Loaded", "PickedUp", "Loaded" ) - - -- Unload - self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "*", "Unload", "*" ) - self:AddTransition( "*", "Unboard", "*" ) - self:AddTransition( "*", "Unloaded", "Unloaded" ) - self:AddTransition( "Unloaded", "Deployed", "Unloaded" ) - - - --- Pickup Handler OnBefore for AI_CARGO - -- @function [parent=#AI_CARGO] OnBeforePickup - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterPickup - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] Pickup - -- @param #AI_CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Asynchronous Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] __Pickup - -- @param #AI_CARGO self - -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location. - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Handler OnBefore for AI_CARGO - -- @function [parent=#AI_CARGO] OnBeforeDeploy - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterDeploy - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] Deploy - -- @param #AI_CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Asynchronous Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] __Deploy - -- @param #AI_CARGO self - -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - - --- Loaded Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterLoaded - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Unloaded Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterUnloaded - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From - -- @param #string Event - -- @param #string To - - --- On after Deployed event. - -- @function [parent=#AI_CARGO] OnAfterDeployed - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- @param #boolean Defend Defend for APCs. - - - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - CarrierUnit:SetCargoBayWeightLimit() - end - - self.Transporting = false - self.Relocating = false - - return self -end - - - -function AI_CARGO:IsTransporting() - - return self.Transporting == true -end - -function AI_CARGO:IsRelocating() - - return self.Relocating == true -end - - ---- On after Pickup event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - self.Transporting = false - self.Relocating = true - -end - - ---- On after Deploy event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Deploy place. --- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - self.Relocating = false - self.Transporting = true - -end - ---- On before Load event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - local Boarding = false - - local LoadInterval = 2 - local LoadDelay = 1 - local Carrier_List = {} - local Carrier_Weight = {} - - if Carrier and Carrier:IsAlive() then - self.Carrier_Cargo = {} - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - - local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - Carrier_List[#Carrier_List+1] = CarrierUnit - Carrier_Weight[CarrierUnit] = CargoBayFreeWeight - end - - local Carrier_Count = #Carrier_List - local Carrier_Index = 1 - - local Loaded = false - - for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - - self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } ) - - -- Try all Carriers, but start from the one according the Carrier_Index - for Carrier_Loop = 1, #Carrier_List do - - local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT - - -- This counters loop through the available Carriers. - Carrier_Index = Carrier_Index + 1 - if Carrier_Index > Carrier_Count then - Carrier_Index = 1 - end - - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then - if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then - self:F( { "In radius", CarrierUnit:GetName() } ) - - local CargoWeight = Cargo:GetWeight() - local CarrierSpace=Carrier_Weight[CarrierUnit] - - -- Only when there is space within the bay to load the next cargo item! - if CarrierSpace > CargoWeight then - Carrier:RouteStop() - --Cargo:Ungroup() - Cargo:__Board( -LoadDelay, CarrierUnit ) - self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone ) - - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval - - -- So now this CarrierUnit has Cargo that is being loaded. - -- This will be used further in the logic to follow and to check cargo status. - self.Carrier_Cargo[Cargo] = CarrierUnit - Boarding = true - Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight - Loaded = true - - -- Ok, we loaded a cargo, now we can stop the loop. - break - else - self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace)) - end - end - end - - end - - end - - if not Loaded == true then - -- No loading happened, so we need to pickup something else. - self.Relocating = false - end - end - - return Boarding - -end - - ---- On before Reload event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onbeforeReload( Carrier, From, Event, To ) - self:F( { Carrier, From, Event, To } ) - - local Boarding = false - - local LoadInterval = 2 - local LoadDelay = 1 - local Carrier_List = {} - local Carrier_Weight = {} - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - - Carrier_List[#Carrier_List+1] = CarrierUnit - end - - local Carrier_Count = #Carrier_List - local Carrier_Index = 1 - - local Loaded = false - - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - - self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } ) - - -- Try all Carriers, but start from the one according the Carrier_Index - for Carrier_Loop = 1, #Carrier_List do - - local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT - - -- This counters loop through the available Carriers. - Carrier_Index = Carrier_Index + 1 - if Carrier_Index > Carrier_Count then - Carrier_Index = 1 - end - - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then - Carrier:RouteStop() - Cargo:__Board( -LoadDelay, CarrierUnit ) - self:__Board( LoadDelay, Cargo, CarrierUnit ) - - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval - - -- So now this CarrierUnit has Cargo that is being loaded. - -- This will be used further in the logic to follow and to check cargo status. - self.Carrier_Cargo[Cargo] = CarrierUnit - Boarding = true - Loaded = true - end - - end - - end - - if not Loaded == true then - -- No loading happened, so we need to pickup something else. - self.Relocating = false - end - end - - return Boarding - -end - ---- On after Board event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param Wrapper.Unit#UNIT CarrierUnit --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } ) - - if Carrier and Carrier:IsAlive() then - self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } ) - if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then - self:__Board( -10, Cargo, CarrierUnit, PickupZone ) - return - end - end - - self:__Loaded( 0.1, Cargo, CarrierUnit, PickupZone ) - -end - ---- On after Loaded event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @return #boolean Cargo loaded. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - local Loaded = true - - if Carrier and Carrier:IsAlive() then - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } ) - if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then - Loaded = false - end - end - end - - if Loaded then - self:__PickedUp( 0.1, PickupZone ) - end - -end - ---- On after PickedUp event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - Carrier:RouteResume() - - local HasCargo = false - if Carrier and Carrier:IsAlive() then - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - HasCargo = true - break - end - end - - self.Relocating = false - if HasCargo then - self:F( "Transporting" ) - self.Transporting = true - end - -end - - - - ---- On after Unload event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } ) - - local UnboardInterval = 5 - local UnboardDelay = 5 - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - Carrier:RouteStop() - for _, Cargo in pairs( CarrierUnit:GetCargo() ) do - self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } ) - if Cargo:IsLoaded() then - Cargo:__UnBoard( UnboardDelay ) - UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval - self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend ) - if not Defend == true then - Cargo:SetDeployed( true ) - end - end - end - end - end - -end - ---- On after Unboard event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) - - if Carrier and Carrier:IsAlive() then - if not Cargo:IsUnLoaded() then - self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend ) - return - end - end - - self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend ) - -end - ---- On after Unloaded event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) - - local AllUnloaded = true - - --Cargo:Regroup() - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - local IsEmpty = CarrierUnit:IsCargoEmpty() - self:T({ IsEmpty = IsEmpty }) - if not IsEmpty then - AllUnloaded = false - break - end - end - - if AllUnloaded == true then - if DeployZone == true then - self.Carrier_Cargo = {} - end - self.CargoCarrier = Carrier - end - end - - if AllUnloaded == true then - self:__Deployed( 5, DeployZone, Defend ) - end - -end - ---- On after Deployed event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- @param #boolean Defend Defend for APCs. -function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - if not Defend == true then - self.Transporting = false - else - self:F( "Defending" ) - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua deleted file mode 100644 index baf202d0d..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ /dev/null @@ -1,609 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using ground vehicles. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_APC --- @image AI_Cargo_Dispatching_For_APC.JPG - --- @type AI_CARGO_APC --- @extends AI.AI_Cargo#AI_CARGO - - ---- Brings a dynamic cargo handling capability for an AI vehicle group. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo loading. --- --- The module will load automatically cargo when the APCs are within boarding or loading radius. --- The boarding or loading radius is specified when the cargo is created in the simulation, and therefore, this radius depends on the type of cargo --- and the specified boarding radius. --- --- ## **Defending** the APCs when enemies nearby. --- --- Cargo will defend the carrier with its available arms, and to avoid cargo being lost within the battlefield. --- --- When the APCs are approaching enemy units, something special is happening. --- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group. --- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs, --- to ensure that the APCs are not too far away from the following running infantry. --- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route. --- --- A combat radius needs to be specified in meters at the @{#AI_CARGO_APC.New}() method. --- This combat radius will trigger the unboarding of troops when enemies are within the combat radius around the APCs. --- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit radius (effectiveness) versus --- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered. --- A combat radius of 350 meters to 500 meters has been proven to be the most effective and efficient. --- --- However, when the defense of the carrier, is not required, it must be switched off. --- This is done by disabling the defense of the carrier using the method @{#AI_CARGO_APC.SetCombatRadius}(), and providing a combat radius of 0 meters. --- It can be switched on later when required by reenabling the defense using the method and providing a combat radius larger than 0. --- --- ## Infantry or cargo **health**. --- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- ## Control the APCs on the map. --- --- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map. --- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch! --- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up. --- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine. --- No workaround is possible on this. --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate. --- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment. --- --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- --- @field #AI_CARGO_APC -AI_CARGO_APC = { - ClassName = "AI_CARGO_APC", - Coordinate = nil, -- Core.Point#COORDINATE, -} - ---- Creates a new AI_CARGO_APC object. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC The carrier APC group. --- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported. --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When the combat radius is 0, no defense will happen of the carrier. --- @return #AI_CARGO_APC -function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) - - local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC - - self:AddTransition( "*", "Monitor", "*" ) - self:AddTransition( "*", "Follow", "Following" ) - self:AddTransition( "*", "Guard", "Unloaded" ) - self:AddTransition( "*", "Home", "*" ) - self:AddTransition( "*", "Reload", "Boarding" ) - self:AddTransition( "*", "Deployed", "*" ) - self:AddTransition( "*", "PickedUp", "*" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - - self:SetCombatRadius( CombatRadius ) - - self:SetCarrier( APC ) - - return self -end - - ---- Set the Carrier. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP CargoCarrier --- @return #AI_CARGO_APC -function AI_CARGO_APC:SetCarrier( CargoCarrier ) - - self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP - self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self ) - - CargoCarrier:HandleEvent( EVENTS.Dead ) - - function CargoCarrier:OnEventDead( EventData ) - self:F({"dead"}) - local AICargoTroops = self:GetState( self, "AI_CARGO_APC" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - AICargoTroops:Destroyed() - end - end - end - --- CargoCarrier:HandleEvent( EVENTS.Hit ) --- --- function CargoCarrier:OnEventHit( EventData ) --- self:F({"hit"}) --- local AICargoTroops = self:GetState( self, "AI_CARGO_APC" ) --- if AICargoTroops then --- self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } ) --- if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then --- -- There are enemies within combat radius. Unload the CargoCarrier. --- AICargoTroops:Unload( false ) --- end --- end --- end - - self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) - self.Coalition = self.CargoCarrier:GetCoalition() - - self:SetControllable( CargoCarrier ) - - self:Guard() - - return self -end - ---- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetOffRoad(Offroad, Formation) - - self:SetPickupOffRoad(Offroad, Formation) - self:SetDeployOffRoad(Offroad, Formation) - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation) - - self.pickupOffroad=Offroad - self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - ---- Set whether the carrier will *not* use roads to *deploy* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation) - - self.deployOffroad=Offroad - self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - - ---- Find a free Carrier within a radius. --- @param #AI_CARGO_APC self --- @param Core.Point#COORDINATE Coordinate --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_APC:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - return NearUnit:GetGroup() - end - end - end - - return nil - -end - ---- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). --- This is only valid for APCs and trucks etc, thus ground vehicles. --- @param #AI_CARGO_APC self --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. --- When the combat radius is 0, no defense will happen of the carrier. --- When the combat radius is not provided, no defense will happen! --- @return #AI_CARGO_APC --- @usage --- --- -- Disembark the infantry when the carrier is under attack. --- AICargoAPC:SetCombatRadius( true ) --- --- -- Keep the cargo in the carrier when the carrier is under attack. --- AICargoAPC:SetCombatRadius( false ) -function AI_CARGO_APC:SetCombatRadius( CombatRadius ) - - self.CombatRadius = CombatRadius or 0 - - if self.CombatRadius > 0 then - self:__Monitor( -5 ) - end - - return self -end - - ---- Follow Infantry to the Carrier. --- @param #AI_CARGO_APC self --- @param #AI_CARGO_APC Me --- @param Wrapper.Unit#UNIT APCUnit --- @param Cargo.CargoGroup#CARGO_GROUP Cargo --- @return #AI_CARGO_APC -function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup ) - - local InfantryGroup = CargoGroup:GetGroup() - - self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) - - --if self:Is( "Following" ) then - - if APCUnit:IsAlive() then - -- We check if the Cargo is near to the CargoCarrier. - if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then - - -- The Cargo does not need to follow the Carrier. - Me:Guard() - - else - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - - if InfantryGroup:IsAlive() then - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - - local Waypoints = {} - - -- Calculate the new Route. - local FromCoord = InfantryGroup:GetCoordinate() - local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) - self:F({FromGround=FromGround}) - table.insert( Waypoints, FromGround ) - - local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) - local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) - self:F({ToGround=ToGround}) - table.insert( Waypoints, ToGround ) - - local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup ) - - self:F({Waypoints = Waypoints}) - local Waypoint = Waypoints[#Waypoints] - InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - end - end - end -end - - ---- On after Monitor event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) - self:F( { APC, From, Event, To, IsTransporting = self:IsTransporting() } ) - - if self.CombatRadius > 0 then - if APC and APC:IsAlive() then - if self.CarrierCoordinate then - if self:IsTransporting() == true then - local Coordinate = APC:GetCoordinate() - if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then - self.Zone:Scan( { Object.Category.UNIT } ) - if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - if self:Is( "Unloaded" ) then - -- There are no enemies within combat radius. Reload the CargoCarrier. - self:Reload() - end - else - if self:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! - else - if self:Is( "Unloaded" ) then - --self:Follow() - end - self:F( "I am here" .. self:GetCurrentState() ) - if self:Is( "Following" ) then - for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - local APCUnit = APCUnit -- Wrapper.Unit#UNIT - if Cargo:IsAlive() then - if not Cargo:IsNear( APCUnit, 40 ) then - APCUnit:RouteStop() - self.CarrierStopped = true - else - if self.CarrierStopped then - if Cargo:IsNear( APCUnit, 25 ) then - APCUnit:RouteResume() - self.CarrierStopped = nil - end - end - end - end - end - end - end - end - end - end - - end - self.CarrierCoordinate = APC:GetCoordinate() - end - - self:__Monitor( -5 ) - end - -end - - ---- On after Follow event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AI_CARGO_APC:onafterFollow( APC, From, Event, To ) - self:F( { APC, From, Event, To } ) - - self:F( "Follow" ) - if APC and APC:IsAlive() then - for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsUnLoaded() then - self:FollowToCarrier( self, APCUnit, Cargo ) - APCUnit:RouteResume() - end - end - end - -end - ---- Pickup task function. Triggers Load event. --- @param Wrapper.Group#GROUP APC The cargo carrier group. --- @param #AI_CARGO_APC sel `AI_CARGO_APC` class. --- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). --- @param #number Speed Speed (not used). --- @param Core.Zone#ZONE PickupZone Pickup zone. -function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone) - - APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) - - if APC:IsAlive() then - self:Load( PickupZone ) - end -end - ---- Deploy task function. Triggers Unload event. --- @param Wrapper.Group#GROUP APC The cargo carrier group. --- @param #AI_CARGO_APC self `AI_CARGO_APC` class. --- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). --- @param Core.Zone#ZONE DeployZone Deploy zone. -function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone) - - APC:F( { "AI_CARGO_APC._Deploy:", APC } ) - - if APC:IsAlive() then - self:Unload( DeployZone ) - end -end - - - ---- On after Pickup event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if APC and APC:IsAlive() then - - if Coordinate then - self.RoutePickup = true - - local _speed=Speed or APC:GetSpeedMax()*0.5 - - -- Route on road. - local Waypoints = {} - - if self.pickupOffroad then - Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation) - Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks) - else - Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) - end - - - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone ) - - local Waypoint = Waypoints[#Waypoints] - APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - else - AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) - end - - self:GetParent( self, AI_CARGO_APC ).onafterPickup( self, APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - end - -end - - ---- On after Deploy event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Deploy place. --- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if APC and APC:IsAlive() then - - self.RouteDeploy = true - - -- Set speed in km/h. - local speedmax=APC:GetSpeedMax() - local _speed=Speed or speedmax*0.5 - _speed=math.min(_speed, speedmax) - - -- Route on road. - local Waypoints = {} - - if self.deployOffroad then - Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation) - Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks) - else - Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) - end - - -- Task function - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) - - -- Last waypoint - local Waypoint = Waypoints[#Waypoints] - - -- Set task function - APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - -- Route group - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - - -- Call parent function. - self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - end - -end - ---- On after Unloaded event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_APC:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - - self:GetParent( self, AI_CARGO_APC ).onafterUnloaded( self, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - - -- If Defend == true then we need to scan for possible enemies within combat zone and engage only ground forces. - if Defend == true then - self.Zone:Scan( { Object.Category.UNIT } ) - if not self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - -- OK, enemies nearby, now find the enemies and attack them. - local AttackUnits = self.Zone:GetScannedUnits() -- #list - local Move = {} - local CargoGroup = Cargo.CargoObject -- Wrapper.Group#GROUP - Move[#Move+1] = CargoGroup:GetCoordinate():WaypointGround( 70, "Custom" ) - for UnitId, AttackUnit in pairs( AttackUnits ) do - local MooseUnit = UNIT:Find( AttackUnit ) - if MooseUnit:GetCoalition() ~= CargoGroup:GetCoalition() then - Move[#Move+1] = MooseUnit:GetCoordinate():WaypointGround( 70, "Line abreast" ) - --MoveTo.Task = CargoGroup:TaskCombo( CargoGroup:TaskAttackUnit( MooseUnit, true ) ) - self:F( { MooseUnit = MooseUnit:GetName(), CargoGroup = CargoGroup:GetName() } ) - end - end - CargoGroup:RoutePush( Move, 0.1 ) - end - - end - -end - ---- On after Deployed event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend ) - self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - self:__Guard( 0.1 ) - - self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend ) - -end - - ---- On after Home event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. This parameter is ignored for APCs. -function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, Height, HomeZone ) - - if APC and APC:IsAlive() ~= nil then - - self.RouteHome = true - - Speed = Speed or APC:GetSpeedMax()*0.5 - - local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true ) - - self:F({Waypoints = Waypoints}) - local Waypoint = Waypoints[#Waypoints] - - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua deleted file mode 100644 index ff814d6be..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ /dev/null @@ -1,512 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using airplanes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Airplane --- @image AI_Cargo_Dispatching_For_Airplanes.JPG - --- @type AI_CARGO_AIRPLANE --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Brings a dynamic cargo handling capability for an AI airplane group. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases. --- --- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Infantry health. --- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_CARGO_AIRPLANE -AI_CARGO_AIRPLANE = { - ClassName = "AI_CARGO_AIRPLANE", - Coordinate = nil, -- Core.Point#COORDINATE -} - ---- Creates a new AI_CARGO_AIRPLANE object. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Plane used for transportation of cargo. --- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported. --- @return #AI_CARGO_AIRPLANE -function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) - - local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE - - self:AddTransition( "*", "Landed", "*" ) - self:AddTransition( "*", "Home" , "*" ) - - self:AddTransition( "*", "Destroyed", "Destroyed" ) - - --- Pickup Handler OnBefore for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo transport plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. - -- @param #number Speed in km/h for travelling to pickup base. - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo transport plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Pickup Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] Pickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] __Pickup - -- @param #AI_CARGO_AIRPLANE self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Deploy Handler OnBefore for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. - -- @param #number Speed Speed in km/h for travelling to deploy base. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- Deploy Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] Deploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] __Deploy - -- @param #AI_CARGO_AIRPLANE self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- On after Loaded event, i.e. triggered when the cargo is inside the carrier. - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param From - -- @param Event - -- @param To - - - --- On after Deployed event. - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. - - -- Set carrier. - self:SetCarrier( Airplane ) - - return self -end - - ---- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Transport plane. --- @return #AI_CARGO_AIRPLANE self -function AI_CARGO_AIRPLANE:SetCarrier( Airplane ) - - local AICargo = self - - self.Airplane = Airplane -- Wrapper.Group#GROUP - self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self ) - - self.RoutePickup = false - self.RouteDeploy = false - - Airplane:HandleEvent( EVENTS.Dead ) - Airplane:HandleEvent( EVENTS.Hit ) - Airplane:HandleEvent( EVENTS.EngineShutdown ) - - function Airplane:OnEventDead( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat range. Unload the Airplane. - AICargoTroops:Destroyed() - end - end - end - - - function Airplane:OnEventHit( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" ) - if AICargoTroops then - self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } ) - if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then - -- There are enemies within combat range. Unload the Airplane. - AICargoTroops:Unload() - end - end - end - - - function Airplane:OnEventEngineShutdown( EventData ) - AICargo.Relocating = false - AICargo:Landed( self.Airplane ) - end - - self.Coalition = self.Airplane:GetCoalition() - - self:SetControllable( Airplane ) - - return self -end - - ---- Find a free Carrier within a range. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - self:SetCarrier( NearUnit ) - break - end - end - end - -end - ---- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param From --- @param Event --- @param To -function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) - - self:F({Airplane, From, Event, To}) - - if Airplane and Airplane:IsAlive()~=nil then - - -- Aircraft was sent to this airbase to pickup troops. Initiate loadling. - if self.RoutePickup == true then - self:Load( self.PickupZone ) - end - - -- Aircraft was send to this airbase to deploy troops. Initiate unloading. - if self.RouteDeploy == true then - self:Unload() - self.RouteDeploy = false - end - - end - -end - - ---- On after "Pickup" event. Routes transport to pickup airbase. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. --- @param #number Speed Speed in km/h for travelling to pickup base. --- @param #number Height Height in meters to move to the pickup coordinate. --- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. -function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if Airplane and Airplane:IsAlive() then - - local airbasepickup=Coordinate:GetClosestAirbase() - - self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName()) - - -- Get closest airbase of current position. - local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() - - -- Two cases. Aircraft spawned in air or at an airbase. - if Airplane:InAir() then - self.Airbase=nil --> route will start in air - else - self.Airbase=ClosestAirbase - end - - -- Set pickup airbase. - local Airbase = self.PickupZone:GetAirbase() - - -- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase. - local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) - - if Airplane:InAir() or Dist>500 then - - -- Route aircraft to pickup airbase. - self:Route( Airplane, Airbase, Speed, Height ) - - -- Set airbase as starting point in the next Route() call. - self.Airbase = Airbase - - -- Aircraft is on a pickup mission. - self.RoutePickup = true - - else - - -- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event. - self.RoutePickup=true - self:Landed() - - end - - self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone ) - - end - - -end - ---- On after Depoly event. Routes plane to the airbase where the troops are deployed. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. --- @param #number Speed Speed in km/h for travelling to the deploy base. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. -function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if Airplane and Airplane:IsAlive()~=nil then - - local Airbase = Coordinate:GetClosestAirbase() - - if DeployZone then - Airbase=DeployZone:GetAirbase() - end - - -- Activate uncontrolled airplane. - if Airplane:IsAlive()==false then - Airplane:SetCommand({id = 'Start', params = {}}) - end - - -- Route to destination airbase. - self:Route( Airplane, Airbase, Speed, Height ) - - -- Aircraft is on a depoly mission. - self.RouteDeploy = true - - -- Set destination airbase for next :Route() command. - self.Airbase = Airbase - - self:GetParent( self, AI_CARGO_AIRPLANE ).onafterDeploy( self, Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone ) - end - -end - - ---- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. -function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone ) - - local UnboardInterval = 10 - local UnboardDelay = 10 - - if Airplane and Airplane:IsAlive() then - for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do - local Cargos = AirplaneUnit:GetCargo() - for CargoID, Cargo in pairs( Cargos ) do - - local Angle = 180 - local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading ) - - Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate ) - UnboardDelay = UnboardDelay + UnboardInterval - Cargo:SetDeployed( true ) - self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone ) - end - end - end - -end - ---- Route the airplane from one airport or it's current position to another airbase. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Airplane group to be routed. --- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase. --- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do. --- @param #number Height Height in meters to move to the Airbase. --- @param #boolean Uncontrolled If true, spawn group in uncontrolled state. -function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled ) - - if Airplane and Airplane:IsAlive() then - - -- Set takeoff type. - local Takeoff = SPAWN.Takeoff.Cold - - -- Get template of group. - local Template = Airplane:GetTemplate() - - -- Nil check - if Template==nil then - return - end - - -- Waypoints of the route. - local Points={} - - -- To point. - local AirbasePointVec2 = Airbase:GetPointVec2() - local ToWaypoint = AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) - - --ToWaypoint["airdromeId"] = Airbase:GetID() - --ToWaypoint["speed_locked"] = true - - - -- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned. - if self.Airbase then - - -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. - Template.route.points[2] = ToWaypoint - - -- Respawn group at the current airbase. - Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled) - - else - - -- From point. - local GroupPoint = Airplane:GetVec2() - local FromWaypoint = {} - FromWaypoint.x = GroupPoint.x - FromWaypoint.y = GroupPoint.y - FromWaypoint.type = "Turning Point" - FromWaypoint.action = "Turning Point" - FromWaypoint.speed = Airplane:GetSpeedMax()*0.8 - - -- The two route points. - Points[1] = FromWaypoint - Points[2] = ToWaypoint - - local PointVec3 = Airplane:GetPointVec3() - Template.x = PointVec3.x - Template.y = PointVec3.z - - Template.route.points = Points - - local GroupSpawned = Airplane:Respawn(Template) - - end - end -end - ---- On after Home event. Aircraft will be routed to their home base. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane The cargo plane. --- @param From From state. --- @param Event Event. --- @param To To State. --- @param Core.Point#COORDINATE Coordinate Home place (not used). --- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to. -function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, Height, HomeZone ) - if Airplane and Airplane:IsAlive() then - - -- We are going home! - self.RouteHome = true - - -- Home Base. - local HomeBase=HomeZone:GetAirbase() - self.Airbase=HomeBase - - -- Now route the airplane home - self:Route( Airplane, HomeBase, Speed, Height ) - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua deleted file mode 100644 index b63c76192..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ /dev/null @@ -1,1240 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- ## Features: --- --- * AI_CARGO_DISPATCHER is the **base class** for: --- --- * @{AI.AI_Cargo_Dispatcher_APC#AI_CARGO_DISPATCHER_APC} --- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_CARGO_DISPATCHER_HELICOPTER} --- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_CARGO_DISPATCHER_AIRPLANE} --- --- * Provides the facilities to transport cargo over the battle field for the above classes. --- * Dispatches transport tasks to a common set of cargo transporting groups. --- * Different options can be setup to tweak the cargo transporation behaviour. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Cargo_Dispatcher) --- --- === --- --- ![Banner Image](..\Images\deprecated.png) --- --- # The dispatcher concept. --- --- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO_DISPATCHER module uses the @{Cargo.Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects --- to transport @{Cargo.Cargo} towards several deploy zones. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_DISPATCHER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- --- ## Why cargo dispatching? --- --- It provides a realistic way of distributing your army forces around the battlefield, and to provide a quick means of cargo transportation. --- Instead of having troops or cargo to "appear" suddenly at certain locations, the dispatchers will pickup the cargo and transport it. --- It also allows to enforce or retreat your army from certain zones when needed, using helicopters or APCs. --- Airplanes can transport cargo over larger distances between the airfields. --- --- --- ## What is a cargo object then? --- --- In order to make use of the MOOSE cargo system, you need to **declare** the DCS objects as MOOSE cargo objects! --- This sounds complicated, but it is actually quite simple. --- --- See here an example: --- --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- --- The above code declares a MOOSE cargo object called `EngineerCargoGroup`. --- It actually just refers to an infantry group created within the sim called `"Engineers"`. --- The infantry group now becomes controlled by the MOOSE cargo object `EngineerCargoGroup`. --- A MOOSE cargo object also has properties, like the type of cargo, the logical name, and the reporting range. --- --- For more information, please consult the @{Cargo.Cargo} module documentation. Please read through it, because it will explain how to setup the cargo objects for use --- within your dispatchers. --- --- --- ## Do I need to do a lot of coding to setup a dispatcher? --- --- No! It requires a bit of studying to set it up, but once you understand the different components that use the cargo dispatcher, it becomes very easy. --- Also, the dispatchers work in a true dynamic environment. The carriers and cargo, pickup and deploy zones can be created dynamically in your mission, --- and will automatically be recognized by the dispatcher. --- --- --- ## Is the dispatcher causing a lot of CPU overhead? --- --- A little yes, but once the cargo is properly loaded into the carrier, the CPU consumption is very little. --- When infantry or vehicles board into a carrier, or unboard from a carrier, you may perceive certain performance lags. --- We are working to minimize the impact of those. --- That being said, the DCS simulator is limited. It is just impossible to deploy hundreds of cargo over the battlefield, hundreds of helicopters transporting, --- without any performance impact. The amount of helicopters that are active and flying in your simulation influences more the performance than the dispatchers. --- It really comes down to trying it out and getting experienced with what is possible and what is not (or too much). --- --- --- ## Are the dispatchers a "black box" in terms of the logic? --- --- No. You can tailor the dispatcher mechanisms using event handlers, and create additional logic to enhance the behaviour and dynamism in your own mission. --- The events are listed below, and so are the options, but here are a couple of examples of what is possible: --- --- * You could handle the **Deployed** event, when all the cargo is unloaded from a carrier in the dispatcher. --- Adding your own code to the event handler, you could move the deployed cargo (infantry) to specific points to engage in the battlefield. --- --- * When a carrier is picking up cargo, the *Pickup** event is triggered, and you can inform the coalition of this event, --- because it is an indication that troops are planned to join. --- --- --- ## Are there options that you can set to modify the behaviour of the carries? --- --- Yes, there are options to configure: --- --- * the location where carriers will park or land near the cargo for pickup. --- * the location where carriers will park or land in the deploy zone for cargo deployment. --- * the height for airborne carriers when they fly to and from pickup and deploy zones. --- * the speed of the carriers. This is an important parameter, because depending on the tactication situation, speed will influence the detection by radars. --- --- --- ## Can the zones be of any zone type? --- --- Yes, please ensure that the zones are declared using the @{Core.Zone} classes. --- Possible zones that function at the moment are ZONE, ZONE_GROUP, ZONE_UNIT, ZONE_POLYGON. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher --- @image AI_Cargo_Dispatcher.JPG - - --- @type AI_CARGO_DISPATCHER --- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. --- @field Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @field Core.Zone#SET_ZONE PickupZoneSet The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere. --- @field Core.Zone#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the carriers. --- @field #number PickupMaxSpeed The maximum speed to move to the cargo pickup location. --- @field #number PickupMinSpeed The minimum speed to move to the cargo pickup location. --- @field #number DeployMaxSpeed The maximum speed to move to the cargo deploy location. --- @field #number DeployMinSpeed The minimum speed to move to the cargo deploy location. --- @field #number PickupMaxHeight The maximum height to fly to the cargo pickup location. --- @field #number PickupMinHeight The minimum height to fly to the cargo pickup location. --- @field #number DeployMaxHeight The maximum height to fly to the cargo deploy location. --- @field #number DeployMinHeight The minimum height to fly to the cargo deploy location. --- @field #number PickupOuterRadius The outer radius in meters around the cargo coordinate to pickup the cargo. --- @field #number PickupInnerRadius The inner radius in meters around the cargo coordinate to pickup the cargo. --- @field #number DeployOuterRadius The outer radius in meters around the cargo coordinate to deploy the cargo. --- @field #number DeployInnerRadius The inner radius in meters around the cargo coordinate to deploy the cargo. --- @field Core.Zone#ZONE_BASE HomeZone The home zone where the carriers will return when there is no more cargo to pickup. --- @field #number MonitorTimeInterval The interval in seconds when the cargo dispatcher will search for new cargo to be picked up. --- @extends Core.Fsm#FSM - - ---- A dynamic cargo handling capability for AI groups. --- --- --- --- --- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO_DISPATCHER module uses the @{Cargo.Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects --- to transport @{Cargo.Cargo} towards several deploy zones. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_DISPATCHER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- # 1) AI_CARGO_DISPATCHER constructor. --- --- * @{#AI_CARGO_DISPATCHER.New}(): Creates a new AI_CARGO_DISPATCHER object. --- --- Find below some examples of AI cargo dispatcher objects created. --- --- ### An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:SetHomeZone( ZONE:FindByName( "Home" ) ) --- --- ### An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- --- ### An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:SetHomeZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Kobuleti ) ) --- --- --- --- --- # 2) AI_CARGO_DISPATCHER is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- --- --- --- # 3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Use these methods to capture the events and tailor the events with your own code! --- All classes derived from AI_CARGO_DISPATCHER can capture these events, and you can write your own code. --- --- In order to properly capture the events, it is mandatory that you execute the following actions using your script: --- --- * Copy / Paste the code section into your script. --- * Change the CLASS literal to the object name you have in your script. --- * Within the function, you can now write your own code! --- * IntelliSense will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, --- but you need to declare them as they are automatically provided by the event handling system of MOOSE. --- --- You can send messages or fire off any other events within the code section. The sky is the limit! --- --- Mission AID-CGO-140, AID-CGO-240 and AID-CGO-340 contain examples how these events can be tailored. --- --- For those who don't have the time to check the test missions, find the underlying example of a Deployed event that is tailored. --- --- --- Deployed Handler OnAfter for AI_CARGO_DISPATCHER. --- -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @function OnAfterDeployed --- -- @param #AICargoDispatcherHelicopter self --- -- @param #string From A string that contains the "*from state name*" when the event was fired. --- -- @param #string Event A string that contains the "*event name*" when the event was fired. --- -- @param #string To A string that contains the "*to state name*" when the event was fired. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function AICargoDispatcherHelicopter:OnAfterDeployed( From, Event, To, CarrierGroup, DeployZone ) --- --- MESSAGE:NewType( "Group " .. CarrierGroup:GetName() .. " deployed all cargo in zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() --- --- end --- --- --- ## 3.1) Tailor the **Pickup** event --- --- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Pickup event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. --- -- @param #number Height Height in meters to move to the pickup coordinate. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterPickup( From, Event, To, CarrierGroup, Coordinate, Speed, Height, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.2) Tailor the **Load** event --- --- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Load event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoad( From, Event, To, CarrierGroup, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.3) Tailor the **Loading** event --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Loading event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoading( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.4) Tailor the **Loaded** event --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- --- The function provides the CarrierGroup, which is the main group that was loading the Cargo into the CarrierUnit. --- A CarrierUnit is part of the larger CarrierGroup. --- --- --- --- Loaded event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- -- A CarrierUnit can be part of the larger CarrierGroup. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.5) Tailor the **PickedUp** event --- --- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- PickedUp event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterPickedUp( From, Event, To, CarrierGroup, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.6) Tailor the **Deploy** event --- --- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Deploy event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. --- -- @param #number Height Height in meters to move to the deploy coordinate. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterDeploy( From, Event, To, CarrierGroup, Coordinate, Speed, Height, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.7) Tailor the **Unload** event --- --- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Unload event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.8) Tailor the **Unloading** event --- --- --- --- UnLoading event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.9) Tailor the **Unloaded** event --- --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- Unloaded event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- -- A CarrierUnit can be part of the larger CarrierGroup. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnloaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.10) Tailor the **Deployed** event --- --- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Deployed event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterDeployed( From, Event, To, CarrierGroup, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- ## 3.11) Tailor the **Home** event --- --- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- Home event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo and this event won't be triggered. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. --- -- @param #number Height Height in meters to move to the home coordinate. --- -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterHome( From, Event, To, CarrierGroup, Coordinate, Speed, Height, HomeZone ) --- --- -- Write here your own code. --- --- end --- --- --- --- --- # 4) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER.SetPickupRadius}(): Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- * @{#AI_CARGO_DISPATCHER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo. --- --- --- --- --- # 5) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER.SetDeployRadius}(): Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- * @{#AI_CARGO_DISPATCHER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo. --- --- --- --- --- # 6) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Carriers will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the carriers will wait near the deploy zone for a new pickup command. --- --- === --- --- @field #AI_CARGO_DISPATCHER -AI_CARGO_DISPATCHER = { - ClassName = "AI_CARGO_DISPATCHER", - AI_Cargo = {}, - PickupCargo = {} -} - ---- List of AI_Cargo --- @field #list -AI_CARGO_DISPATCHER.AI_Cargo = {} - ---- List of PickupCargo --- @field #list -AI_CARGO_DISPATCHER.PickupCargo = {} - - ---- Creates a new AI_CARGO_DISPATCHER object. --- @param #AI_CARGO_DISPATCHER self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the carriers. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:Start() --- --- @usage --- --- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- --- @usage --- --- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:Start() --- -function AI_CARGO_DISPATCHER:New( CarrierSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER - - self.SetCarrier = CarrierSet -- Core.Set#SET_GROUP - self.SetCargo = CargoSet -- Core.Set#SET_CARGO - - - self.PickupZoneSet=PickupZoneSet - self.DeployZoneSet=DeployZoneSet - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - - self:AddTransition( "Monitoring", "Pickup", "Monitoring" ) - self:AddTransition( "Monitoring", "Load", "Monitoring" ) - self:AddTransition( "Monitoring", "Loading", "Monitoring" ) - self:AddTransition( "Monitoring", "Loaded", "Monitoring" ) - self:AddTransition( "Monitoring", "PickedUp", "Monitoring" ) - - self:AddTransition( "Monitoring", "Transport", "Monitoring" ) - - self:AddTransition( "Monitoring", "Deploy", "Monitoring" ) - self:AddTransition( "Monitoring", "Unload", "Monitoring" ) - self:AddTransition( "Monitoring", "Unloading", "Monitoring" ) - self:AddTransition( "Monitoring", "Unloaded", "Monitoring" ) - self:AddTransition( "Monitoring", "Deployed", "Monitoring" ) - - self:AddTransition( "Monitoring", "Home", "Monitoring" ) - - self:SetMonitorTimeInterval( 30 ) - - self:SetDeployRadius( 500, 200 ) - - self.PickupCargo = {} - self.CarrierHome = {} - - -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.SetCarrier.OnAfterRemoved( SetCarrier, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - self.PickupCargo[Carrier] = nil - self.CarrierHome[Carrier] = nil - end - - return self -end - - ---- Set the monitor time interval. --- @param #AI_CARGO_DISPATCHER self --- @param #number MonitorTimeInterval The interval in seconds when the cargo dispatcher will search for new cargo to be picked up. --- @return #AI_CARGO_DISPATCHER -function AI_CARGO_DISPATCHER:SetMonitorTimeInterval( MonitorTimeInterval ) - - self.MonitorTimeInterval = MonitorTimeInterval - - return self -end - - ---- Set the home zone. --- When there is nothing anymore to pickup, the carriers will go to a random coordinate in this zone. --- They will await here new orders. --- @param #AI_CARGO_DISPATCHER self --- @param Core.Zone#ZONE_BASE HomeZone The home zone where the carriers will return when there is no more cargo to pickup. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the home coordinate --- local HomeZone = ZONE:New( "Home" ) --- AICargoDispatcherHelicopter:SetHomeZone( HomeZone ) --- -function AI_CARGO_DISPATCHER:SetHomeZone( HomeZone ) - - self.HomeZone = HomeZone - - return self -end - - ---- Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. --- This radius is influencing the location where the carrier will land to pickup the cargo. --- There are two aspects that are very important to remember and take into account: --- --- - Ensure that the outer and inner radius are within reporting radius set by the cargo. --- For example, if the cargo has a reporting radius of 400 meters, and the outer and inner radius is set to 500 and 450 respectively, --- then no cargo will be loaded!!! --- - Also take care of the potential cargo position and possible reasons to crash the carrier. This is especially important --- for locations which are crowded with other objects, like in the middle of villages or cities. --- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces. --- --- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone. --- @param #AI_CARGO_DISPATCHER self --- @param #number OuterRadius The outer radius in meters around the cargo coordinate. --- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! --- AICargoDispatcherHelicopter:SetPickupRadius( 500, 300 ) --- -function AI_CARGO_DISPATCHER:SetPickupRadius( OuterRadius, InnerRadius ) - - OuterRadius = OuterRadius or 0 - InnerRadius = InnerRadius or OuterRadius - - self.PickupOuterRadius = OuterRadius - self.PickupInnerRadius = InnerRadius - - return self -end - - ---- Set the speed or randomizes the speed in km/h to pickup the cargo. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxSpeed (optional) The maximum speed to move to the cargo pickup location. --- @param #number MinSpeed The minimum speed to move to the cargo pickup location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum pickup speed to be 100 km/h and the maximum speed to be 200 km/h. --- AICargoDispatcherHelicopter:SetPickupSpeed( 200, 100 ) --- -function AI_CARGO_DISPATCHER:SetPickupSpeed( MaxSpeed, MinSpeed ) - - MaxSpeed = MaxSpeed or 999 - MinSpeed = MinSpeed or MaxSpeed - - self.PickupMinSpeed = MinSpeed - self.PickupMaxSpeed = MaxSpeed - - return self -end - - ---- Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius. --- This radius is influencing the location where the carrier will land to deploy the cargo. --- There is an aspect that is very important to remember and take into account: --- --- - Take care of the potential cargo position and possible reasons to crash the carrier. This is especially important --- for locations which are crowded with other objects, like in the middle of villages or cities. --- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces. --- --- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone. --- @param #AI_CARGO_DISPATCHER self --- @param #number OuterRadius The outer radius in meters around the cargo coordinate. --- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! --- AICargoDispatcherHelicopter:SetDeployRadius( 500, 300 ) --- -function AI_CARGO_DISPATCHER:SetDeployRadius( OuterRadius, InnerRadius ) - - OuterRadius = OuterRadius or 0 - InnerRadius = InnerRadius or OuterRadius - - self.DeployOuterRadius = OuterRadius - self.DeployInnerRadius = InnerRadius - - return self -end - - ---- Sets or randomizes the speed in km/h to deploy the cargo. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxSpeed The maximum speed to move to the cargo deploy location. --- @param #number MinSpeed (optional) The minimum speed to move to the cargo deploy location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum deploy speed to be 100 km/h and the maximum speed to be 200 km/h. --- AICargoDispatcherHelicopter:SetDeploySpeed( 200, 100 ) --- -function AI_CARGO_DISPATCHER:SetDeploySpeed( MaxSpeed, MinSpeed ) - - MaxSpeed = MaxSpeed or 999 - MinSpeed = MinSpeed or MaxSpeed - - self.DeployMinSpeed = MinSpeed - self.DeployMaxSpeed = MaxSpeed - - return self -end - - ---- Set the height or randomizes the height in meters to fly and pickup the cargo. The default height is 200 meters. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxHeight (optional) The maximum height to fly to the cargo pickup location. --- @param #number MinHeight (optional) The minimum height to fly to the cargo pickup location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum pickup fly height to be 50 meters and the maximum height to be 200 meters. --- AICargoDispatcherHelicopter:SetPickupHeight( 200, 50 ) --- -function AI_CARGO_DISPATCHER:SetPickupHeight( MaxHeight, MinHeight ) - - MaxHeight = MaxHeight or 200 - MinHeight = MinHeight or MaxHeight - - self.PickupMinHeight = MinHeight - self.PickupMaxHeight = MaxHeight - - return self -end - - ---- Set the height or randomizes the height in meters to fly and deploy the cargo. The default height is 200 meters. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxHeight (optional) The maximum height to fly to the cargo deploy location. --- @param #number MinHeight (optional) The minimum height to fly to the cargo deploy location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum deploy fly height to be 50 meters and the maximum height to be 200 meters. --- AICargoDispatcherHelicopter:SetDeployHeight( 200, 50 ) --- -function AI_CARGO_DISPATCHER:SetDeployHeight( MaxHeight, MinHeight ) - - MaxHeight = MaxHeight or 200 - MinHeight = MinHeight or MaxHeight - - self.DeployMinHeight = MinHeight - self.DeployMaxHeight = MaxHeight - - return self -end - - ---- The Start trigger event, which actually takes action at the specified time interval. --- @param #AI_CARGO_DISPATCHER self -function AI_CARGO_DISPATCHER:onafterMonitor() - - self:F("Carriers") - self.SetCarrier:Flush() - - for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do - local Carrier = Carrier -- Wrapper.Group#GROUP - if Carrier:IsAlive() ~= nil then - local AI_Cargo = self.AI_Cargo[Carrier] - if not AI_Cargo then - - -- ok, so this Carrier does not have yet an AI_CARGO handling object... - -- let's create one and also declare the Loaded and UnLoaded handlers. - self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius ) - AI_Cargo = self.AI_Cargo[Carrier] - - --- Pickup event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickup - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - function AI_Cargo.OnAfterPickup( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, PickupZone ) - self:Pickup( CarrierGroup, Coordinate, Speed, Height, PickupZone ) - end - - --- Load event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoad - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoad( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:Load( CarrierGroup, PickupZone ) - end - - --- Loading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterBoard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loading( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- Loaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoaded( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loaded( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- PickedUp event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickedUp - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterPickedUp( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:PickedUp( CarrierGroup, PickupZone ) - self:Transport( CarrierGroup ) - end - - - --- Deploy event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeploy - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. - -- @param #number Height Height in meters to move to the deploy coordinate. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeploy( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, DeployZone ) - self:Deploy( CarrierGroup, Coordinate, Speed, Height, DeployZone ) - end - - - --- Unload event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnload - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- UnLoading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnboard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( CarrierGroup, Cargo, CarrierUnit, DeployZone ) - end - - - --- Unloaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloaded( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- Deployed event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeployed - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeployed( AI_Cargo, Carrier, From, Event, To, DeployZone ) - self:Deployed( Carrier, DeployZone ) - end - - --- Home event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterHome - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterHome( AI_Cargo, Carrier, From, Event, To, Coordinate, Speed, Height, HomeZone ) - self:Home( Carrier, Coordinate, Speed, Height, HomeZone ) - end - end - - -- The Pickup sequence ... - -- Check if this Carrier need to go and Pickup something... - -- So, if the cargo bay is not full yet with cargo to be loaded ... - self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) - if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then - -- ok, so there is a free Carrier - -- now find the first cargo that is Unloaded - - local PickupCargo = nil - local PickupZone = nil - - self.SetCargo:Flush() - for CargoName, Cargo in UTILS.spairs( self.SetCargo:GetSet(), function( t, a, b ) return t[a]:GetWeight() < t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then - local CargoCoordinate = Cargo:GetCoordinate() - local CoordinateFree = true - --self.PickupZoneSet:Flush() - --PickupZone = self.PickupZoneSet:GetRandomZone() - PickupZone = self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone( CargoCoordinate ) - if not self.PickupZoneSet or PickupZone then - for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do - if CarrierPickup:IsAlive() == true then - if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then - self:F( { "Coordinate not free for ", Cargo = Cargo:GetName(), Carrier:GetName(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - CoordinateFree = false - break - end - else - self.PickupCargo[CarrierPickup] = nil - end - end - if CoordinateFree == true then - -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. - local LargestLoadCapacity = 0 - for _, Carrier in pairs( Carrier:GetUnits() ) do - local LoadCapacity = Carrier:GetCargoBayFreeWeight() - if LargestLoadCapacity < LoadCapacity then - LargestLoadCapacity = LoadCapacity - end - end - -- So if there is a carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. - -- Otherwise break and go to the next carrier. - -- This will skip cargo which is too large to be able to be loaded by carriers - -- and will secure an efficient dispatching scheme. - if LargestLoadCapacity >= Cargo:GetWeight() then - self.PickupCargo[Carrier] = CargoCoordinate - PickupCargo = Cargo - break - else - local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", - tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName())) - self:T(text) - end - end - end - end - end - - if PickupCargo then - self.CarrierHome[Carrier] = nil - local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) - AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), PickupZone ) - break - else - if self.HomeZone then - if not self.CarrierHome[Carrier] then - self.CarrierHome[Carrier] = true - AI_Cargo:Home( self.HomeZone:GetRandomPointVec2(), math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), self.HomeZone ) - end - end - end - end - end - end - - self:__Monitor( self.MonitorTimeInterval ) -end - - ---- Start Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] Start --- @param #AI_CARGO_DISPATCHER self - ---- Start Asynchronous Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] __Start --- @param #AI_CARGO_DISPATCHER self --- @param #number Delay - -function AI_CARGO_DISPATCHER:onafterStart( From, Event, To ) - self:__Monitor( -1 ) -end - - ---- Stop Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] Stop --- @param #AI_CARGO_DISPATCHER self - ---- Stop Asynchronous Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] __Stop --- @param #AI_CARGO_DISPATCHER self --- @param #number Delay - - ---- Make a Carrier run for a cargo deploy action after the cargo has been loaded, by default. --- @param #AI_CARGO_DISPATCHER self --- @param From --- @param Event --- @param To --- @param Wrapper.Group#GROUP Carrier --- @param Cargo.Cargo#CARGO Cargo --- @return #AI_CARGO_DISPATCHER -function AI_CARGO_DISPATCHER:onafterTransport( From, Event, To, Carrier, Cargo ) - - if self.DeployZoneSet then - if self.AI_Cargo[Carrier]:IsTransporting() == true then - local DeployZone = self.DeployZoneSet:GetRandomZone() - - local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) - self.AI_Cargo[Carrier]:__Deploy( 0.1, DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ), math.random( self.DeployMinHeight, self.DeployMaxHeight ), DeployZone ) - end - end - - self:F( { Carrier = Carrier:GetName(), PickupCargo = self.PickupCargo } ) - self.PickupCargo[Carrier] = nil -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua deleted file mode 100644 index ca0ec07d8..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ /dev/null @@ -1,265 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using APCs. --- --- ## Features: --- --- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...). --- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates. --- * Define a list of deploy zones of various types to transport the cargo to. --- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground. --- * Multiple vehicles can transport multiple cargo as one vehicle group. --- * Multiple vehicle groups can be enabled as one collaborating transportation process. --- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles. --- * Different ranges can be setup for enemy defenses. --- * Different options can be setup to tweak the cargo transporation behaviour. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_APC --- @image AI_Cargo_Dispatching_For_APC.JPG - --- @type AI_CARGO_DISPATCHER_APC --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo transportation capability for AI groups. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!! --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- --- # 1) AI_CARGO_DISPATCHER_APC constructor. --- --- * @{#AI_CARGO_DISPATCHER_APC.New}(): Creates a new AI_CARGO_DISPATCHER_APC object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_APC is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- # 3) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- --- # 4) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- --- # 5) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_APC.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CARGO_DISPATCHER_APC -AI_CARGO_DISPATCHER_APC = { - ClassName = "AI_CARGO_DISPATCHER_APC", -} - ---- Creates a new AI_CARGO_DISPATCHER_APC object. --- @param #AI_CARGO_DISPATCHER_APC self --- @param Core.Set#SET_GROUP APCSet The set of @{Wrapper.Group#GROUP} objects of vehicles, trucks, APCs that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the APCs. --- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters. --- @return #AI_CARGO_DISPATCHER_APC --- @usage --- --- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- -function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC - - self:SetDeploySpeed( 120, 70 ) - self:SetPickupSpeed( 120, 70 ) - self:SetPickupRadius( 0, 0 ) - self:SetDeployRadius( 0, 0 ) - - self:SetPickupHeight() - self:SetDeployHeight() - - self:SetCombatRadius( CombatRadius ) - - return self -end - - ---- AI cargo --- @param #AI_CARGO_DISPATCHER_APC self --- @param Wrapper.Group#GROUP APC The APC carrier. --- @param Core.Set#SET_CARGO CargoSet Cargo set. --- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object. -function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet ) - - local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius) - - aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation) - aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation) - - return aicargoapc -end - ---- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). --- This is only valid for APCs and trucks etc, thus ground vehicles. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. --- When the combat radius is 0 (default), no defense will happen of the carrier. --- When the combat radius is not provided, no defense will happen! --- @return #AI_CARGO_DISPATCHER_APC --- @usage --- --- -- Disembark the infantry when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( 500 ) --- --- -- Keep the cargo in the carrier when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( 0 ) -function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius ) - - self.CombatRadius = CombatRadius or 0 - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation) - - self:SetPickupOffRoad(Offroad, Formation) - self:SetDeployOffRoad(Offroad, Formation) - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation) - - self.pickupOffroad=Offroad - self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - ---- Set whether the carrier will *not* use roads to *deploy* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation) - - self.deployOffroad=Offroad - self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua deleted file mode 100644 index b70a43f4c..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ /dev/null @@ -1,171 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Planes. --- --- ## Features: --- --- * The airplanes will fly towards the pickup airbases to pickup the cargo. --- * The airplanes will fly towards the deploy airbases to deploy the cargo. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Airplane --- @image AI_Cargo_Dispatching_For_Airplanes.JPG - - --- @type AI_CARGO_DISPATCHER_AIRPLANE --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- Brings a dynamic cargo handling capability for AI groups. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- # 1) AI_CARGO_DISPATCHER_AIRPLANE constructor. --- --- * @{#AI_CARGO_DISPATCHER_AIRPLANE.New}(): Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_AIRPLANE is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- --- @field #AI_CARGO_DISPATCHER_AIRPLANE -AI_CARGO_DISPATCHER_AIRPLANE = { - ClassName = "AI_CARGO_DISPATCHER_AIRPLANE", -} - ---- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. --- @param #AI_CARGO_DISPATCHER_AIRPLANE self --- @param Core.Set#SET_GROUP AirplaneSet The set of @{Wrapper.Group#GROUP} objects of airplanes that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Zone#SET_ZONE PickupZoneSet The set of zone airbases where the cargo has to be picked up. --- @param Core.Zone#SET_ZONE DeployZoneSet The set of zone airbases where the cargo is deployed. Choice for each cargo is random. --- @return #AI_CARGO_DISPATCHER_AIRPLANE self --- @usage --- --- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:Start() --- -function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE - - self:SetPickupSpeed( 1200, 600 ) - self:SetDeploySpeed( 1200, 600 ) - - self:SetPickupRadius( 0, 0 ) - self:SetDeployRadius( 0, 0 ) - - self:SetPickupHeight( 8000, 6000 ) - self:SetDeployHeight( 8000, 6000 ) - - self:SetMonitorTimeInterval( 600 ) - - return self -end - -function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet ) - - return AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua deleted file mode 100644 index 1c228e872..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ /dev/null @@ -1,201 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Helicopters. --- --- ## Features: --- --- * The helicopters will fly towards the pickup locations to pickup the cargo. --- * The helicopters will fly towards the deploy zones to deploy the cargo. --- * Precision deployment as well as randomized deployment within the deploy zones are possible. --- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Helicopter --- @image AI_Cargo_Dispatching_For_Helicopters.JPG - --- @type AI_CARGO_DISPATCHER_HELICOPTER --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo handling capability for AI helicopter groups. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- --- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- --- --- --- # 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor. --- --- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object. --- --- --- --- --- # 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- ## 3. Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo. --- --- --- --- --- ## 4. Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo. --- --- --- --- --- ## 5. Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_HELICOPTER.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CARGO_DISPATCHER_HELICOPTER -AI_CARGO_DISPATCHER_HELICOPTER = { - ClassName = "AI_CARGO_DISPATCHER_HELICOPTER", -} - ---- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object. --- @param #AI_CARGO_DISPATCHER_HELICOPTER self --- @param Core.Set#SET_GROUP HelicopterSet The set of @{Wrapper.Group#GROUP} objects of helicopters that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the Helicopters. --- @return #AI_CARGO_DISPATCHER_HELICOPTER --- @usage --- --- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:Start() --- -function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER - - self:SetPickupSpeed( 350, 150 ) - self:SetDeploySpeed( 350, 150 ) - - self:SetPickupRadius( 40, 12 ) - self:SetDeployRadius( 40, 12 ) - - self:SetPickupHeight( 500, 200 ) - self:SetDeployHeight( 500, 200 ) - - return self -end - - -function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet ) - - local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) - dispatcher:SetLandingSpeedAndHeight(27, 6) - return dispatcher - -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua deleted file mode 100644 index f0afedeb3..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ /dev/null @@ -1,200 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Ships. --- --- ## Features: --- --- * Transport cargo to various deploy zones using naval vehicles. --- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates. --- * Define a deploy zone of various types to determine the destination of the cargo. --- * Ships will follow shipping lanes as defined in the Mission Editor. --- * Multiple ships can transport multiple cargo as a single group. --- --- === --- --- ## Test Missions: --- --- NEED TO DO --- --- === --- --- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl) --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Ship --- @image AI_Cargo_Dispatcher.JPG - --- @type AI_CARGO_DISPATCHER_SHIP --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo transportation capability for AI groups. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!! --- --- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**. --- --- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo. --- --- --- # 1) AI_CARGO_DISPATCHER_SHIP constructor. --- --- * @{#AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine. --- --- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- # 3) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- --- # 4) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- --- # 5) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CARGO_DISPATCHER_SHIP -AI_CARGO_DISPATCHER_SHIP = { - ClassName = "AI_CARGO_DISPATCHER_SHIP" - } - ---- Creates a new AI_CARGO_DISPATCHER_SHIP object. --- @param #AI_CARGO_DISPATCHER_SHIP self --- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship. --- @param #table ShippingLane Table containing list of Shipping Lanes to be used --- @return #AI_CARGO_DISPATCHER_SHIP --- @usage --- --- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- NEED MORE THOUGHT - ShippingLane is part of Warehouse....... --- local ShippingLane = SET_GROUP:New():FilterPrefixes( "ShippingLane" ):FilterOnce():GetSetObjects() --- --- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane ) --- AICargoDispatcherShip:Start() --- -function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) - - self:SetPickupSpeed( 60, 10 ) - self:SetDeploySpeed( 60, 10 ) - - self:SetPickupRadius( 500, 6000 ) - self:SetDeployRadius( 500, 6000 ) - - self:SetPickupHeight( 0, 0 ) - self:SetDeployHeight( 0, 0 ) - - self:SetShippingLane( ShippingLane ) - - self:SetMonitorTimeInterval( 600 ) - - return self -end - -function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane ) - self.ShippingLane = ShippingLane - - return self - -end - -function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet ) - - return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane ) -end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua deleted file mode 100644 index 61294ba6d..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ /dev/null @@ -1,663 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using helicopters. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Helicopter --- @image AI_Cargo_Dispatching_For_Helicopters.JPG - --- @type AI_CARGO_HELICOPTER --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Brings a dynamic cargo handling capability for an AI helicopter group. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Infantry health. --- --- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CARGO_HELICOPTER -AI_CARGO_HELICOPTER = { - ClassName = "AI_CARGO_HELICOPTER", - Coordinate = nil, -- Core.Point#COORDINATE, -} - -AI_CARGO_QUEUE = {} - ---- Creates a new AI_CARGO_HELICOPTER object. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param Core.Set#SET_CARGO CargoSet --- @return #AI_CARGO_HELICOPTER -function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) - - local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER - - self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) - - self:SetStartState( "Unloaded" ) - -- Boarding - self:AddTransition( "Unloaded", "Pickup", "Unloaded" ) - self:AddTransition( "*", "Landed", "*" ) - self:AddTransition( "*", "Load", "*" ) - self:AddTransition( "*", "Loaded", "Loaded" ) - self:AddTransition( "Loaded", "PickedUp", "Loaded" ) - - -- Unboarding - self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "*", "Queue", "*" ) - self:AddTransition( "*", "Orbit" , "*" ) - self:AddTransition( "*", "Destroyed", "*" ) - self:AddTransition( "*", "Unload", "*" ) - self:AddTransition( "*", "Unloaded", "Unloaded" ) - self:AddTransition( "Unloaded", "Deployed", "Unloaded" ) - - -- RTB - self:AddTransition( "*", "Home" , "*" ) - - --- Pickup Handler OnBefore for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp - -- @param #AI_CARGO_HELICOPTER self - -- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object - - --- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. - -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object - - --- Pickup Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Pickup - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deploy Handler OnBefore for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deployed Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Deploy Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Deploy - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy - -- @param #number Delay Delay in seconds. - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Home Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Home - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should be flying at. - - --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Home - -- @param #number Delay Delay in seconds. - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should be flying at. - - -- We need to capture the Crash events for the helicopters. - -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. - -- So, we need to unlock this when the helo is not anymore ... - Helicopter:HandleEvent( EVENTS.Crash, - function( Helicopter, EventData ) - AI_CARGO_QUEUE[Helicopter] = nil - end - ) - - -- We need to capture the Land events for the helicopters. - -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. - -- So, we need to unlock this when the helo has landed, which can be anywhere ... - -- But only free the landing coordinate after 1 minute, to ensure that all helos have left. - Helicopter:HandleEvent( EVENTS.Land, - function( Helicopter, EventData ) - self:ScheduleOnce( 60, - function( Helicopter ) - AI_CARGO_QUEUE[Helicopter] = nil - end, Helicopter - ) - end - ) - - self:SetCarrier( Helicopter ) - - self.landingspeed = 15 -- kph - self.landingheight = 5.5 -- meter - - return self -end - - - - - ---- Set the Carrier. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @return #AI_CARGO_HELICOPTER -function AI_CARGO_HELICOPTER:SetCarrier( Helicopter ) - - local AICargo = self - - self.Helicopter = Helicopter -- Wrapper.Group#GROUP - self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self ) - - self.RoutePickup = false - self.RouteDeploy = false - - Helicopter:HandleEvent( EVENTS.Dead ) - Helicopter:HandleEvent( EVENTS.Hit ) - Helicopter:HandleEvent( EVENTS.Land ) - - function Helicopter:OnEventDead( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat range. Unload the Helicopter. - AICargoTroops:Destroyed() - end - end - end - - function Helicopter:OnEventLand( EventData ) - AICargo:Landed() - end - - self.Coalition = self.Helicopter:GetCoalition() - - self:SetControllable( Helicopter ) - - return self -end - ---- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing. --- @param #AI_CARGO_HELICOPTER self --- @param #number speed Landing speed in kph(!), e.g. 15 --- @param #number height Landing height in meters(!), e.g. 5.5 --- @return #AI_CARGO_HELICOPTER self --- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so: --- --- BASE:TraceOn() --- BASE:TraceClass("AI_CARGO_HELICOPTER") --- --- Watch the DCS.log for entries stating `Helicopter:, Height = Helicopter:, Velocity = Helicopter:` --- Adjust if necessary. -function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height) - local _speed = speed or 15 - local _height = height or 5.5 - self.landingheight = _height - self.landingspeed = _speed - return self -end - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To -function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) - self:F({From, Event, To}) - Helicopter:F( { Name = Helicopter:GetName() } ) - - if Helicopter and Helicopter:IsAlive() then - - -- S_EVENT_LAND is directly called in two situations: - -- 1 - When the helo lands normally on the ground. - -- 2 - when the helo is hit and goes RTB or even when it is destroyed. - -- For point 2, this is an issue, the infantry may not unload in this case! - -- So we check if the helo is on the ground, and velocity< 15. - -- Only then the infantry can unload (and load too, for consistency)! - - self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) - - if self.RoutePickup == true then - if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then - --self:Load( Helicopter:GetPointVec2() ) - self:Load( self.PickupZone ) - self.RoutePickup = false - end - end - - if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then - self:Unload( self.DeployZone ) - self.RouteDeploy = false - end - end - - end - -end - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate --- @param #number Speed -function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) - self:F({From, Event, To, Coordinate, Speed, DeployZone}) - local HelicopterInZone = false - - if Helicopter and Helicopter:IsAlive() == true then - - local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() ) - - if Distance > 2000 then - self:__Queue( -10, Coordinate, Speed, DeployZone ) - else - - local ZoneFree = true - - for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do - local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS - if ZoneQueue:IsCoordinateInZone( Coordinate ) then - ZoneFree = false - end - end - - self:F({ZoneFree=ZoneFree}) - - if ZoneFree == true then - - local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 ) - - AI_CARGO_QUEUE[Helicopter] = ZoneQueue - - local Route = {} - --- local CoordinateFrom = Helicopter:GetCoordinate() --- local WaypointFrom = CoordinateFrom:WaypointAir( --- "RADIO", --- COORDINATE.WaypointType.TurningPoint, --- COORDINATE.WaypointAction.TurningPoint, --- Speed, --- true --- ) --- Route[#Route+1] = WaypointFrom - local CoordinateTo = Coordinate - - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - 50, - true - ) - Route[#Route+1] = WaypointTo - - local Tasks = {} - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 0 ) - - -- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event. - self.DeployZone = DeployZone - - else - self:__Queue( -10, Coordinate, Speed, DeployZone ) - end - end - else - AI_CARGO_QUEUE[Helicopter] = nil - end -end - - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate --- @param #number Speed -function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate ) - self:F({From, Event, To, Coordinate}) - - if Helicopter and Helicopter:IsAlive() then - - local Route = {} - - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 50, true) - Route[#Route+1] = WaypointTo - - local Tasks = {} - Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route(Route, 0) - end -end - - - ---- On after Deployed event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @return #boolean True if all cargo has been unloaded. -function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) - self:F( { From, Event, To, DeployZone = DeployZone } ) - - self:Orbit( Helicopter:GetCoordinate(), 50 ) - - -- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first. - self:ScheduleOnce( 30, - function( Helicopter ) - AI_CARGO_QUEUE[Helicopter] = nil - end, Helicopter - ) - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone ) - -end - ---- On after Pickup event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Pickup place. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) - self:F({Coordinate, Speed, Height, PickupZone }) - - if Helicopter and Helicopter:IsAlive() ~= nil then - - Helicopter:Activate() - - self.RoutePickup = true - Coordinate.y = Height - - local _speed=Speed or Helicopter:GetSpeedMax()*0.5 - - local Route = {} - - --- Calculate the target route point. - local CoordinateFrom = Helicopter:GetCoordinate() - - --- Create a route point of type air. - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) - - --- Create a route point of type air. - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint,_speed, true) - - Route[#Route+1] = WaypointFrom - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 1 ) - - self.PickupZone = PickupZone - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterPickup( self, Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - end - -end - ---- Depoloy function and queue. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP AICargoHelicopter --- @param Core.Point#COORDINATE Coordinate Coordinate -function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone ) - AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone ) -end - ---- On after Deploy event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter Transport helicopter. --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. -function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - self:F({From, Event, To, Coordinate, Speed, Height, DeployZone}) - if Helicopter and Helicopter:IsAlive() ~= nil then - - self.RouteDeploy = true - - - local Route = {} - - --- Calculate the target route point. - - Coordinate.y = Height - - local _speed=Speed or Helicopter:GetSpeedMax()*0.5 - - --- Create a route point of type air. - local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) - Route[#Route+1] = WaypointFrom - Route[#Route+1] = WaypointFrom - - --- Create a route point of type air. - - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) - - Route[#Route+1] = WaypointTo - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - -- The _Deploy function does not exist. - Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone ) - - Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) - - --Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 0 ) - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeploy( self, Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - end - -end - - ---- On after Home event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) - self:F({From, Event, To, Coordinate, Speed, Height}) - - if Helicopter and Helicopter:IsAlive() ~= nil then - - self.RouteHome = true - - local Route = {} - - --- Calculate the target route point. - - --Coordinate.y = Height - Height = Height or 50 - - Speed = Speed or Helicopter:GetSpeedMax()*0.5 - - --- Create a route point of type air. - local CoordinateFrom = Helicopter:GetCoordinate() - - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) - Route[#Route+1] = WaypointFrom - - --- Create a route point of type air. - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + Height -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) - - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route(Route, 0) - end - -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua deleted file mode 100644 index 259c2009f..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ /dev/null @@ -1,404 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- === --- --- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl) --- --- === --- --- @module AI.AI_Cargo_Ship --- @image AI_Cargo_Dispatcher.JPG - --- @type AI_CARGO_SHIP --- @extends AI.AI_Cargo#AI_CARGO - ---- Brings a dynamic cargo handling capability for an AI naval group. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Naval ships can be utilized to transport cargo around the map following naval shipping lanes. --- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo loading. --- --- The module will automatically load cargo when the Ship is within boarding or loading radius. --- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of --- cargo and the specified boarding radius. --- --- ## Defending the Ship when enemies are nearby --- This is not supported for naval cargo because most tanks don't float. Protect your transports... --- --- ## Infantry or cargo **health**. --- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield. --- As a result, the unboarding cargo is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter. --- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for --- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance --- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat --- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting --- between warehouses. --- --- ## Control the Ships on the map. --- --- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission --- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module. --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the --- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified --- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. --- --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_CARGO_SHIP -AI_CARGO_SHIP = { - ClassName = "AI_CARGO_SHIP", - Coordinate = nil -- Core.Point#COORDINATE -} - ---- Creates a new AI_CARGO_SHIP object. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP Ship The carrier Ship group --- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur. --- @param #table ShippingLane Table containing list of Shipping Lanes to be used --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane ) - - local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP - - self:AddTransition( "*", "Monitor", "*" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - self:AddTransition( "*", "Home", "*" ) - - self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0 - self:SetShippingLane ( ShippingLane ) - - self:SetCarrier( Ship ) - - return self -end - ---- Set the Carrier --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP CargoCarrier --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:SetCarrier( CargoCarrier ) - self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP - self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self ) - - CargoCarrier:HandleEvent( EVENTS.Dead ) - - function CargoCarrier:OnEventDead( EventData ) - self:F({"dead"}) - local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- Better hope they can swim! - AICargoTroops:Destroyed() - end - end - end - - self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) - self.Coalition = self.CargoCarrier:GetCoalition() - - self:SetControllable( CargoCarrier ) - - return self -end - - ---- FInd a free Carrier within a radius --- @param #AI_CARGO_SHIP self --- @param Core.Point#COORDINATE Coordinate --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttributes( "Trucks" ) then - return NearUnit:GetGroup() - end - end - end - - return nil -end - -function AI_CARGO_SHIP:SetShippingLane( ShippingLane ) - self.ShippingLane = ShippingLane - - return self -end - -function AI_CARGO_SHIP:SetCombatRadius( CombatRadius ) - self.CombatRadius = CombatRadius or 0 - - return self -end - - ---- Follow Infantry to the Carrier --- @param #AI_CARGO_SHIP self --- @param #AI_CARGO_SHIP Me --- @param Wrapper.Unit#UNIT ShipUnit --- @param Cargo.CargoGroup#CARGO_GROUP Cargo --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) - - local InfantryGroup = CargoGroup:GetGroup() - - self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) - - if ShipUnit:IsAlive() then - -- Check if the Cargo is near the CargoCarrier - if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then - - -- Cargo does not need to navigate to Carrier - Me:Guard() - else - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - if InfantryGroup:IsAlive() then - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - local Waypoints = {} - - -- Calculate new route - local FromCoord = InfantryGroup:GetCoordinate() - local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) - self:F({FromGround=FromGround}) - table.insert( Waypoints, FromGround ) - - local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) - local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) - self:F({ToGround=ToGround}) - table.insert( Waypoints, ToGround ) - - local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup ) - - self:F({Waypoints=Waypoints}) - local Waypoint = Waypoints[#Waypoints] - InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone - - InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details - end - end - end -end - - -function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To ) - self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } ) - - if self.CombatRadius > 0 then - -- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0. - -- This is to avoid Unloading the Ship in the middle of the sea. - if Ship and Ship:IsAlive() then - if self.CarrierCoordinate then - if self:IsTransporting() == true then - local Coordinate = Ship:GetCoordinate() - if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then - self.Zone:Scan( { Object.Category.UNIT } ) - if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - if self:Is( "Unloaded" ) then - -- There are no enemies within combat radius. Reload the CargoCarrier. - self:Reload() - end - else - if self:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! - else - if self:Is( "Unloaded" ) then - --self:Follow() - end - self:F( "I am here" .. self:GetCurrentState() ) - if self:Is( "Following" ) then - for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT - if Cargo:IsAlive() then - if not Cargo:IsNear( ShipUnit, 40 ) then - ShipUnit:RouteStop() - self.CarrierStopped = true - else - if self.CarrierStopped then - if Cargo:IsNear( ShipUnit, 25 ) then - ShipUnit:RouteResume() - self.CarrierStopped = nil - end - end - end - end - end - end - end - end - end - end - end - self.CarrierCoordinate = Ship:GetCoordinate() - end - self:__Monitor( -5 ) - end -end - ---- Check if cargo ship is alive and trigger Load event --- @param Wrapper.Group#Group Ship --- @param #AI_CARGO_SHIP self -function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) - - Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } ) - - if Ship:IsAlive() then - self:Load( PickupZone ) - end -end - ---- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad --- @param Wrapper.Group#GROUP Ship --- @param #AI_CARGO_SHIP self -function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone ) - Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } ) - - if Ship:IsAlive() then - self:Unload( DeployZone ) - end -end - ---- on after Pickup event. --- @param AI_CARGO_SHIP Ship --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point --- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit --- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided -function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if Ship and Ship:IsAlive() then - AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) - self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) - end -end - ---- On after Deploy event. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP SHIP --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point --- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit --- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if Ship and Ship:IsAlive() then - - Speed = Speed or Ship:GetSpeedMax()*0.8 - local lane = self.ShippingLane - - if lane then - local Waypoints = {} - - for i=1, #lane do - local coord = lane[i] - local Waypoint = coord:WaypointGround(_speed) - table.insert(Waypoints, Waypoint) - end - - local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone ) - local Waypoint = Waypoints[#Waypoints] - Ship:SetTaskWaypoint( Waypoint, TaskFunction ) - Ship:Route(Waypoints, 1) - self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) - else - self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") - end - end -end - ---- On after Unload event. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP Ship --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend ) - self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } ) - - local UnboardInterval = 5 - local UnboardDelay = 5 - - if Ship and Ship:IsAlive() then - for _, ShipUnit in pairs( Ship:GetUnits() ) do - local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT - Ship:RouteStop() - for _, Cargo in pairs( ShipUnit:GetCargo() ) do - self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } ) - if Cargo:IsLoaded() then - local unboardCoord = DeployZone:GetRandomPointVec2() - Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000) - UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval - self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend ) - if not Defend == true then - Cargo:SetDeployed( true ) - end - end - end - end - end -end - -function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone ) - if Ship and Ship:IsAlive() then - - self.RouteHome = true - Speed = Speed or Ship:GetSpeedMax()*0.8 - local lane = self.ShippingLane - - if lane then - local Waypoints = {} - - -- Need to find a more generalized way to do this instead of reversing the shipping lane. - -- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1 - for i=#lane, 1, -1 do - local coord = lane[i] - local Waypoint = coord:WaypointGround(_speed) - table.insert(Waypoints, Waypoint) - end - - local Waypoint = Waypoints[#Waypoints] - Ship:Route(Waypoints, 1) - - else - self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") - end - end -end diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua deleted file mode 100644 index c32312f27..000000000 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ /dev/null @@ -1,2193 +0,0 @@ ---- **AI** - Taking the lead of AI escorting your flight or of other AI. --- --- === --- --- ## Features: --- --- * Escort navigation commands. --- * Escort hold at position commands. --- * Escorts reporting detected targets. --- * Escorts scanning targets in advance. --- * Escorts attacking specific targets. --- * Request assistance from other groups for attack. --- * Manage rule of engagement of escorts. --- * Manage the allowed evasion techniques of escorts. --- * Make escort to execute a defined mission or path. --- * Escort tactical situation reporting. --- --- === --- --- ## Missions: --- --- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort) --- --- === --- --- Allows you to interact with escorting AI on your flight and take the lead. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. --- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. --- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. --- --- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. --- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. --- --- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. --- In this way, you can spread out the escorts over the battle field before a coordinated attack. --- --- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. --- --- ## Leading the flight --- --- When leading the flight, you are expected to guide the escorts towards the target areas, --- and carefully coordinate the attack based on the threat levels reported, and the available weapons --- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. --- --- ## Following the flight --- --- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. --- You as a player, are following the escorts, and are commanding them to progress the mission while --- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets --- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target --- type. Once the attack is finished, the escort will resume the mission it was assigned. --- In other words, you can use the escorts for reconnaissance, and for guiding the attack. --- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are --- following. You are in control. The ka-50s detect targets, report them, and you command how the attack --- will commence and from where. You can control where the escorts are holding position and which targets --- are attacked first. You are in control how the ka-50s will follow their mission path. --- --- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. --- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the --- attack is well coordinated. --- --- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism --- it brings in your DCS world simulations. --- --- # RADIO MENUs that can be created: --- --- Find a summary below of the current available commands: --- --- ## Navigation ...: --- --- Escort group navigation functions: --- --- * **"Join-Up":** The escort group fill follow you in the assigned formation. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- ## Hold position ...: --- --- Escort group navigation functions: --- --- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. --- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. --- --- ## Report targets ...: --- --- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- ## Attack targets ...: --- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- This menu will be available in Flight menu or in each Escort menu. --- --- ## Scan targets ...: --- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- ## Request assistance from ...: --- --- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other ground based escorts supporting the current escort. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ## ROE ...: --- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- ## Evasion ...: --- --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- ## Resume Mission ...: --- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- === --- --- ### Authors: **FlightControl** --- --- === --- --- @module AI.AI_Escort --- @image Escorting.JPG - - ---- --- @type AI_ESCORT --- @extends AI.AI_Formation#AI_FORMATION - - --- TODO: Add the menus when the class Start method is activated. --- TODO: Remove the menus when the class Stop method is called. - ---- AI_ESCORT class --- --- # AI_ESCORT construction methods. --- --- Create a new AI_ESCORT object with the @{#AI_ESCORT.New} method: --- --- * @{#AI_ESCORT.New}: Creates a new AI_ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort". --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- EscortPlanes:MenusAirplanes() -- create menus for airplanes --- EscortPlanes:__Start(2) --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- @field #AI_ESCORT -AI_ESCORT = { - ClassName = "AI_ESCORT", - EscortName = nil, -- The Escort Name - EscortUnit = nil, - EscortGroup = nil, - EscortMode = 1, - Targets = {}, -- The identified targets - FollowScheduler = nil, - ReportTargets = true, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - SmokeDirectionVector = false, - TaskPoints = {} -} - --- @field Functional.Detection#DETECTION_AREAS -AI_ESCORT.Detection = nil - ---- AI_ESCORT class constructor for an AI group --- @param #AI_ESCORT self --- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. --- @param Core.Set#SET_GROUP EscortGroupSet The set of group AI escorting the EscortUnit. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT self --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort". --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- EscortPlanes:MenusAirplanes() -- create menus for airplanes --- EscortPlanes:__Start(2) --- --- -function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, AI_FORMATION:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT - self:F( { EscortUnit, EscortGroupSet } ) - - self.PlayerUnit = self.FollowUnit -- Wrapper.Unit#UNIT - self.PlayerGroup = self.FollowUnit:GetGroup() -- Wrapper.Group#GROUP - - self.EscortName = EscortName - self.EscortGroupSet = EscortGroupSet - - self.EscortGroupSet:SetSomeIteratorLimit( 8 ) - - self.EscortBriefing = EscortBriefing - - self.Menu = {} - self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {} - self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {} - self.Menu.Flare = self.Menu.Flare or {} - self.Menu.Smoke = self.Menu.Smoke or {} - self.Menu.Targets = self.Menu.Targets or {} - self.Menu.ROE = self.Menu.ROE or {} - self.Menu.ROT = self.Menu.ROT or {} - --- if not EscortBriefing then --- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. --- "We're escorting your flight. " .. --- "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", --- 60, EscortUnit --- ) --- else --- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, --- 60, EscortUnit --- ) --- end - - self.FollowDistance = 100 - self.CT1 = 0 - self.GT1 = 0 - - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - -- Set EscortGroup known at EscortUnit. - if not self.PlayerUnit._EscortGroups then - self.PlayerUnit._EscortGroups = {} - end - - if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()] then - self.PlayerUnit._EscortGroups[EscortGroup:GetName()] = {} - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup = EscortGroup - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection = self.Detection - end - end - ) - - self:SetFlightReportType( self.__Enum.ReportType.All ) - - return self -end - - -function AI_ESCORT:_InitFlightMenus() - - self:SetFlightMenuJoinUp() - self:SetFlightMenuFormation( "Trail" ) - self:SetFlightMenuFormation( "Stack" ) - self:SetFlightMenuFormation( "LeftLine" ) - self:SetFlightMenuFormation( "RightLine" ) - self:SetFlightMenuFormation( "LeftWing" ) - self:SetFlightMenuFormation( "RightWing" ) - self:SetFlightMenuFormation( "Vic" ) - self:SetFlightMenuFormation( "Box" ) - - self:SetFlightMenuHoldAtEscortPosition() - self:SetFlightMenuHoldAtLeaderPosition() - - self:SetFlightMenuFlare() - self:SetFlightMenuSmoke() - - self:SetFlightMenuROE() - self:SetFlightMenuROT() - - self:SetFlightMenuTargets() - self:SetFlightMenuReportType() - -end - -function AI_ESCORT:_InitEscortMenus( EscortGroup ) - - EscortGroup.EscortMenu = MENU_GROUP:New( self.PlayerGroup, EscortGroup:GetCallsign(), self.MainMenu ) - - self:SetEscortMenuJoinUp( EscortGroup ) - self:SetEscortMenuResumeMission( EscortGroup ) - - self:SetEscortMenuHoldAtEscortPosition( EscortGroup ) - self:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) - - self:SetEscortMenuFlare( EscortGroup ) - self:SetEscortMenuSmoke( EscortGroup ) - - self:SetEscortMenuROE( EscortGroup ) - self:SetEscortMenuROT( EscortGroup ) - - self:SetEscortMenuTargets( EscortGroup ) - -end - -function AI_ESCORT:_InitEscortRoute( EscortGroup ) - - EscortGroup.MissionRoute = EscortGroup:GetTaskRoute() - -end - - --- @param #AI_ESCORT self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT:onafterStart( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - -- TODO:Revise this... - local LeaderEscort = EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - if LeaderEscort then - local Report = REPORT:New( "Escort reporting:" ) - Report:Add( "Joining Up " .. EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.PlayerUnit ) ) - LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) - end - - self.Detection = DETECTION_AREAS:New( EscortGroupSet, 5000 ) - - -- This only makes the escort report detections made by the escort, not through DLINK. - -- These must be enquired using other facilities. - -- In this way, the escort will report the target areas that are relevant for the mission. - self.Detection:InitDetectVisual( true ) - self.Detection:InitDetectIRST( true ) - self.Detection:InitDetectOptical( true ) - self.Detection:InitDetectRadar( true ) - self.Detection:InitDetectRWR( true ) - - self.Detection:SetAcceptRange( 100000 ) - - self.Detection:__Start( 30 ) - - self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) - self.FlightMenu = MENU_GROUP:New( self.PlayerGroup, "Flight", self.MainMenu ) - - self:_InitFlightMenus() - - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - - self:_InitEscortMenus( EscortGroup ) - self:_InitEscortRoute( EscortGroup ) - - self:SetFlightModeFormation( EscortGroup ) - - -- @param #AI_ESCORT self - -- @param Core.Event#EVENTDATA EventData - function EscortGroup:OnEventDeadOrCrash( EventData ) - self:F( { "EventDead", EventData } ) - self.EscortMenu:Remove() - end - - EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash ) - EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash ) - - end - ) - - -end - --- @param #AI_ESCORT self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT:onafterStop( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - self.Detection:Stop() - - self.MainMenu:Remove() - -end - ---- Set a Detection method for the EscortUnit to be reported upon. --- Detection methods are based on the derived classes from DETECTION_BASE. --- @param #AI_ESCORT self --- @param Functional.Detection#DETECTION_AREAS Detection -function AI_ESCORT:SetDetection( Detection ) - - self.Detection = Detection - self.EscortGroup.Detection = self.Detection - self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection - - Detection:__Start( 1 ) - -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #AI_ESCORT self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. -function AI_ESCORT:TestSmokeDirectionVector( SmokeDirection ) - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false -end - - ---- Defines the default menus for helicopters. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenusHelicopters( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - self:F() - --- self:MenuScanForTargets( 100, 60 ) - - self.XStart = XStart or 50 - self.XSpace = XSpace or 50 - self.YStart = YStart or 50 - self.YSpace = YSpace or 50 - self.ZStart = ZStart or 50 - self.ZSpace = ZSpace or 50 - self.ZLevels = ZLevels or 10 - - self:MenuJoinUp() - self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) - self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) - self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) - self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) - - self:MenuHoldAtEscortPosition( 30 ) - self:MenuHoldAtEscortPosition( 100 ) - self:MenuHoldAtEscortPosition( 500 ) - self:MenuHoldAtLeaderPosition( 30, 500 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuROT() - - return self -end - - ---- Defines the default menus for airplanes. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenusAirplanes( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - self:F() - --- self:MenuScanForTargets( 100, 60 ) - - self.XStart = XStart or 50 - self.XSpace = XSpace or 50 - self.YStart = YStart or 50 - self.YSpace = YSpace or 50 - self.ZStart = ZStart or 50 - self.ZSpace = ZSpace or 50 - self.ZLevels = ZLevels or 10 - - self:MenuJoinUp() - self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) - self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) - self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) - self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) - - self:MenuHoldAtEscortPosition( 1000, 500 ) - self:MenuHoldAtLeaderPosition( 1000, 500 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuROT() - - return self -end - - -function AI_ESCORT:SetFlightMenuFormation( Formation ) - - local FormationID = "Formation" .. Formation - - local MenuFormation = self.Menu[FormationID] - - if MenuFormation then - local Arguments = MenuFormation.Arguments - --self:T({Arguments=unpack(Arguments)}) - local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu ) - local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, - function ( self, Formation, ... ) - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, self, Formation, Arguments ) - if EscortGroup:IsAir() then - self:E({FormationID=FormationID}) - self[FormationID]( self, unpack(Arguments) ) - end - end, self, Formation, Arguments - ) - end, self, Formation, Arguments - ) - end - - return self -end - - -function AI_ESCORT:MenuFormation( Formation, ... ) - - local FormationID = "Formation"..Formation - self.Menu[FormationID] = self.Menu[FormationID] or {} - self.Menu[FormationID].Arguments = arg - -end - - ---- Defines a menu slot to let the escort to join in a trail formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) - - self:MenuFormation( "Trail", XStart, XSpace, YStart ) - - return self -end - ---- Defines a menu slot to let the escort to join in a stacked formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace ) - - self:MenuFormation( "Stack", XStart, XSpace, YStart, YSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a leFt wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationLeftLine( XStart, YStart, ZStart, ZSpace ) - - self:MenuFormation( "LeftLine", XStart, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a right line formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationRightLine( XStart, YStart, ZStart, ZSpace ) - - self:MenuFormation( "RightLine", XStart, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a left wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationLeftWing( XStart, XSpace, YStart, ZStart, ZSpace ) - - self:MenuFormation( "LeftWing", XStart, XSpace, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a right wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationRightWing( XStart, XSpace, YStart, ZStart, ZSpace ) - - self:MenuFormation( "RightWing", XStart, XSpace, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a center wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationCenterWing( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - self:MenuFormation( "CenterWing", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a vic formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationVic( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - self:MenuFormation( "Vic", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a box formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationBox( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - - self:MenuFormation( "Box", XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - - return self -end - -function AI_ESCORT:SetFlightMenuJoinUp() - - if self.Menu.JoinUp == true then - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", FlightMenuReportNavigation, AI_ESCORT._FlightJoinUp, self ) - end - -end - - ---- Sets a menu slot to join formation for an escort. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:SetEscortMenuJoinUp( EscortGroup ) - - if self.Menu.JoinUp == true then - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", EscortMenuReportNavigation, AI_ESCORT._JoinUp, self, EscortGroup ) - end - end -end - - - ---- Defines --- Defines a menu slot to let the escort to join formation. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuJoinUp() - - self.Menu.JoinUp = true - - return self -end - - -function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() - - for _, MenuHoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {} ) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - - local FlightMenuHoldPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuHoldAtEscortPosition.MenuText, - FlightMenuReportNavigation, - AI_ESCORT._FlightHoldPosition, - self, - nil, - MenuHoldAtEscortPosition.Height, - MenuHoldAtEscortPosition.Speed - ) - - end - return self -end - -function AI_ESCORT:SetEscortMenuHoldAtEscortPosition( EscortGroup ) - - for _, HoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {}) do - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuHoldPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - HoldAtEscortPosition.MenuText, - EscortMenuReportNavigation, - AI_ESCORT._HoldPosition, - self, - EscortGroup, - EscortGroup, - HoldAtEscortPosition.Height, - HoldAtEscortPosition.Speed - ) - end - end - - return self -end - - ---- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Hold position**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Speed Optional parameter that lets the escort orbit with a specified speed. The default value is a speed that is average for the type of airplane or helicopter. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) - self:F( { Height, Speed, MenuTextFormat } ) - - if not Height then - Height = 30 - end - - if not Speed then - Speed = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Speed == 0 then - MenuText = string.format( "Hold at %d meter", Height ) - else - MenuText = string.format( "Hold at %d meter at %d", Height, Speed ) - end - else - if Speed == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Speed ) - end - end - - self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {} - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1] = {} - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height = Height - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed = Speed - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText = MenuText - - return self -end - - -function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() - - for _, MenuHoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - - local FlightMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuHoldAtLeaderPosition.MenuText, - FlightMenuReportNavigation, - AI_ESCORT._FlightHoldPosition, - self, - self.PlayerGroup, - MenuHoldAtLeaderPosition.Height, - MenuHoldAtLeaderPosition.Speed - ) - end - - return self -end - -function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) - - for _, HoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - - local EscortMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - HoldAtLeaderPosition.MenuText, - EscortMenuReportNavigation, - AI_ESCORT._HoldPosition, - self, - self.PlayerGroup, - EscortGroup, - HoldAtLeaderPosition.Height, - HoldAtLeaderPosition.Speed - ) - end - end - - return self -end - ---- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Navigation**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Speed Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) - self:F( { Height, Speed, MenuTextFormat } ) - - if not Height then - Height = 30 - end - - if not Speed then - Speed = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Speed == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter at %d", Height, Speed ) - end - else - if Speed == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Speed ) - end - end - - self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {} - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1] = {} - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height = Height - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed = Speed - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText = MenuText - - return self -end - ---- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. --- This menu will appear under **Scan targets**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuScan then - self.EscortMenuScan = MENU_GROUP:New( self.PlayerGroup, "Scan for targets", self.EscortMenu ) - end - - if not Height then - Height = 100 - end - - if not Seconds then - Seconds = 30 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "At %d meter", Height ) - else - MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuScanForTargets then - self.EscortMenuScanForTargets = {} - end - - self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuText, - self.EscortMenuScan, - AI_ESCORT._ScanTargets, - self, - 30 - ) - end - - return self -end - - -function AI_ESCORT:SetFlightMenuFlare() - - for _, MenuFlare in pairs( self.Menu.Flare or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, FlightMenuReportNavigation ) - - local FlightMenuFlareGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Green, "Released a green flare!" ) - local FlightMenuFlareRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Red, "Released a red flare!" ) - local FlightMenuFlareWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.White, "Released a white flare!" ) - local FlightMenuFlareYellowFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - - return self -end - -function AI_ESCORT:SetEscortMenuFlare( EscortGroup ) - - for _, MenuFlare in pairs( self.Menu.Flare or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, EscortMenuReportNavigation ) - - local EscortMenuFlareGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Green, "Released a green flare!" ) - local EscortMenuFlareRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Red, "Released a red flare!" ) - local EscortMenuFlareWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.White, "Released a white flare!" ) - local EscortMenuFlareYellow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - end - - return self -end - - - ---- Defines a menu slot to let the escort disperse a flare in a certain color. --- This menu will appear under **Navigation**. --- The flare will be fired from the first unit in the group. --- @param #AI_ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuFlare( MenuTextFormat ) - self:F() - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - self.Menu.Flare = self.Menu.Flare or {} - self.Menu.Flare[#self.Menu.Flare+1] = {} - self.Menu.Flare[#self.Menu.Flare].MenuText = MenuText - - return self -end - - -function AI_ESCORT:SetFlightMenuSmoke() - - for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, FlightMenuReportNavigation ) - - local FlightMenuSmokeGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) - local FlightMenuSmokeRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) - local FlightMenuSmokeWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) - local FlightMenuSmokeOrangeFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - local FlightMenuSmokeBlueFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuSmoke( EscortGroup ) - - for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, EscortMenuReportNavigation ) - - local EscortMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Green, "Releasing green smoke!" ) - local EscortMenuSmokeRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Red, "Releasing red smoke!" ) - local EscortMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.White, "Releasing white smoke!" ) - local EscortMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - local EscortMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - end - - return self -end - - ---- Defines a menu slot to let the escort disperse a smoke in a certain color. --- This menu will appear under **Navigation**. --- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. --- The smoke will be fired from the first unit in the group. --- @param #AI_ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuSmoke( MenuTextFormat ) - self:F() - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - self.Menu.Smoke = self.Menu.Smoke or {} - self.Menu.Smoke[#self.Menu.Smoke+1] = {} - self.Menu.Smoke[#self.Menu.Smoke].MenuText = MenuText - - return self -end - -function AI_ESCORT:SetFlightMenuReportType() - - local FlightMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", self.FlightMenu ) - local MenuStamp = FlightMenuReportTargets:GetStamp() - - local FlightReportType = self:GetFlightReportType() - - if FlightReportType ~= self.__Enum.ReportType.All then - local FlightMenuReportTargetsAll = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report all targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeAll, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.Airborne then - local FlightMenuReportTargetsAirborne = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report airborne targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeAirborne, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.GroundRadar then - local FlightMenuReportTargetsGroundRadar = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report gound radar targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeGroundRadar, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.Ground then - local FlightMenuReportTargetsGround = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report ground targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeGround, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - FlightMenuReportTargets:RemoveSubMenus( MenuStamp, "ReportType" ) - -end - - -function AI_ESCORT:SetFlightMenuTargets() - - local FlightMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", self.FlightMenu ) - - -- Report Targets - local FlightMenuReportTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets now!", FlightMenuReportTargets, AI_ESCORT._FlightReportNearbyTargetsNow, self ) - local FlightMenuReportTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, true ) - local FlightMenuReportTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, false ) - - -- Attack Targets - self.FlightMenuAttack = MENU_GROUP:New( self.PlayerGroup, "Attack targets", self.FlightMenu ) - local FlightMenuAttackNearby = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self ):SetTag( "Attack" ) - local FlightMenuAttackNearbyAir = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest airborne targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Air ):SetTag( "Attack" ) - local FlightMenuAttackNearbyGround = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest ground targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Ground ):SetTag( "Attack" ) - - for _, MenuTargets in pairs( self.Menu.Targets or {}) do - MenuTargets.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, MenuTargets.Interval, MenuTargets.Interval ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuTargets( EscortGroup ) - - for _, MenuTargets in pairs( self.Menu.Targets or {} or {}) do - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - --local EscortMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu ) - - -- Report Targets - EscortGroup.EscortMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu, AI_ESCORT._ReportNearbyTargetsNow, self, EscortGroup, true ) - --EscortGroup.EscortMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, true ) - --EscortGroup.EscortMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, false ) - - -- Attack Targets - --local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) - - EscortGroup.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, { EscortGroup }, 1, MenuTargets.Interval ) - EscortGroup.ResumeScheduler = SCHEDULER:New( self, self._ResumeScheduler, { EscortGroup }, 1, 60 ) - end - end - - return self -end - - - ---- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. --- This menu will appear under **Report targets**. --- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. --- @param #AI_ESCORT self --- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #AI_ESCORT -function AI_ESCORT:MenuTargets( Seconds ) - self:F( { Seconds } ) - - if not Seconds then - Seconds = 30 - end - - self.Menu.Targets = self.Menu.Targets or {} - self.Menu.Targets[#self.Menu.Targets+1] = {} - self.Menu.Targets[#self.Menu.Targets].Interval = Seconds - - return self -end - ---- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. --- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuTargets. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuAssistedAttack() - self:F() - - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if not EscortGroup:IsAir() then - -- Request assistance from other escorts. - -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, "Request assistance from", EscortGroup.EscortMenu ) - end - end - ) - - return self -end - -function AI_ESCORT:SetFlightMenuROE() - - for _, MenuROE in pairs( self.Menu.ROE or {}) do - local FlightMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", self.FlightMenu ) - - local FlightMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", FlightMenuROE, AI_ESCORT._FlightROEHoldFire, self, "Holding weapons!" ) - local FlightMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", FlightMenuROE, AI_ESCORT._FlightROEReturnFire, self, "Returning fire!" ) - local FlightMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", FlightMenuROE, AI_ESCORT._FlightROEOpenFire, self, "Open fire at designated targets!" ) - local FlightMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", FlightMenuROE, AI_ESCORT._FlightROEWeaponFree, self, "Engaging all targets!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuROE( EscortGroup ) - - for _, MenuROE in pairs( self.Menu.ROE or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", EscortGroup.EscortMenu ) - - if EscortGroup:OptionROEHoldFirePossible() then - local EscortMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEHoldFire, "Holding weapons!" ) - end - if EscortGroup:OptionROEReturnFirePossible() then - local EscortMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEReturnFire, "Returning fire!" ) - end - if EscortGroup:OptionROEOpenFirePossible() then - EscortGroup.EscortMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEOpenFire, "Opening fire on designated targets!!" ) - end - if EscortGroup:OptionROEWeaponFreePossible() then - EscortGroup.EscortMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEWeaponFree, "Opening fire on targets of opportunity!" ) - end - end - end - - return self -end - - ---- Defines a menu to let the escort set its rules of engagement. --- All rules of engagement will appear under the menu **ROE**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuROE() - self:F() - - self.Menu.ROE = self.Menu.ROE or {} - self.Menu.ROE[#self.Menu.ROE+1] = {} - - return self -end - - -function AI_ESCORT:SetFlightMenuROT() - - for _, MenuROT in pairs( self.Menu.ROT or {}) do - local FlightMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", self.FlightMenu ) - - local FlightMenuROTNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", FlightMenuROT, AI_ESCORT._FlightROTNoReaction, self, "Fighting until death!" ) - local FlightMenuROTPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", FlightMenuROT, AI_ESCORT._FlightROTPassiveDefense, self, "Defending using jammers, chaff and flares!" ) - local FlightMenuROTEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", FlightMenuROT, AI_ESCORT._FlightROTEvadeFire, self, "Evading on enemy fire!" ) - local FlightMenuROTVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", FlightMenuROT, AI_ESCORT._FlightROTVertical, self, "Evading on enemy fire with vertical manoeuvres!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuROT( EscortGroup ) - - for _, MenuROT in pairs( self.Menu.ROT or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", EscortGroup.EscortMenu ) - - if not EscortGroup.EscortMenuEvasion then - -- Reaction to Threats - if EscortGroup:OptionROTNoReactionPossible() then - local EscortMenuEvasionNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTNoReaction, "Fighting until death!" ) - end - if EscortGroup:OptionROTPassiveDefensePossible() then - local EscortMenuEvasionPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTPassiveDefense, "Defending using jammers, chaff and flares!" ) - end - if EscortGroup:OptionROTEvadeFirePossible() then - local EscortMenuEvasionEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTEvadeFire, "Evading on enemy fire!" ) - end - if EscortGroup:OptionROTVerticalPossible() then - local EscortMenuOptionEvasionVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTVertical, "Evading on enemy fire with vertical manoeuvres!" ) - end - end - end - end - - return self -end - - - ---- Defines a menu to let the escort set its evasion when under threat. --- All rules of engagement will appear under the menu **Evasion**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuROT( MenuTextFormat ) - self:F( MenuTextFormat ) - - self.Menu.ROT = self.Menu.ROT or {} - self.Menu.ROT[#self.Menu.ROT+1] = {} - - return self -end - ---- Defines a menu to let the escort resume its mission from a waypoint on its route. --- All rules of engagement will appear under the menu **Resume mission from**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:SetEscortMenuResumeMission( EscortGroup ) - self:F() - - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - EscortGroup.EscortMenuResumeMission = MENU_GROUP:New( self.PlayerGroup, "Resume from", EscortGroup.EscortMenu ) - end - - return self -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP OrbitGroup --- @param Wrapper.Group#GROUP EscortGroup --- @param #number OrbitHeight --- @param #number OrbitSeconds -function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) - - local EscortUnit = self.PlayerUnit - - local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - - self:SetFlightModeMission( EscortGroup ) - - local PointFrom = {} - local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() - PointFrom = {} - PointFrom.x = GroupVec3.x - PointFrom.y = GroupVec3.z - PointFrom.speed = 250 - PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupVec3.y - PointFrom.alt_type = AI.Task.AltitudeType.BARO - - local OrbitPoint = OrbitUnit:GetVec2() - local PointTo = {} - PointTo.x = OrbitPoint.x - PointTo.y = OrbitPoint.y - PointTo.speed = 250 - PointTo.type = AI.Task.WaypointType.TURNING_POINT - PointTo.alt = OrbitHeight - PointTo.alt_type = AI.Task.AltitudeType.BARO - PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) - - local Points = { PointFrom, PointTo } - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - EscortGroup:SetTask( EscortGroup:TaskRoute( Points ), 1 ) - EscortGroup:MessageTypeToGroup( "Orbiting at current location.", MESSAGE.Type.Information, EscortUnit:GetGroup() ) - -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP OrbitGroup --- @param #number OrbitHeight --- @param #number OrbitSeconds -function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - - local EscortUnit = self.PlayerUnit - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, OrbitGroup ) - if EscortGroup:IsAir() then - if OrbitGroup == nil then - OrbitGroup = EscortGroup - end - self:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) - end - end, OrbitGroup - ) - -end - - - -function AI_ESCORT:_JoinUp( EscortGroup ) - - local EscortUnit = self.PlayerUnit - - self:SetFlightModeFormation( EscortGroup ) - - EscortGroup:MessageTypeToGroup( "Joining up!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - - -function AI_ESCORT:_FlightJoinUp() - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_JoinUp( EscortGroup ) - end - end - ) - -end - - ---- Lets the escort to join in a trail formation. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_ESCORT -function AI_ESCORT:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) - - self:FormationTrail( XStart, XSpace, YStart ) - -end - - -function AI_ESCORT:_FlightFormationTrail( XStart, XSpace, YStart ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) - end - end - ) - -end - ---- Lets the escort to join in a stacked formation. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) - - self:FormationStack( XStart, XSpace, YStart, YSpace ) - -end - - -function AI_ESCORT:_FlightFormationStack( XStart, XSpace, YStart, YSpace ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) - end - end - ) - -end - - -function AI_ESCORT:_Flare( EscortGroup, Color, Message ) - - local EscortUnit = self.PlayerUnit - - EscortGroup:GetUnit(1):Flare( Color ) - EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - - -function AI_ESCORT:_FlightFlare( Color, Message ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_Flare( EscortGroup, Color, Message ) - end - end - ) - -end - - - -function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) - - local EscortUnit = self.PlayerUnit - - EscortGroup:GetUnit(1):Smoke( Color ) - EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - -function AI_ESCORT:_FlightSmoke( Color, Message ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_Smoke( EscortGroup, Color, Message ) - end - end - ) - -end - - -function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) - - local EscortUnit = self.PlayerUnit - - self:_ReportTargetsScheduler( EscortGroup ) - -end - - -function AI_ESCORT:_FlightReportNearbyTargetsNow() - - self:_FlightReportTargetsScheduler() - -end - - - -function AI_ESCORT:_FlightSwitchReportNearbyTargets( ReportTargets ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortSwitchReportNearbyTargets( EscortGroup, ReportTargets ) - end - end - ) - -end - -function AI_ESCORT:SetFlightReportType( ReportType ) - - self.FlightReportType = ReportType - -end - -function AI_ESCORT:GetFlightReportType() - - return self.FlightReportType - -end - -function AI_ESCORT:_FlightSwitchReportTypeAll() - - self:SetFlightReportType( self.__Enum.ReportType.All ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting all targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeAirborne() - - self:SetFlightReportType( self.__Enum.ReportType.Airborne ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting airborne targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeGroundRadar() - - self:SetFlightReportType( self.__Enum.ReportType.Ground ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting ground radar targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeGround() - - self:SetFlightReportType( self.__Enum.ReportType.Ground ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting ground targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - - -function AI_ESCORT:_ScanTargets( ScanDuration ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - local EscortUnit = self.PlayerUnit - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsHelicopter() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - elseif EscortGroup:IsAirPlane() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - end - - EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortUnit ) - - if self.EscortMode == AI_ESCORT.MODE.FOLLOW then - self.FollowScheduler:Start( self.FollowSchedule ) - end - -end - --- @param Wrapper.Group#GROUP EscortGroup --- @param #AI_ESCORT self -function AI_ESCORT.___Resume( EscortGroup, self ) - - self:F( { self=self } ) - - local PlayerGroup = self.PlayerGroup - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTVertical() - - EscortGroup:SetState( EscortGroup, "Mode", EscortGroup:GetState( EscortGroup, "PreviousMode" ) ) - - if EscortGroup:GetState( EscortGroup, "Mode" ) == self.__Enum.Mode.Mission then - EscortGroup:MessageTypeToGroup( "Resuming route.", MESSAGE.Type.Information, PlayerGroup ) - else - EscortGroup:MessageTypeToGroup( "Rejoining formation.", MESSAGE.Type.Information, PlayerGroup ) - end - -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup --- @param #number WayPoint -function AI_ESCORT:_ResumeMission( EscortGroup, WayPoint ) - - --self.FollowScheduler:Stop( self.FollowSchedule ) - - self:SetFlightModeMission( EscortGroup ) - - local WayPoints = EscortGroup.MissionRoute - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - EscortGroup:SetTask( EscortGroup:TaskRoute( WayPoints ), 1 ) - - EscortGroup:MessageTypeToGroup( "Resuming mission from waypoint ", MESSAGE.Type.Information, self.PlayerGroup ) -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. --- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) - - self:F( EscortGroup ) - - self:SetFlightModeAttack( EscortGroup ) - - if EscortGroup:IsAir() then - EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTVertical() - EscortGroup:SetState( EscortGroup, "Escort", self ) - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - local AttackUnitTasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - AttackUnitTasks[#AttackUnitTasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroup:TaskCombo( AttackUnitTasks ) - Tasks[#Tasks+1] = EscortGroup:TaskFunction( "AI_ESCORT.___Resume", self ) - - EscortGroup:PushTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - else - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:PushTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - end - - local DetectedTargetsReport = REPORT:New( "Engaging target:\n" ) - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text(), MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightAttackTarget( DetectedItem ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, DetectedItem ) - if EscortGroup:IsAir() then - self:_AttackTarget( EscortGroup, DetectedItem ) - end - end, DetectedItem - ) - -end - - -function AI_ESCORT:_FlightAttackNearestTarget( TargetType ) - - self.Detection:Detect() - self:_FlightReportTargetsScheduler() - - local EscortGroup = self.EscortGroupSet:GetFirst() - local AttackDetectedItem = nil - local DetectedItems = self.Detection:GetDetectedItems() - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - if ( TargetType and TargetType == self.__Enum.ReportType.Ground and HasGround ) or - ( TargetType and TargetType == self.__Enum.ReportType.Air and HasAir ) or - ( TargetType == nil ) then - AttackDetectedItem = DetectedItem - break - end - end - - if AttackDetectedItem then - self:_FlightAttackTarget( AttackDetectedItem ) - else - EscortGroup:MessageTypeToGroup( "Nothing to attack!", MESSAGE.Type.Information, self.PlayerGroup ) - end - -end - - ---- --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. --- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) - - local EscortUnit = self.PlayerUnit - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - - EscortGroup:MessageTypeToGroup( "Assisting attack!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) - -end - -function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) - pcall( function() EscortROEFunction( EscortGroup ) end ) - EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightROEHoldFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEHoldFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEOpenFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEOpenFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEReturnFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEReturnFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEWeaponFree( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEWeaponFree, EscortROEMessage ) - end - ) -end - - -function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) - pcall( function() EscortROTFunction( EscortGroup ) end ) - EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightROTNoReaction( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTNoReaction, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTPassiveDefense( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTPassiveDefense, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTEvadeFire( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTEvadeFire, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTVertical( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTVertical, EscortROTMessage ) - end - ) -end - ---- Registers the waypoints --- @param #AI_ESCORT self --- @return #table -function AI_ESCORT:RegisterRoute() - self:F() - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - - local TaskPoints = EscortGroup:GetTaskRoute() - - self:T( TaskPoints ) - - return TaskPoints -end - ---- Resume Scheduler. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_ResumeScheduler( EscortGroup ) - self:F( EscortGroup:GetName() ) - - if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then - - - local EscortGroupName = EscortGroup:GetCallsign() - - if EscortGroup.EscortMenuResumeMission then - EscortGroup.EscortMenuResumeMission:RemoveSubMenus() - - local TaskPoints = EscortGroup.MissionRoute - - for WayPointID, WayPoint in pairs( TaskPoints ) do - local EscortVec3 = EscortGroup:GetVec3() - local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + - ( WayPoint.y - EscortVec3.z )^2 - ) ^ 0.5 / 1000 - MENU_GROUP_COMMAND:New( self.PlayerGroup, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", EscortGroup.EscortMenuResumeMission, AI_ESCORT._ResumeMission, self, EscortGroup, WayPointID ) - end - end - end -end - - ---- Measure distance between coordinate player and coordinate detected item. --- @param #AI_ESCORT self -function AI_ESCORT:Distance( PlayerUnit, DetectedItem ) - - local DetectedCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - local PlayerCoordinate = PlayerUnit:GetCoordinate() - - return DetectedCoordinate:Get3DDistance( PlayerCoordinate ) - -end - ---- Report Targets Scheduler. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_ReportTargetsScheduler( EscortGroup, Report ) - self:F( EscortGroup:GetName() ) - - if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then - - local EscortGroupName = EscortGroup:GetCallsign() - - local DetectedTargetsReport = REPORT:New( "Reporting targets:\n" ) -- A new report to display the detected targets as a message to the player. - - - if EscortGroup.EscortMenuTargetAssistance then - EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() - end - - local DetectedItems = self.Detection:GetDetectedItems() - - local ClientEscortTargets = self.Detection - - local TimeUpdate = timer.getTime() - - local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) - - local DetectedTargets = false - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - --for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasGroundRadar = HasGround and DetectedItemSet:HasRadar() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - - if ( FlightReportType == self.__Enum.ReportType.All ) or - ( FlightReportType == self.__Enum.ReportType.Airborne and HasAir ) or - ( FlightReportType == self.__Enum.ReportType.Ground and HasGround ) or - ( FlightReportType == self.__Enum.ReportType.GroundRadar and HasGroundRadar ) then - - DetectedTargets = true - - local DetectedMenu = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ):Text("\n") - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - - if EscortGroup:IsAir() then - - MENU_GROUP_COMMAND:New( self.PlayerGroup, - DetectedMenu, - EscortMenuAttackTargets, - AI_ESCORT._AttackTarget, - self, - EscortGroup, - DetectedItem - ):SetTag( "Escort" ):SetTime( TimeUpdate ) - else - if self.EscortMenuTargetAssistance then - local MenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) - MENU_GROUP_COMMAND:New( self.PlayerGroup, - DetectedMenu, - MenuTargetAssistance, - AI_ESCORT._AssistTarget, - self, - EscortGroup, - DetectedItem - ) - end - end - end - end - - EscortMenuAttackTargets:RemoveSubMenus( TimeUpdate, "Escort" ) - - if Report then - if DetectedTargets then - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) - else - EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) - end - end - - return true - end - - return false -end - ---- Report Targets Scheduler for the flight. The report is generated from the perspective of the player plane, and is reported by the first plane in the formation set. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_FlightReportTargetsScheduler() - - self:F("FlightReportTargetScheduler") - - local EscortGroup = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - - local DetectedTargetsReport = REPORT:New( "Reporting your targets:\n" ) -- A new report to display the detected targets as a message to the player. - - if EscortGroup and ( self.PlayerUnit:IsAlive() and EscortGroup:IsAlive() ) then - - local TimeUpdate = timer.getTime() - - local DetectedItems = self.Detection:GetDetectedItems() - - local DetectedTargets = false - - local ClientEscortTargets = self.Detection - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - - self:F("FlightReportTargetScheduler Targets") - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasGroundRadar = HasGround and DetectedItemSet:HasRadar() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - - if ( FlightReportType == self.__Enum.ReportType.All ) or - ( FlightReportType == self.__Enum.ReportType.Airborne and HasAir ) or - ( FlightReportType == self.__Enum.ReportType.Ground and HasGround ) or - ( FlightReportType == self.__Enum.ReportType.GroundRadar and HasGroundRadar ) then - - - DetectedTargets = true -- There are detected targets, when the content of the for loop is executed. We use it to display a message. - - local DetectedItemReportMenu = self.Detection:DetectedItemReportMenu( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportMenuText = DetectedItemReportMenu:Text(", ") - - MENU_GROUP_COMMAND:New( self.PlayerGroup, - ReportMenuText, - self.FlightMenuAttack, - AI_ESCORT._FlightAttackTarget, - self, - DetectedItem - ):SetTag( "Flight" ):SetTime( TimeUpdate ) - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - end - end - - self.FlightMenuAttack:RemoveSubMenus( TimeUpdate, "Flight" ) - - if DetectedTargets then - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) --- else --- EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) - end - - return true - end - - return false -end diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua deleted file mode 100644 index d2366568f..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ /dev/null @@ -1,192 +0,0 @@ ---- **AI** - Models the automatic assignment of AI escorts to player flights. --- --- ## Features: --- -- --- * Provides the facilities to trigger escorts when players join flight slots. --- * --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Dispatcher --- @image MOOSE.JPG - - --- @type AI_ESCORT_DISPATCHER --- @extends Core.Fsm#FSM - - ---- Models the automatic assignment of AI escorts to player flights. --- --- # Developer Note --- --- ![Banner Image](..\Images\deprecated.png) --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_ESCORT_DISPATCHER -AI_ESCORT_DISPATCHER = { - ClassName = "AI_ESCORT_DISPATCHER", -} - --- @field #list -AI_ESCORT_DISPATCHER.AI_Escorts = {} - - ---- Creates a new AI_ESCORT_DISPATCHER object. --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. --- @param #string EscortName Name of the escort, which will also be the name of the escort menu. --- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_DISPATCHER --- @usage --- --- -- Create a new escort when a player joins an SU-25T plane. --- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined. --- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart() --- --- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot. --- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 ) --- --- -- Create an airbase object, where the escorts will be spawned. --- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya ) --- --- -- Park the airplanes at the airbase, visible before start. --- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig ) --- --- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase. --- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group. --- -- And a briefing to appear when the player joins the player slot. --- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." ) --- --- -- The dispatcher needs to be started using the :Start() method. --- Red_SU25T_EscortDispatcher:Start() -function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER - - self.CarrierSet = CarrierSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - end - - return self -end - -function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To ) - - self:HandleEvent( EVENTS.Birth ) - - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit ) - self:HandleEvent( EVENTS.Crash, self.OnEventExit ) - self:HandleEvent( EVENTS.Dead, self.OnEventExit ) - -end - --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER:OnEventExit( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - self:T({EscortAirbase= self.EscortAirbase } ) - self:T({PlayerGroupName = PlayerGroupName } ) - self:T({PlayerGroup = PlayerGroup}) - self:T({FirstGroup = self.CarrierSet:GetFirst()}) - self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if self.AI_Escorts[PlayerGroupName] then - self.AI_Escorts[PlayerGroupName]:Stop() - self.AI_Escorts[PlayerGroupName] = nil - end - end - -end - --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - self:T({EscortAirbase= self.EscortAirbase } ) - self:T({PlayerGroupName = PlayerGroupName } ) - self:T({PlayerGroup = PlayerGroup}) - self:T({FirstGroup = self.CarrierSet:GetFirst()}) - self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if not self.AI_Escorts[PlayerGroupName] then - local LeaderUnit = PlayerUnit - local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) - self:T({EscortGroup = EscortGroup}) - - self:ScheduleOnce( 1, - function( EscortGroup ) - local EscortSet = SET_GROUP:New() - EscortSet:AddGroup( EscortGroup ) - self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing ) - self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) - if EscortGroup:IsHelicopter() then - self.AI_Escorts[PlayerGroupName]:MenusHelicopters() - else - self.AI_Escorts[PlayerGroupName]:MenusAirplanes() - end - self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) - end, EscortGroup - ) - end - end - -end - - ---- Start Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] Start --- @param #AI_ESCORT_DISPATCHER self - ---- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] __Start --- @param #AI_ESCORT_DISPATCHER self --- @param #number Delay - ---- Stop Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] Stop --- @param #AI_ESCORT_DISPATCHER self - ---- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] __Stop --- @param #AI_ESCORT_DISPATCHER self --- @param #number Delay - - - - - - diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua deleted file mode 100644 index 1b0370b54..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ /dev/null @@ -1,153 +0,0 @@ ---- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu. --- --- ## Features: --- --- * Provides the facilities to trigger escorts when players join flight units. --- * Provide a menu for which escorts can be requested. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Dispatcher_Request --- @image MOOSE.JPG - - --- @type AI_ESCORT_DISPATCHER_REQUEST --- @extends Core.Fsm#FSM - - ---- Models the assignment of AI escorts to player flights upon request using the radio menu. --- --- ![Banner Image](..\Images\deprecated.png) --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_ESCORT_DISPATCHER_REQUEST -AI_ESCORT_DISPATCHER_REQUEST = { - ClassName = "AI_ESCORT_DISPATCHER_REQUEST", -} - --- @field #list -AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {} - - ---- Creates a new AI_ESCORT_DISPATCHER_REQUEST object. --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. --- @param #string EscortName Name of the escort, which will also be the name of the escort menu. --- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_DISPATCHER_REQUEST -function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST - - self.CarrierSet = CarrierSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - end - - return self -end - -function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To ) - - self:HandleEvent( EVENTS.Birth ) - - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit ) - self:HandleEvent( EVENTS.Crash, self.OnEventExit ) - self:HandleEvent( EVENTS.Dead, self.OnEventExit ) - -end - --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if self.AI_Escorts[PlayerGroupName] then - self.AI_Escorts[PlayerGroupName]:Stop() - self.AI_Escorts[PlayerGroupName] = nil - end - end - -end - --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if not self.AI_Escorts[PlayerGroupName] then - local LeaderUnit = PlayerUnit - self:ScheduleOnce( 0.1, - function() - self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing ) - self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) - if PlayerGroup:IsHelicopter() then - self.AI_Escorts[PlayerGroupName]:MenusHelicopters() - else - self.AI_Escorts[PlayerGroupName]:MenusAirplanes() - end - self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) - end - ) - end - end - -end - - ---- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start --- @param #AI_ESCORT_DISPATCHER_REQUEST self - ---- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param #number Delay - ---- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop --- @param #AI_ESCORT_DISPATCHER_REQUEST self - ---- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param #number Delay - - - - - - diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua deleted file mode 100644 index 843f75160..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ /dev/null @@ -1,323 +0,0 @@ ---- **AI** - Taking the lead of AI escorting your flight or of other AI, upon request using the menu. --- --- === --- --- ## Features: --- --- * Escort navigation commands. --- * Escort hold at position commands. --- * Escorts reporting detected targets. --- * Escorts scanning targets in advance. --- * Escorts attacking specific targets. --- * Request assistance from other groups for attack. --- * Manage rule of engagement of escorts. --- * Manage the allowed evasion techniques of escorts. --- * Make escort to execute a defined mission or path. --- * Escort tactical situation reporting. --- --- === --- --- ## Missions: --- --- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort) --- --- === --- --- Allows you to interact with escorting AI on your flight and take the lead. --- --- ![Banner Image](..\Images\deprecated.png) --- --- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. --- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. --- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. --- --- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. --- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. --- --- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. --- In this way, you can spread out the escorts over the battle field before a coordinated attack. --- --- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. --- --- ## Leading the flight --- --- When leading the flight, you are expected to guide the escorts towards the target areas, --- and carefully coordinate the attack based on the threat levels reported, and the available weapons --- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. --- --- ## Following the flight --- --- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. --- You as a player, are following the escorts, and are commanding them to progress the mission while --- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets --- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target --- type. Once the attack is finished, the escort will resume the mission it was assigned. --- In other words, you can use the escorts for reconnaissance, and for guiding the attack. --- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are --- following. You are in control. The ka-50s detect targets, report them, and you command how the attack --- will commence and from where. You can control where the escorts are holding position and which targets --- are attacked first. You are in control how the ka-50s will follow their mission path. --- --- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. --- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the --- attack is well coordinated. --- --- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism --- it brings in your DCS world simulations. --- --- # RADIO MENUs that can be created: --- --- Find a summary below of the current available commands: --- --- ## Navigation ...: --- --- Escort group navigation functions: --- --- * **"Join-Up":** The escort group fill follow you in the assigned formation. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- ## Hold position ...: --- --- Escort group navigation functions: --- --- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. --- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. --- --- ## Report targets ...: --- --- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- ## Attack targets ...: --- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- This menu will be available in Flight menu or in each Escort menu. --- --- ## Scan targets ...: --- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- ## Request assistance from ...: --- --- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other ground based escorts supporting the current escort. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ## ROE ...: --- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- ## Evasion ...: --- --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- ## Resume Mission ...: --- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. - -- - -- # Developer Note - -- - -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE - -- Therefore, this class is considered to be deprecated - -- --- === --- --- ### Authors: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Request --- @image Escorting.JPG - - - --- @type AI_ESCORT_REQUEST --- @extends AI.AI_Escort#AI_ESCORT - ---- AI_ESCORT_REQUEST class --- --- # AI_ESCORT_REQUEST construction methods. --- --- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method: --- --- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- --- @field #AI_ESCORT_REQUEST -AI_ESCORT_REQUEST = { - ClassName = "AI_ESCORT_REQUEST", -} - ---- AI_ESCORT_REQUEST.Mode class --- @type AI_ESCORT_REQUEST.MODE --- @field #number FOLLOW --- @field #number MISSION - ---- MENUPARAM type --- @type MENUPARAM --- @field #AI_ESCORT_REQUEST ParamSelf --- @field #Distance ParamDistance --- @field #function ParamFunction --- @field #string ParamMessage - ---- AI_ESCORT_REQUEST class constructor for an AI group --- @param #AI_ESCORT_REQUEST self --- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_REQUEST --- @usage --- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 ) --- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig ) --- --- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" ) --- --- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" ) --- Escort:FormationTrail( 50, 100, 100 ) --- Escort:Menus() --- Escort:__Start( 5 ) -function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes() - local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST - - self.EscortGroupSet = EscortGroupSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - - self.LeaderGroup = self.PlayerUnit:GetGroup() - - self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 ) - self.Detection:__Start( 30 ) - - self.SpawnMode = self.__Enum.Mode.Mission - - return self -end - --- @param #AI_ESCORT_REQUEST self -function AI_ESCORT_REQUEST:SpawnEscort() - - local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) - - self:ScheduleOnce( 0.1, - function( EscortGroup ) - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEHoldFire() - - self.EscortGroupSet:AddGroup( EscortGroup ) - - local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - local Report = REPORT:New() - Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) ) - LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) - - self:SetFlightModeFormation( EscortGroup ) - self:FormationTrail() - - self:_InitFlightMenus() - self:_InitEscortMenus( EscortGroup ) - self:_InitEscortRoute( EscortGroup ) - - -- @param #AI_ESCORT self - -- @param Core.Event#EVENTDATA EventData - function EscortGroup:OnEventDeadOrCrash( EventData ) - self:F( { "EventDead", EventData } ) - self.EscortMenu:Remove() - end - - EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash ) - EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash ) - - end, EscortGroup - ) - -end - --- @param #AI_ESCORT_REQUEST self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) - - self:F() - - if not self.MenuRequestEscort then - self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) - self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu, - function() - self:SpawnEscort() - end - ) - end - - self:GetParent( self ).onafterStart( self, EscortGroupSet ) - - self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash ) - -end - --- @param #AI_ESCORT_REQUEST self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - self.Detection:Stop() - - self.MainMenu:Remove() - -end - ---- Set the spawn mode to be mission execution. --- @param #AI_ESCORT_REQUEST self -function AI_ESCORT_REQUEST:SetEscortSpawnMission() - - self.SpawnMode = self.__Enum.Mode.Mission - -end diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua deleted file mode 100644 index 425585e90..000000000 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ /dev/null @@ -1,1281 +0,0 @@ ---- **AI** - Build large airborne formations of aircraft. --- --- **Features:** --- --- * Build in-air formations consisting of more than 40 aircraft as one group. --- * Build different formation types. --- * Assign a group leader that will guide the large formation path. --- --- === --- --- ## Additional Material: --- --- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Formation) --- * **YouTube videos:** [Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0bFIJ9jIdYM22uaWmIN4oz) --- * **Guides:** None --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module AI.AI_Formation --- @image AI_Large_Formations.JPG - ---- AI_FORMATION class --- @type AI_FORMATION --- @extends Core.Fsm#FSM_SET --- @field Wrapper.Unit#UNIT FollowUnit --- @field Core.Set#SET_GROUP FollowGroupSet --- @field #string FollowName --- @field #AI_FORMATION.MODE FollowMode The mode the escort is in. --- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. --- @field #number dtFollow Time step between position updates. - - ---- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. --- --- ![Banner Image](..\Images\deprecated.png) --- --- AI_FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions. --- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!! --- The purpose of the class is to: --- --- * Make formation building a process that can be managed while in flight, rather than a task. --- * Human players can guide formations, consisting of larget planes. --- * Build large formations (like a large bomber field). --- * Form formations that DCS does not support off the shelve. --- --- A few remarks: --- --- * Depending on the type of plane, the change in direction by the leader may result in the formation getting disentangled while in flight and needs to be rebuild. --- * Formations are vulnerable to collissions, but is depending on the type of plane, the distance between the planes and the speed and angle executed by the leader. --- * Formations may take a while to build up. --- --- As a result, the AI_FORMATION is not perfect, but is very useful to: --- --- * Model large formations when flying straight line. You can build close formations when doing this. --- * Make humans guide a large formation, when the planes are wide from each other. --- --- ## AI_FORMATION construction --- --- Create a new SPAWN object with the @{#AI_FORMATION.New} method: --- --- * @{#AI_FORMATION.New}(): Creates a new AI_FORMATION object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT} or a @{Wrapper.Unit#UNIT}, with an optional briefing text. --- --- ## Formation methods --- --- The following methods can be used to set or change the formation: --- --- * @{#AI_FORMATION.FormationLine}(): Form a line formation (core formation function). --- * @{#AI_FORMATION.FormationTrail}(): Form a trail formation. --- * @{#AI_FORMATION.FormationLeftLine}(): Form a left line formation. --- * @{#AI_FORMATION.FormationRightLine}(): Form a right line formation. --- * @{#AI_FORMATION.FormationRightWing}(): Form a right wing formation. --- * @{#AI_FORMATION.FormationLeftWing}(): Form a left wing formation. --- * @{#AI_FORMATION.FormationCenterWing}(): Form a center wing formation. --- * @{#AI_FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing. --- * @{#AI_FORMATION.FormationCenterBoxed}(): Form a center boxed formation. --- --- ## Randomization --- --- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters. --- --- @usage --- local FollowGroupSet = SET_GROUP:New():FilterCategories("plane"):FilterCoalitions("blue"):FilterPrefixes("Follow"):FilterStart() --- FollowGroupSet:Flush() --- local LeaderUnit = UNIT:FindByName( "Leader" ) --- local LargeFormation = AI_FORMATION:New( LeaderUnit, FollowGroupSet, "Center Wing Formation", "Briefing" ) --- LargeFormation:FormationCenterWing( 500, 50, 0, 250, 250 ) --- LargeFormation:__Start( 1 ) - -- - -- # Developer Note - -- - -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE - -- Therefore, this class is considered to be deprecated - -- --- @field #AI_FORMATION -AI_FORMATION = { - ClassName = "AI_FORMATION", - FollowName = nil, -- The Follow Name - FollowUnit = nil, - FollowGroupSet = nil, - FollowMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, - FollowScheduler = nil, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - dtFollow = 0.5, -} - -AI_FORMATION.__Enum = {} - --- @type AI_FORMATION.__Enum.Formation --- @field #number None --- @field #number Line --- @field #number Trail --- @field #number Stack --- @field #number LeftLine --- @field #number RightLine --- @field #number LeftWing --- @field #number RightWing --- @field #number Vic --- @field #number Box -AI_FORMATION.__Enum.Formation = { - None = 0, - Mission = 1, - Line = 2, - Trail = 3, - Stack = 4, - LeftLine = 5, - RightLine = 6, - LeftWing = 7, - RightWing = 8, - Vic = 9, - Box = 10, -} - --- @type AI_FORMATION.__Enum.Mode --- @field #number Mission --- @field #number Formation -AI_FORMATION.__Enum.Mode = { - Mission = "M", - Formation = "F", - Attack = "A", - Reconnaissance = "R", -} - --- @type AI_FORMATION.__Enum.ReportType --- @field #number All --- @field #number Airborne --- @field #number GroundRadar --- @field #number Ground -AI_FORMATION.__Enum.ReportType = { - All = "*", - Airborne = "A", - GroundRadar = "R", - Ground = "G", -} - ---- AI_FORMATION class constructor for an AI group --- @param #AI_FORMATION self --- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string FollowName Name of the escort. --- @param #string FollowBriefing Briefing. --- @return #AI_FORMATION self -function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1 - local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) - self:F( { FollowUnit, FollowGroupSet, FollowName } ) - - self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT - self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP - - self.FollowGroupSet:ForEachGroup( - function( FollowGroup ) - --self:E("Following") - FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation ) - end - ) - - self:SetFlightModeFormation() - - self:SetFlightRandomization( 2 ) - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - - self:AddTransition( {"None", "Stopped"}, "Start", "Following" ) - - self:AddTransition( "*", "FormationLine", "*" ) - --- FormationLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationTrail", "*" ) - --- FormationTrail Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationTrail - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @return #boolean - - --- FormationTrail Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationTrail - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - --- FormationTrail Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationTrail - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - --- FormationTrail Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationTrail - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - self:AddTransition( "*", "FormationStack", "*" ) - --- FormationStack Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationStack - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @return #boolean - - --- FormationStack Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationStack - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - --- FormationStack Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationStack - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - --- FormationStack Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationStack - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationLeftLine", "*" ) - --- FormationLeftLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLeftLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLeftLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLeftLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLeftLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLeftLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationRightLine", "*" ) - --- FormationRightLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationRightLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationRightLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationRightLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationRightLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationRightLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationLeftWing", "*" ) - --- FormationLeftWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLeftWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLeftWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLeftWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLeftWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLeftWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationRightWing", "*" ) - --- FormationRightWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationRightWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationRightWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationRightWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationRightWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationRightWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationCenterWing", "*" ) - --- FormationCenterWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationCenterWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationCenterWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationCenterWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationCenterWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationCenterWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationCenterWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationCenterWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationVic", "*" ) - --- FormationVic Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationVic - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationVic Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationVic - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationVic Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationVic - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationVic Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationVic - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationBox", "*" ) - --- FormationBox Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationBox - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - -- @return #boolean - - --- FormationBox Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationBox - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - --- FormationBox Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationBox - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - --- FormationBox Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationBox - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - - self:AddTransition( "*", "Follow", "Following" ) - - self:FormationLeftLine( 500, 0, 250, 250 ) - - self.FollowName = FollowName - self.FollowBriefing = FollowBriefing - - - self.CT1 = 0 - self.GT1 = 0 - - self.FollowMode = AI_FORMATION.MODE.MISSION - - return self -end - - ---- Set time interval between updates of the formation. --- @param #AI_FORMATION self --- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds. --- @return #AI_FORMATION -function AI_FORMATION:SetFollowTimeInterval(dt) --R2.1 - self.dtFollow=dt or 0.5 - return self -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #AI_FORMATION self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. --- @return #AI_FORMATION -function AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1 - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false - return self -end - ---- FormationLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1 - self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation } ) - - XStart = XStart or self.XStart - XSpace = XSpace or self.XSpace - YStart = YStart or self.YStart - YSpace = YSpace or self.YSpace - ZStart = ZStart or self.ZStart - ZSpace = ZSpace or self.ZSpace - - FollowGroupSet:Flush( self ) - - local FollowSet = FollowGroupSet:GetSet() - - local i = 1 --FF i=0 caused first unit to have no XSpace! Probably needs further adjustments. This is just a quick work around. - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = COORDINATE:New() - PointVec3:SetX( XStart + i * XSpace ) - PointVec3:SetY( YStart + i * YSpace ) - PointVec3:SetZ( ZStart + i * ZSpace ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - - FollowGroup:SetState( FollowGroup, "Formation", Formation ) - end - - return self - -end - ---- FormationTrail Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0, self.__Enum.Formation.Trail ) - - return self -end - - ---- FormationStack Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0, self.__Enum.Formation.Stack ) - - return self -end - - - - ---- FormationLeftLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace, self.__Enum.Formation.LeftLine ) - - return self -end - - ---- FormationRightLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) - - return self -end - - ---- FormationLeftWing Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) - - return self -end - - ---- FormationRightWing Handler OnAfter for AI_FORMATION --- @function [parent=#AI_FORMATION] OnAfterFormationRightWing --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) - - return self -end - - ---- FormationCenterWing Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 - - local FollowSet = FollowGroupSet:GetSet() - - local i = 0 - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = COORDINATE:New() - - local Side = ( i % 2 == 0 ) and 1 or -1 - local Row = i / 2 + 1 - - PointVec3:SetX( XStart + Row * XSpace ) - PointVec3:SetY( YStart ) - PointVec3:SetZ( Side * ( ZStart + i * ZSpace ) ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Vic ) - end - - return self -end - - ---- FormationVic Handle for AI_FORMATION --- @param #AI_FORMATION self --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 - - self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) - - return self -end - ---- FormationBox Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1 - - local FollowSet = FollowGroupSet:GetSet() - - local i = 0 - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = COORDINATE:New() - - local ZIndex = i % ZLevels - local XIndex = math.floor( i / ZLevels ) - local YIndex = math.floor( i / ZLevels ) - - PointVec3:SetX( XStart + XIndex * XSpace ) - PointVec3:SetY( YStart + YIndex * YSpace ) - PointVec3:SetZ( -ZStart - (ZSpace * ZLevels / 2 ) + ZSpace * ZIndex ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Box ) - end - - return self -end - - ---- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation. --- @param #AI_FORMATION self --- @param #number FlightRandomization The formation flying errors that pilots can make while in formation. Is a range set in meters. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 - - self.FlightRandomization = FlightRandomization - - return self -end - - ---- Gets your escorts to flight mode. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:GetFlightMode( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - end - - - return FollowGroup:GetState( FollowGroup, "Mode" ) -end - - - ---- This sets your escorts to fly a mission. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeMission( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - end - ) - end - - - return self -end - - ---- This sets your escorts to execute an attack. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeAttack( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) - end - ) - end - - - return self -end - - ---- This sets your escorts to fly in a formation. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeFormation( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) - end - ) - end - - return self -end - - - - ---- Stop function. Formation will not be updated any more. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1 - self:E("Stopping formation.") -end - ---- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 - if From=="Stopped" then - return false -- Deny transition. - end - return true -end - ---- Enter following state. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 - - if self.FollowUnit:IsAlive() then - - local ClientUnit = self.FollowUnit - - local CT1, CT2, CV1, CV2 - CT1 = ClientUnit:GetState( self, "CT1" ) - - local CuVec3=ClientUnit:GetVec3() - - if CT1 == nil or CT1 == 0 then - ClientUnit:SetState( self, "CV1", CuVec3) - ClientUnit:SetState( self, "CT1", timer.getTime() ) - else - CT1 = ClientUnit:GetState( self, "CT1" ) - CT2 = timer.getTime() - CV1 = ClientUnit:GetState( self, "CV1" ) - CV2 = CuVec3 - - ClientUnit:SetState( self, "CT1", CT2 ) - ClientUnit:SetState( self, "CV1", CV2 ) - end - - --FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2) - - for _,_group in pairs(FollowGroupSet:GetSet()) do - local group=_group --Wrapper.Group#GROUP - if group and group:IsAlive() then - self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2) - end - end - - self:__Follow( -self.dtFollow ) - end - -end - - ---- Follow me. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup Follow group. --- @param Wrapper.Unit#UNIT ClientUnit Client Unit. --- @param DCS#Time CT1 Time --- @param DCS#Vec3 CV1 Vec3 --- @param DCS#Time CT2 Time --- @param DCS#Vec3 CV2 Vec3 -function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) - - if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then - - self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) - - FollowGroup:OptionROTEvadeFire() - FollowGroup:OptionROEReturnFire() - - local GroupUnit = FollowGroup:GetUnit( 1 ) - - local GuVec3=GroupUnit:GetVec3() - - local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) - - if FollowFormation then - local FollowDistance = FollowFormation.x - - local GT1 = GroupUnit:GetState( self, "GT1" ) - - if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then - GroupUnit:SetState( self, "GV1", GuVec3) - GroupUnit:SetState( self, "GT1", timer.getTime() ) - else - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 - - local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } - local Ca = math.atan2( CDv.x, CDv.z ) - - local GT1 = GroupUnit:GetState( self, "GT1" ) - local GT2 = timer.getTime() - - local GV1 = GroupUnit:GetState( self, "GV1" ) - local GV2 = GuVec3 - - --[[ - GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - ]] - - GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - - - GroupUnit:SetState( self, "GT1", GT2 ) - GroupUnit:SetState( self, "GV1", GV2 ) - - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - - -- Calculate the distance - local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } - local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) - local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T - local Position = math.cos( Alpha_R ) - local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 - local Distance = GD * Position + - CS * 0.5 - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.x, GV.z ) - - local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) - local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) - - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local Inclination = ( Distance + FollowFormation.x ) / 10 - if Inclination < -30 then - Inclination = - 30 - end - - local CVI = { - x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y + Inclination, -- + FollowFormation.y, - --y = GH2.y, - z = CV2.z + CS * 10 * math.cos(Ca), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } - - local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) - local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) - - local GDV_Formation = { - x = GDV.x - GVx, - y = GDV.y, - z = GDV.z - GVz - } - - -- Debug smoke. - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Green ) - trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) - end - - - - local Time = 120 - - local Speed = - ( Distance + FollowFormation.x ) / Time - - if Distance > -10000 then - Speed = - ( Distance + FollowFormation.x ) / 60 - end - - if Distance > -2500 then - Speed = - ( Distance + FollowFormation.x ) / 20 - end - - local GS = Speed + CS - - --self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) - - -- Now route the escort to the desired point with the desired speed. - FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) - - end - end - end -end diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua deleted file mode 100644 index e8d21ed44..000000000 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ /dev/null @@ -1,950 +0,0 @@ ---- **AI** - Perform Air Patrolling for airplanes. --- --- **Features:** --- --- * Patrol AI airplanes within a given zone. --- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel threshold to RTB on time. --- --- === --- --- AI PATROL classes makes AI Controllables execute an Patrol. --- --- There are the following types of PATROL classes defined: --- --- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Patrol) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **Pikey**: Testing and API concept review. --- --- === --- --- @module AI.AI_Patrol --- @image AI_Air_Patrolling.JPG - ---- AI_PATROL_ZONE class --- @type AI_PATROL_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @field DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @field DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @field Core.Spawn#SPAWN CoordTest --- @extends Core.Fsm#FSM_CONTROLLABLE - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) --- --- ## 1. AI_PATROL_ZONE constructor --- --- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object. --- --- ## 2. AI_PATROL_ZONE is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 2.1. AI_PATROL_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 2.2. AI_PATROL_ZONE Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set or Get the AI controllable --- --- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable. --- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable. --- --- ## 4. Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 5. Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targeted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this process in place. --- --- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE --- --- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. --- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this process in place. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_PATROL_ZONE -AI_PATROL_ZONE = { - ClassName = "AI_PATROL_ZONE", -} - ---- Creates a new AI_PATROL_ZONE object --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_PATROL_ZONE self --- @usage --- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "BARO" if not specified - self.PatrolAltType = PatrolAltType or "BARO" - - self:SetRefreshTimeInterval( 30 ) - - self.CheckStatus = true - - self:ManageFuel( .2, 60 ) - self:ManageDamage( 1 ) - - - self.DetectedUnits = {} -- This table contains the targets detected during patrol. - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnEnterStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnAfterStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] Stop --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] __Stop --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "None", "Start", "Patrolling" ) - ---- OnBefore Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnAfterStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] Start --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] __Start --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnAfterRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] Route --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] __Route --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnAfterStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] Status --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] __Status --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] Detect --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] __Detect --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] Detected --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] __Detected --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnAfterRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] RTB --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] __RTB --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnEnterReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_PATROL_ZONE self --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_PATROL_ZONE self --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. - ---- Set the detection on. The AI will detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOn() - self:F2() - - self.DetectOn = true -end - ---- Set the detection off. The AI will NOT detect for targets. --- However, the list of already detected targets will be kept and can be enquired! --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOff() - self:F2() - - self.DetectOn = false -end - ---- Set the status checking off. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - ---- Activate the detection. The AI will detect for targets if the Detection is switched On. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionActivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = true - self:__Detect( -self.DetectInterval ) -end - ---- Deactivate the detection. The AI will NOT detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionDeactivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = false -end - ---- Set the interval in seconds between each detection executed by the AI. --- The list of already detected targets will be kept and updated. --- Newly detected targets will be added, but already detected targets that were --- not detected in this cycle, will NOT be removed! --- The default interval is 30 seconds. --- @param #AI_PATROL_ZONE self --- @param #number Seconds The interval in seconds. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds ) - self:F2() - - if Seconds then - self.DetectInterval = Seconds - else - self.DetectInterval = 30 - end -end - ---- Set the detection zone where the AI is detecting targets. --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE DetectionZone The zone where to detect targets. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionZone( DetectionZone ) - self:F2() - - if DetectionZone then - self.DetectZone = DetectionZone - else - self.DetectZone = nil - end -end - ---- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI. --- No filtering is applied, so, ANY detected UNIT can be in this list. --- It is up to the mission designer to use the @{Wrapper.Unit} class and methods to filter the targets. --- @param #AI_PATROL_ZONE self --- @return #table The list of @{Wrapper.Unit#UNIT}s -function AI_PATROL_ZONE:GetDetectedUnits() - self:F2() - - return self.DetectedUnits -end - ---- Clears the list of @{Wrapper.Unit#UNIT}s that were detected by the AI. --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ClearDetectedUnits() - self:F2() - self.DetectedUnits = {} -end - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - return self -end - ---- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage threshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. --- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageThreshold = PatrolDamageThreshold - - return self -end - ---- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings. --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To ) - self:F2() - - self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live. - self:__Status( 60 ) -- Check status status every 30 seconds. - self:SetDetectionActivated() - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() - - self.Controllable:OnReSpawn( - function( PatrolGroup ) - self:T( "ReSpawn" ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) - - self:SetDetectionOn() - -end - - --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable+ -function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To ) - - return self.DetectOn and self.DetectActivated -end - --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To ) - - local Detected = false - - local DetectedTargets = Controllable:GetDetectedTargets() - for TargetID, Target in pairs( DetectedTargets or {} ) do - local TargetObject = Target.object - - if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then - - local TargetUnit = UNIT:Find( TargetObject ) - - -- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234 - if TargetUnit and TargetUnit:IsAlive() then - - local TargetUnitName = TargetUnit:GetName() - - if self.DetectionZone then - if TargetUnit:IsInZone( self.DetectionZone ) then - self:T( {"Detected ", TargetUnit } ) - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - else - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - - end - end - end - - self:__Detect( -self.DetectInterval ) - - if Detected == true then - self:__Detected( 1.5 ) - end - -end - --- @param Wrapper.Controllable#CONTROLLABLE AIControllable --- This static method is called from the route path within the last task at the last waypoint of the Controllable. --- Note that this method is required, as triggers the next route when patrolling for the Controllable. -function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable ) - - local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE - PatrolZone:__Route( 1 ) -end - - ---- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings. --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - local life = self.Controllable:GetLife() or 0 - if self.Controllable:IsAlive() and life > 1 then - -- Determine if the AIControllable is within the PatrolZone. - -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. - - local PatrolRoute = {} - - -- Calculate the current route point of the controllable as the start point of the route. - -- However, when the controllable is not in the air, - -- the controllable current waypoint is probably the airbase... - -- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies - -- immediately back to the airbase, and this is not correct. - -- Therefore, when on a runway, get as the current route point a random point within the PatrolZone. - -- This will make the plane fly immediately to the patrol zone. - - if self.Controllable:InAir() == false then - self:T( "Not in the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --Done: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TakeOffParking, - COORDINATE.WaypointAction.FromParkingArea, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - else - self:T( "In the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - end - - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "PatrolZone", self ) - self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - end - -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onbeforeStatus() - - return self.CheckStatus -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStatus() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local Fuel = self.Controllable:GetFuelMin() - if Fuel < self.PatrolFuelThresholdPercentage then - self:T( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) - local OldAIControllable = self.Controllable - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - RTB = true - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - if Damage <= self.PatrolDamageThreshold then - self:T( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) - RTB = true - end - - if self:IsInstanceOf("AI_CAS") or self:IsInstanceOf("AI_BAI") then - local atotal,shells,rockets,bombs,missiles = self.Controllable:GetAmmunition() - local arelevant = rockets+bombs - if arelevant == 0 or missiles == 0 then - RTB = true - self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles}) - self:T( self.Controllable:GetName() .. " is out of ammo, RTB!" ) - end - end - - if RTB == true then - self:RTB() - else - self:__Status( 60 ) -- Execute the Patrol event after 30 seconds. - end - end -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterRTB() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - self:SetDetectionOff() - self.CheckStatus = false - - local PatrolRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 1 ) - - end - -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterDead() - self:SetDetectionOff() - self:SetStatusOff() -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) - end - end -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) - end -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) - end -end