From c55732a02f0f1df051662de70e7049ad80207a0c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Nov 2024 12:49:36 +0100 Subject: [PATCH] Leaner new version --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 218 - .../Moose/AI/AI_A2A_Dispatcher.lua | 4518 ---------------- Moose Development/Moose/AI/AI_A2A_Gci.lua | 147 - Moose Development/Moose/AI/AI_A2A_Patrol.lua | 410 -- Moose Development/Moose/AI/AI_A2G_BAI.lua | 96 - Moose Development/Moose/AI/AI_A2G_CAS.lua | 96 - .../Moose/AI/AI_A2G_Dispatcher.lua | 4792 ----------------- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 131 - Moose Development/Moose/AI/AI_Air.lua | 840 --- .../Moose/AI/AI_Air_Dispatcher.lua | 3239 ----------- Moose Development/Moose/AI/AI_Air_Engage.lua | 603 --- Moose Development/Moose/AI/AI_Air_Patrol.lua | 391 -- .../Moose/AI/AI_Air_Squadron.lua | 294 - Moose Development/Moose/AI/AI_BAI.lua | 652 --- Moose Development/Moose/AI/AI_Balancer.lua | 314 -- Moose Development/Moose/AI/AI_CAP.lua | 541 -- Moose Development/Moose/AI/AI_CAS.lua | 570 -- Moose Development/Moose/AI/AI_Cargo.lua | 589 -- Moose Development/Moose/AI/AI_Cargo_APC.lua | 607 --- .../Moose/AI/AI_Cargo_Airplane.lua | 510 -- .../Moose/AI/AI_Cargo_Dispatcher.lua | 1238 ----- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 263 - .../Moose/AI/AI_Cargo_Dispatcher_Airplane.lua | 169 - .../AI/AI_Cargo_Dispatcher_Helicopter.lua | 199 - .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 198 - .../Moose/AI/AI_Cargo_Helicopter.lua | 661 --- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 402 -- Moose Development/Moose/AI/AI_Escort.lua | 2191 -------- .../Moose/AI/AI_Escort_Dispatcher.lua | 190 - .../Moose/AI/AI_Escort_Dispatcher_Request.lua | 151 - .../Moose/AI/AI_Escort_Request.lua | 321 -- Moose Development/Moose/AI/AI_Formation.lua | 1279 ----- Moose Development/Moose/AI/AI_Patrol.lua | 939 ---- .../Moose/Actions/Act_Account.lua | 310 -- .../Moose/Actions/Act_Assign.lua | 292 - .../Moose/Actions/Act_Assist.lua | 219 - Moose Development/Moose/Actions/Act_Route.lua | 483 -- Moose Development/Moose/Cargo/Cargo.lua | 1399 ----- Moose Development/Moose/Cargo/CargoCrate.lua | 337 -- Moose Development/Moose/Cargo/CargoGroup.lua | 773 --- .../Moose/Cargo/CargoSlingload.lua | 275 - Moose Development/Moose/Cargo/CargoUnit.lua | 395 -- Moose Development/Moose/Modules.lua | 55 - Moose Development/Moose/Modules_local.lua | 53 - .../Moose/Tasking/CommandCenter.lua | 821 --- .../Moose/Tasking/DetectionManager.lua | 402 -- Moose Development/Moose/Tasking/Mission.lua | 1211 ----- Moose Development/Moose/Tasking/Task.lua | 2058 ------- Moose Development/Moose/Tasking/TaskInfo.lua | 377 -- Moose Development/Moose/Tasking/Task_A2A.lua | 654 --- .../Moose/Tasking/Task_A2A_Dispatcher.lua | 620 --- Moose Development/Moose/Tasking/Task_A2G.lua | 635 --- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 828 --- .../Moose/Tasking/Task_CARGO.lua | 1410 ----- .../Moose/Tasking/Task_Capture_Dispatcher.lua | 402 -- .../Moose/Tasking/Task_Capture_Zone.lua | 334 -- .../Moose/Tasking/Task_Cargo_CSAR.lua | 398 -- .../Moose/Tasking/Task_Cargo_Dispatcher.lua | 919 ---- .../Moose/Tasking/Task_Cargo_Transport.lua | 363 -- .../Moose/Tasking/Task_Manager.lua | 192 - Moose Development/Moose/Utilities/STTS.lua | 259 - Moose Setup/Moose.files | 1 - 62 files changed, 44234 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 delete mode 100644 Moose Development/Moose/Actions/Act_Account.lua delete mode 100644 Moose Development/Moose/Actions/Act_Assign.lua delete mode 100644 Moose Development/Moose/Actions/Act_Assist.lua delete mode 100644 Moose Development/Moose/Actions/Act_Route.lua delete mode 100644 Moose Development/Moose/Cargo/Cargo.lua delete mode 100644 Moose Development/Moose/Cargo/CargoCrate.lua delete mode 100644 Moose Development/Moose/Cargo/CargoGroup.lua delete mode 100644 Moose Development/Moose/Cargo/CargoSlingload.lua delete mode 100644 Moose Development/Moose/Cargo/CargoUnit.lua delete mode 100644 Moose Development/Moose/Tasking/CommandCenter.lua delete mode 100644 Moose Development/Moose/Tasking/DetectionManager.lua delete mode 100644 Moose Development/Moose/Tasking/Mission.lua delete mode 100644 Moose Development/Moose/Tasking/Task.lua delete mode 100644 Moose Development/Moose/Tasking/TaskInfo.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2A.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2G.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_CARGO.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Capture_Zone.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_Transport.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Manager.lua delete mode 100644 Moose Development/Moose/Utilities/STTS.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 d86c20bbe..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ /dev/null @@ -1,218 +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. --- --- ![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 bbfaa868b..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ /dev/null @@ -1,4518 +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) --- --- === --- --- # 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 8f85f3cd2..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ /dev/null @@ -1,147 +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. --- --- 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 a9bfc8a41..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ /dev/null @@ -1,410 +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}. --- --- ![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 7a6a80fca..000000000 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ /dev/null @@ -1,96 +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. --- --- # 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 16c9cb976..000000000 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ /dev/null @@ -1,96 +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 --- --- 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 0396e4e9f..000000000 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ /dev/null @@ -1,4792 +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 --- --- 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 17f9f86f5..000000000 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ /dev/null @@ -1,131 +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. --- --- 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 5d2feda5e..000000000 --- a/Moose Development/Moose/AI/AI_Air.lua +++ /dev/null @@ -1,840 +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}. --- --- --- # 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, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - RTBSpeed, - true - ) - - --- Create a route point of type air. - local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) - - --- Create a route point of type air. NOT used! - local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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 9e5939aa0..000000000 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ /dev/null @@ -1,3239 +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 --- --- 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 6a8472ace..000000000 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ /dev/null @@ -1,603 +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. --- --- 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 TargetUnit = AttackSetUnit:GetRandomSurely() - local TargetCoord = nil - - if TargetUnit then - TargetUnit:GetPointVec3() - end - - 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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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 = nil - - local TargetUnit = AttackSetUnit:GetRandomSurely() - if TargetUnit then - TargetCoord=TargetUnit:GetPointVec3() - end - 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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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 9950740a8..000000000 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ /dev/null @@ -1,391 +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. --- --- ![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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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 6651a92a5..000000000 --- a/Moose Development/Moose/AI/AI_Air_Squadron.lua +++ /dev/null @@ -1,294 +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 --- --- 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 65fd78645..000000000 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ /dev/null @@ -1,652 +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}. --- --- 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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 6499a7fd5..000000000 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ /dev/null @@ -1,314 +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 - - ---- 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. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - --[[ - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - ]] - 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 f4cfe55bf..000000000 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ /dev/null @@ -1,541 +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. --- --- ![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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 7fb848d42..000000000 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ /dev/null @@ -1,570 +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}. --- 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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 0bd6ab9ea..000000000 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ /dev/null @@ -1,589 +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. --- --- 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 fc9037fea..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ /dev/null @@ -1,607 +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. --- --- 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 6666eb379..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ /dev/null @@ -1,510 +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. --- --- 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(POINT_VEC3.RoutePointAltType.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 71b7f9f43..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ /dev/null @@ -1,1238 +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) --- --- === --- --- # 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 3d98522e1..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ /dev/null @@ -1,263 +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. --- --- 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 d3a7c78ac..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ /dev/null @@ -1,169 +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. --- --- 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 b219c78b0..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ /dev/null @@ -1,199 +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. --- --- 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 6fc670e40..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ /dev/null @@ -1,198 +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. --- --- 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 cea586679..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ /dev/null @@ -1,661 +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. --- --- 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", --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.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", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.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 5639c52da..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ /dev/null @@ -1,402 +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. --- --- 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 ad325ed94..000000000 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ /dev/null @@ -1,2191 +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. --- --- 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 ff4c0ddfe..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ /dev/null @@ -1,190 +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 --- --- 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 160c2beed..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ /dev/null @@ -1,151 +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. --- --- # 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 3251d3717..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ /dev/null @@ -1,321 +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. --- --- 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 4b01217c3..000000000 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ /dev/null @@ -1,1279 +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. --- --- 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 = POINT_VEC3: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 = POINT_VEC3: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 = POINT_VEC3: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 d5ce61d72..000000000 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ /dev/null @@ -1,939 +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}. --- --- ![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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 - else - 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 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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua deleted file mode 100644 index a72a7b445..000000000 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ /dev/null @@ -1,310 +0,0 @@ ---- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs. --- --- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG) --- --- === --- --- @module Actions.Act_Account --- @image MOOSE.JPG - -do -- ACT_ACCOUNT - - --- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} - -- - -- ## ACT_ACCOUNT state machine: - -- - -- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. - -- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. - -- Each derived class follows exactly the same process, using the same events and following the same state transitions, - -- but will have **different implementation behaviour** upon each event or state transition. - -- - -- ### ACT_ACCOUNT States - -- - -- * **Assigned**: The player is assigned. - -- * **Waiting**: Waiting for an event. - -- * **Report**: Reporting. - -- * **Account**: Account for an event. - -- * **Accounted**: All events have been accounted for, end of the process. - -- * **Failed**: Failed the process. - -- - -- ### ACT_ACCOUNT Events - -- - -- * **Start**: Start the process. - -- * **Wait**: Wait for an event. - -- * **Report**: Report the status of the accounting. - -- * **Event**: An event happened, process the event. - -- * **More**: More targets. - -- * **NoMore (*)**: No more targets. - -- * **Fail (*)**: The action process has failed. - -- - -- (*) End states of the process. - -- - -- ### ACT_ACCOUNT state transition methods: - -- - -- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. - -- There are 2 moments when state transition methods will be called by the state machine: - -- - -- * **Before** the state transition. - -- The state transition method needs to start with the name **OnBefore + the name of the state**. - -- If the state transition method returns false, then the processing of the state transition will not be done! - -- If you want to change the behaviour of the AIControllable at this event, return false, - -- but then you'll need to specify your own logic using the AIControllable! - -- - -- * **After** the state transition. - -- The state transition method needs to start with the name **OnAfter + the name of the state**. - -- These state transition methods need to provide a return value, which is specified at the function description. - -- - -- # 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 - -- - -- @type ACT_ACCOUNT - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Core.Fsm#FSM_PROCESS - ACT_ACCOUNT = { - ClassName = "ACT_ACCOUNT", - TargetSetUnit = nil, - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT self - -- @return #ACT_ACCOUNT - function ACT_ACCOUNT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "Assigned", "Start", "Waiting" ) - self:AddTransition( "*", "Wait", "Waiting" ) - self:AddTransition( "*", "Report", "Report" ) - self:AddTransition( "*", "Event", "Account" ) - self:AddTransition( "Account", "Player", "AccountForPlayer" ) - self:AddTransition( "Account", "Other", "AccountForOther" ) - self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" ) - self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - - self:SetStartState( "Assigned" ) - - return self - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To ) - - self:HandleEvent( EVENTS.Dead, self.onfuncEventDead ) - self:HandleEvent( EVENTS.Crash, self.onfuncEventCrash ) - self:HandleEvent( EVENTS.Hit ) - - self:__Wait( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To ) - - if self.DisplayCount >= self.DisplayInterval then - self:Report() - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event ) - - self:__NoMore( 1 ) - end - -end -- ACT_ACCOUNT - -do -- ACT_ACCOUNT_DEADS - - --- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{#ACT_ACCOUNT} - -- - -- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. - -- The process is given a @{Core.Set} of units that will be tracked upon successful destruction. - -- The process will end after each target has been successfully destroyed. - -- Each successful dead will trigger an Account state transition that can be scored, modified or administered. - -- - -- - -- ## ACT_ACCOUNT_DEADS constructor: - -- - -- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. - -- - -- @type ACT_ACCOUNT_DEADS - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends #ACT_ACCOUNT - ACT_ACCOUNT_DEADS = { - ClassName = "ACT_ACCOUNT_DEADS", - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskName - function ACT_ACCOUNT_DEADS:New() - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - return self - end - - function ACT_ACCOUNT_DEADS:Init( FsmAccount ) - - self.Task = self:GetTask() - self.TaskName = self.Task:GetName() - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To ) - - local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then - local PlayerName = ProcessUnit:GetPlayerName() - local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName] - if PlayerHit == PlayerName then - self:Player( EventData ) - else - self:Other( EventData ) - end - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - local TaskGroup = ProcessUnit:GetGroup() - - Task.TargetSetUnit:Remove( EventData.IniUnitName ) - - local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - - local PlayerName = ProcessUnit:GetPlayerName() - Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 ) - - if Task.TargetSetUnit:Count() > 0 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - local TaskGroup = ProcessUnit:GetGroup() - Task.TargetSetUnit:Remove( EventData.IniUnitName ) - - local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - - if Task.TargetSetUnit:Count() > 0 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- DCS Events - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:OnEventHit( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniPlayerName and EventData.TgtDCSUnitName then - self.PlayerHits = self.PlayerHits or {} - self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName - end - end - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - - --- DCS Events - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - -end -- ACT_ACCOUNT DEADS diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua deleted file mode 100644 index 3b261cfb1..000000000 --- a/Moose Development/Moose/Actions/Act_Assign.lua +++ /dev/null @@ -1,292 +0,0 @@ ---- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. --- --- === --- --- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} --- --- ## ACT_ASSIGN state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIGN **Events**: --- --- These are the events defined in this class: --- --- * **Start**: Start the tasking acceptance process. --- * **Assign**: Assign the task. --- * **Reject**: Reject the task.. --- --- ### ACT_ASSIGN **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIGN **States**: --- --- * **UnAssigned**: The player has not accepted the task. --- * **Assigned (*)**: The player has accepted the task. --- * **Rejected (*)**: The player has not accepted the task. --- * **Waiting**: The process is awaiting player feedback. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIGN state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 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 --- --- === --- --- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN} --- --- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- ## 1.1) ACT_ASSIGN_ACCEPT constructor: --- --- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. --- --- === --- --- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN} --- --- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: --- ----------------------------------------- --- --- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. --- --- === --- --- @module Actions.Act_Assign --- @image MOOSE.JPG - - -do -- ACT_ASSIGN - - --- ACT_ASSIGN class - -- @type ACT_ASSIGN - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIGN = { - ClassName = "ACT_ASSIGN", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN self - -- @return #ACT_ASSIGN The task acceptance process. - function ACT_ASSIGN:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "UnAssigned", "Start", "Waiting" ) - self:AddTransition( "Waiting", "Assign", "Assigned" ) - self:AddTransition( "Waiting", "Reject", "Rejected" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Assigned" ) - self:AddEndState( "Rejected" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "UnAssigned" ) - - return self - end - -end -- ACT_ASSIGN - - - -do -- ACT_ASSIGN_ACCEPT - - --- ACT_ASSIGN_ACCEPT class - -- @type ACT_ASSIGN_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_ACCEPT = { - ClassName = "ACT_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN_ACCEPT self - -- @param #string TaskBriefing - function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) - - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) - - self.TaskBriefing = FsmAssign.TaskBriefing - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) - - self:__Assign( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup ) - - self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() ) - end - -end -- ACT_ASSIGN_ACCEPT - - -do -- ACT_ASSIGN_MENU_ACCEPT - - --- ACT_ASSIGN_MENU_ACCEPT class - -- @type ACT_ASSIGN_MENU_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_MENU_ACCEPT = { - ClassName = "ACT_ASSIGN_MENU_ACCEPT", - } - - --- Init. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing ) - - self.TaskBriefing = TaskBriefing - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) - - self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 ) - - local TaskGroup = ProcessUnit:GetGroup() - - self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup ) - - self:__Reject( 120, TaskGroup ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup ) - - self:__Assign( -1, TaskGroup ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup ) - - self:__Reject( -1, TaskGroup ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup ) - - self.Menu:Remove() - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup ) - self:F( { TaskGroup = TaskGroup } ) - - self.Menu:Remove() - --TODO: need to resolve this problem ... it has to do with the events ... - --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event - self.Task:RejectGroup( TaskGroup ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup ) - - --self.Task:AssignToGroup( TaskGroup ) - self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() ) - end - -end -- ACT_ASSIGN_MENU_ACCEPT diff --git a/Moose Development/Moose/Actions/Act_Assist.lua b/Moose Development/Moose/Actions/Act_Assist.lua deleted file mode 100644 index 2ae132ac1..000000000 --- a/Moose Development/Moose/Actions/Act_Assist.lua +++ /dev/null @@ -1,219 +0,0 @@ ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- ## ACT_ASSIST state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIST **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. --- * **Next**: The process is smoking the targets in the given zone. --- --- ### ACT_ASSIST **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIST **States**: --- --- * **None**: The controllable did not receive route commands. --- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. --- * **Smoking (*)**: The process is smoking the targets in the zone. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIST state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{#ACT_ASSIST} --- --- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Core.Zone}. --- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. --- At random intervals, a new target is smoked. --- --- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: --- --- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. --- --- # 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 --- --- === --- --- @module Actions.Act_Assist --- @image MOOSE.JPG - - -do -- ACT_ASSIST - - --- ACT_ASSIST class - -- @type ACT_ASSIST - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIST = { - ClassName = "ACT_ASSIST", - } - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST self - -- @return #ACT_ASSIST - function ACT_ASSIST:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "AwaitSmoke" ) - self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) - self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) - self:AddTransition( "*", "Stop", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - self:AddEndState( "Success" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To ) - - local ProcessGroup = ProcessUnit:GetGroup() - local MissionMenu = self:GetMission():GetMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:__Next( 1 ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To ) - - self.Menu:Remove() -- When stopped, remove the menus - end - -end - -do -- ACT_ASSIST_SMOKE_TARGETS_ZONE - - --- ACT_ASSIST_SMOKE_TARGETS_ZONE class - -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIST - ACT_ASSIST_SMOKE_TARGETS_ZONE = { - ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", - } - --- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() --- self:E("_Destructor") --- --- self.Menu:Remove() --- self:EventRemoveAll() --- end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) - - self.TargetSetUnit = FsmSmoke.TargetSetUnit - self.TargetZone = FsmSmoke.TargetZone - end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To ) - - self.TargetSetUnit:ForEachUnit( - -- @param Wrapper.Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua deleted file mode 100644 index b387b6584..000000000 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ /dev/null @@ -1,483 +0,0 @@ ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} --- --- ## ACT_ROUTE state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ROUTE **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Report**: The process is reporting to the player the route to be followed. --- * **Route**: The process is routing the controllable. --- * **Pause**: The process is pausing the route of the controllable. --- * **Arrive**: The controllable has arrived at a route point. --- * **More**: There are more route points that need to be followed. The process will go back into the Report state. --- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. --- --- ### ACT_ROUTE **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ROUTE **States**: --- --- * **None**: The controllable did not receive route commands. --- * **Arrived (*)**: The controllable has arrived at a route point. --- * **Aborted (*)**: The controllable has aborted the route path. --- * **Routing**: The controllable is understay to the route point. --- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. --- * **Success (*)**: All route points were reached. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ROUTE state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ROUTE_ZONE} class, extends @{#ACT_ROUTE} --- --- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Core.Zone}. --- The player receives on perioding times messages with the coordinates of the route to follow. --- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. --- --- # 1.1) ACT_ROUTE_ZONE constructor: --- --- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. --- --- # 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 --- --- === --- --- @module Actions.Act_Route --- @image MOOSE.JPG - - -do -- ACT_ROUTE - - --- ACT_ROUTE class - -- @type ACT_ROUTE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @field Core.Point#COORDINATE Coordinate - -- @extends Core.Fsm#FSM_PROCESS - ACT_ROUTE = { - ClassName = "ACT_ROUTE", - } - - - --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE self - function ACT_ROUTE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "*", "Reset", "None" ) - self:AddTransition( "None", "Start", "Routing" ) - self:AddTransition( "*", "Report", "*" ) - self:AddTransition( "Routing", "Route", "Routing" ) - self:AddTransition( "Routing", "Pause", "Pausing" ) - self:AddTransition( "Routing", "Arrive", "Arrived" ) - self:AddTransition( "*", "Cancel", "Cancelled" ) - self:AddTransition( "Arrived", "Success", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - self:AddTransition( "", "", "" ) - self:AddTransition( "", "", "" ) - - self:AddEndState( "Arrived" ) - self:AddEndState( "Failed" ) - self:AddEndState( "Cancelled" ) - - self:SetStartState( "None" ) - - self:SetRouteMode( "C" ) - - return self - end - - --- Set a Cancel Menu item. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE - function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag ) - - self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New( - MenuGroup, - MenuText, - ParentMenu, - self.MenuCancel, - self - ):SetTime( MenuTime ):SetTag( MenuTag ) - - ParentMenu:SetTime( MenuTime ) - - ParentMenu:Remove( MenuTime, MenuTag ) - - return self - end - - --- Set the route mode. - -- There are 2 route modes supported: - -- - -- * SetRouteMode( "B" ): Route mode is Bearing and Range. - -- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup. - -- - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE - function ACT_ROUTE:SetRouteMode( RouteMode ) - - self.RouteMode = RouteMode - - return self - end - - --- Get the routing text to be displayed. - -- The route mode determines the text displayed. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT Controllable - -- @return #string - function ACT_ROUTE:GetRouteText( Controllable ) - - local RouteText = "" - - local Coordinate = nil -- Core.Point#COORDINATE - - if self.Coordinate then - Coordinate = self.Coordinate - end - - if self.Zone then - Coordinate = self.Zone:GetPointVec3( self.Altitude ) - Coordinate:SetHeading( self.Heading ) - end - - - local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G). - local CC = self:GetTask():GetMission():GetCommandCenter() - if CC then - if CC:IsModeWWII() then - -- Find closest reference point to the target. - local ShortestDistance = 0 - local ShortestReferencePoint = nil - local ShortestReferenceName = "" - self:F( { CC.ReferencePoints } ) - for ZoneName, Zone in pairs( CC.ReferencePoints ) do - self:F( { ZoneName = ZoneName } ) - local Zone = Zone -- Core.Zone#ZONE - local ZoneCoord = Zone:GetCoordinate() - local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate ) - self:F( { ShortestDistance, ShortestReferenceName } ) - if ShortestDistance == 0 or ZoneDistance < ShortestDistance then - ShortestDistance = ZoneDistance - ShortestReferencePoint = ZoneCoord - ShortestReferenceName = CC.ReferenceNames[ZoneName] - end - end - if ShortestReferencePoint then - RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable ) - end - else - RouteText = Coordinate:ToString( Controllable, nil, Task ) - end - end - - return RouteText - end - - - function ACT_ROUTE:MenuCancel() - self:F("Cancelled") - self.CancelMenuGroupCommand:Remove() - self:__Cancel( 1 ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To ) - - - self:__Route( 1 ) - end - - --- Check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) - return false - end - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To ) - - if ProcessUnit:IsAlive() then - local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic - if self.DisplayCount >= self.DisplayInterval then - self:T( { HasArrived = HasArrived } ) - if not HasArrived then - self:Report() - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - if HasArrived then - self:__Arrive( 1 ) - else - self:__Route( 1 ) - end - - return HasArrived -- if false, then the event will not be executed... - end - - return false - - end - -end -- ACT_ROUTE - - -do -- ACT_ROUTE_POINT - - --- ACT_ROUTE_POINT class - -- @type ACT_ROUTE_POINT - -- @field Tasking.Task#TASK TASK - -- @extends #ACT_ROUTE - ACT_ROUTE_POINT = { - ClassName = "ACT_ROUTE_POINT", - } - - - --- Creates a new routing state machine. - -- The task will route a controllable to a Coordinate until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#COORDINATE The Coordinate to Target. - -- @param #number Range The Distance to Target. - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_POINT:New( Coordinate, Range ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT - - self.Coordinate = Coordinate - self.Range = Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - --- Creates a new routing state machine. - -- The task will route a controllable to a Coordinate until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - function ACT_ROUTE_POINT:Init( FsmRoute ) - - self.Coordinate = FsmRoute.Coordinate - self.Range = FsmRoute.Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self:SetStartState("None") - end - - --- Set Coordinate - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#COORDINATE Coordinate The Coordinate to route to. - function ACT_ROUTE_POINT:SetCoordinate( Coordinate ) - self:F2( { Coordinate } ) - self.Coordinate = Coordinate - end - - --- Get Coordinate - -- @param #ACT_ROUTE_POINT self - -- @return Core.Point#COORDINATE Coordinate The Coordinate to route to. - function ACT_ROUTE_POINT:GetCoordinate() - self:F2( { self.Coordinate } ) - return self.Coordinate - end - - --- Set Range around Coordinate - -- @param #ACT_ROUTE_POINT self - -- @param #number Range The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:SetRange( Range ) - self:F2( { Range } ) - self.Range = Range or 10000 - end - - --- Get Range around Coordinate - -- @param #ACT_ROUTE_POINT self - -- @return #number The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:GetRange() - self:F2( { self.Range } ) - return self.Range - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsAlive() then - local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() ) - - if Distance <= self.Range then - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived." - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - return true - end - end - - return false - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To ) - - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit ) - - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update ) - end - -end -- ACT_ROUTE_POINT - - -do -- ACT_ROUTE_ZONE - - --- ACT_ROUTE_ZONE class - -- @type ACT_ROUTE_ZONE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends #ACT_ROUTE - ACT_ROUTE_ZONE = { - ClassName = "ACT_ROUTE_ZONE", - } - - - --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_ZONE:New( Zone ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - - self.Zone = Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_ZONE:Init( FsmRoute ) - - self.Zone = FsmRoute.Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set Zone - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to. - -- @param #number Altitude - -- @param #number Heading - function ACT_ROUTE_ZONE:SetZone( Zone, Altitude, Heading ) -- R2.2 Added altitude and heading - self.Zone = Zone - self.Altitude = Altitude - self.Heading = Heading - end - - --- Get Zone - -- @param #ACT_ROUTE_ZONE self - -- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:GetZone() - return self.Zone - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsInZone( self.Zone ) then - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived within the zone." - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - end - - return ProcessUnit:IsInZone( self.Zone ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_ZONE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit = ProcessUnit } ) - - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit ) - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update ) - end - -end -- ACT_ROUTE_ZONE diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua deleted file mode 100644 index 8b7d6040e..000000000 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ /dev/null @@ -1,1399 +0,0 @@ ---- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers. --- --- === --- --- # 1) MOOSE Cargo System. --- --- #### Those who have used the mission editor, know that the DCS mission editor provides cargo facilities. --- However, these are merely static objects. Wouldn't it be nice if cargo could bring a new dynamism into your --- simulations? Where various objects of various types could be treated also as cargo? --- --- This is what MOOSE brings to you, a complete new cargo object model that used the cargo capabilities of --- DCS world, but enhances it. --- --- MOOSE Cargo introduces also a new concept, called a "carrier". These can be: --- --- - Helicopters --- - Planes --- - Ground Vehicles --- - Ships --- --- With the MOOSE Cargo system, you can: --- --- - Take full control of the cargo as objects within your script (see below). --- - Board/Unboard infantry into carriers. Also other objects can be boarded, like mortars. --- - Load/Unload dcs world cargo objects into carriers. --- - Load/Unload other static objects into carriers (like tires etc). --- - Slingload cargo objects. --- - Board units one by one... --- --- # 2) MOOSE Cargo Objects. --- --- 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. --- --- There are 4 types of MOOSE cargo objects possible, each represented by its own class: --- --- - @{Cargo.CargoGroup#CARGO_GROUP}: A MOOSE cargo that is represented by a DCS world GROUP object. --- - @{Cargo.CargoCrate#CARGO_CRATE}: A MOOSE cargo that is represented by a DCS world cargo object (static object). --- - @{Cargo.CargoUnit#CARGO_UNIT}: A MOOSE cargo that is represented by a DCS world unit object or static object. --- - @{Cargo.CargoSlingload#CARGO_SLINGLOAD}: A MOOSE cargo that is represented by a DCS world cargo object (static object), that can be slingloaded. --- --- Note that a CARGO crate is not meant to be slingloaded (it can, but it is not **meant** to be handled like that. --- Instead, a CARGO_CRATE is able to load itself into the bays of a carrier. --- --- Each of these MOOSE cargo objects behave in its own way, and have methods to be handled. --- --- local InfantryGroup = GROUP:FindByName( "Infantry" ) --- local InfantryCargo = CARGO_GROUP:New( InfantryGroup, "Engineers", "Infantry Engineers", 2000 ) --- local CargoCarrier = UNIT:FindByName( "Carrier" ) --- -- This call will make the Cargo run to the CargoCarrier. --- -- Upon arrival at the CargoCarrier, the Cargo will be Loaded into the Carrier. --- -- This process is now fully automated. --- InfantryCargo:Board( CargoCarrier, 25 ) --- --- The above would create a MOOSE cargo object called `InfantryCargo`, and using that object, --- you can board the cargo into the carrier `CargoCarrier`. --- Simple, isn't it? Told you, and this is only the beginning. --- --- The boarding, unboarding, loading, unloading of cargo is however something that is not meant to be coded manually by mission designers. --- It would be too low-level and not end-user friendly to deal with cargo handling complexity. --- Things can become really complex if you want to make cargo being handled and behave in multiple scenarios. --- --- # 3) Cargo Handling Classes, the main engines for mission designers! --- --- For this reason, the MOOSE Cargo System is heavily used by 3 important **cargo handling class hierarchies** within MOOSE, --- that make cargo come "alive" within your mission in a full automatic manner! --- --- ## 3.1) AI Cargo handlers. --- --- - @{AI.AI_Cargo_APC} will create for you the capability to make an APC group handle cargo. --- - @{AI.AI_Cargo_Helicopter} will create for you the capability to make a Helicopter group handle cargo. --- --- --- ## 3.2) AI Cargo transportation dispatchers. --- --- There are also dispatchers that make AI work together to transport cargo automatically!!! --- --- - @{AI.AI_Cargo_Dispatcher_APC} derived classes will create for your dynamic cargo handlers controlled by AI ground vehicle groups (APCs) to transport cargo between sites. --- - @{AI.AI_Cargo_Dispatcher_Helicopter} derived classes will create for your dynamic cargo handlers controlled by AI helicopter groups to transport cargo between sites. --- --- ## 3.3) Cargo transportation tasking. --- --- And there is cargo transportation tasking for human players. --- --- - @{Tasking.Task_CARGO} derived classes will create for you cargo transportation tasks, that allow human players to interact with MOOSE cargo objects to complete tasks. --- --- Please refer to the documentation reflected within these modules to understand the detailed capabilities. --- --- # 4) Cargo SETs. --- --- To make life a bit more easy, MOOSE cargo objects can be grouped into a @{Core.Set#SET_CARGO}. --- This is a collection of MOOSE cargo objects. --- --- This would work as follows: --- --- -- Define the cargo set. --- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() --- --- -- Now add cargo the cargo set. --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) --- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) --- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) --- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) --- --- This is a very powerful concept! --- Instead of having to deal with multiple MOOSE cargo objects yourself, the cargo set capability will group cargo objects into one set. --- The key is the **cargo type** name given at each cargo declaration! --- In the above example, the cargo type name is `"Workmaterials"`. Each cargo object declared is given that type name. (the 2nd parameter). --- What happens now is that the cargo set `CargoSetWorkmaterials` will be added with each cargo object **dynamically** when the cargo object is created. --- In other words, the cargo set `CargoSetWorkmaterials` will incorporate any `"Workmaterials"` dynamically into its set. --- --- The cargo sets are extremely important for the AI cargo transportation dispatchers and the cargo transporation tasking. --- --- # 5) Declare cargo directly in the mission editor! --- --- But I am not finished! There is something more, that is even more great! --- Imagine the mission designers having to code all these lines every time it wants to embed cargo within a mission. --- --- -- Now add cargo the cargo set. --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) --- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) --- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) --- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) --- --- This would be extremely tiring and a huge overload. --- However, the MOOSE framework allows to declare MOOSE cargo objects within the mission editor!!! --- --- So, at mission startup, MOOSE will search for objects following a special naming convention, and will **create** for you **dynamically --- cargo objects** at **mission start**!!! -- These cargo objects can then be automatically incorporated within cargo set(s)!!! --- In other words, your mission will be reduced to about a few lines of code, providing you with a full dynamic cargo handling mission! --- --- ## 5.1) Use \#CARGO tags in the mission editor: --- --- MOOSE can create automatically cargo objects, if the name of the cargo contains the **\#CARGO** tag. --- When a mission starts, MOOSE will scan all group and static objects it found for the presence of the \#CARGO tag. --- When found, MOOSE will declare the object as cargo (create in the background a CARGO_ object, like CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD. --- The creation of these CARGO_ objects will allow to be filtered and automatically added in SET_CARGO objects. --- In other words, with very minimal code as explained in the above code section, you are able to create vast amounts of cargo objects just from within the editor. --- --- What I talk about is this: --- --- -- BEFORE THIS SCRIPT STARTS, MOOSE WILL ALREADY HAVE SCANNED FOR OBJECTS WITH THE #CARGO TAG IN THE NAME. --- -- FOR EACH OF THESE OBJECT, MOOSE WILL HAVE CREATED CARGO_ OBJECTS LIKE CARGO_GROUP, CARGO_CRATE AND CARGO_SLINGLOAD. --- --- HQ = GROUP:FindByName( "HQ", "Bravo" ) --- --- CommandCenter = COMMANDCENTER --- :New( HQ, "Lima" ) --- --- Mission = MISSION --- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.RED ) --- --- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart() --- --- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) --- --- -- This is the most important now. You setup a new SET_CARGO filtering the relevant type. --- -- The actual cargo objects are now created by MOOSE in the background. --- -- Each cargo is setup in the Mission Editor using the #CARGO tag in the group name. --- -- This allows a truly dynamic setup. --- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() --- --- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) --- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) --- --- The above code example has the `CargoSetWorkmaterials`, which is a SET_CARGO collection and will include the CARGO_ objects of the type "Workmaterials". --- And there is NO cargo object actually declared within the script! However, if you would open the mission, there would be hundreds of cargo objects... --- --- The \#CARGO tag even allows for several options to be specified, which are important to learn. --- --- ## 5.2) The \#CARGO tag to create CARGO_GROUP objects: --- --- You can also use the \#CARGO tag on **group** objects of the mission editor. --- --- For example, the following #CARGO naming in the **group name** of the object, will create a CARGO_GROUP object when the mission starts. --- --- `Infantry #CARGO(T=Workmaterials,RR=500,NR=25)` --- --- This will create a CARGO_GROUP object: --- --- * with the group name `Infantry #CARGO` --- * is of type `Workmaterials` --- * will report when a carrier is within 500 meters --- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will disappear when the cargo is within 25 meters from the carrier during boarding --- --- So the overall syntax of the #CARGO naming tag and arguments are: --- --- `GroupName #CARGO(T=CargoTypeName,RR=Range,NR=Range)` --- --- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. --- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. --- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. --- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. --- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. --- --- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects: --- --- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor. --- --- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts. --- --- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)` --- --- This will create a CARGO_CRATE object: --- --- * with the group name `Static #CARGO` --- * is of type `Workmaterials` --- * is of category `CRATE` (as opposed to `SLING`) --- * will report when a carrier is within 500 meters --- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will disappear when the cargo is within 25 meters from the carrier during boarding --- --- So the overall syntax of the #CARGO naming tag and arguments are: --- --- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)` --- --- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. --- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectively. --- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. --- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. --- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. --- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. --- --- # 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** --- ### Contributions: --- --- === --- --- @module Cargo.Cargo --- @image Cargo.JPG - --- Events - --- Board - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] Board --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] __Board --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - - --- UnBoard - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] UnBoard --- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] __UnBoard --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - - --- Load - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] Load --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] __Load --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnLoad - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] UnLoad --- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] __UnLoad --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - --- State Transition Functions - --- UnLoaded - ---- @function [parent=#CARGO] OnLeaveUnLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterUnLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Loaded - ---- @function [parent=#CARGO] OnLeaveLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Boarding - ---- @function [parent=#CARGO] OnLeaveBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - --- UnBoarding - ---- @function [parent=#CARGO] OnLeaveUnBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterUnBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - - --- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. - -CARGOS = {} - -do -- CARGO - - -- @type CARGO - -- @extends Core.Fsm#FSM_PROCESS - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Wrapper.Unit#UNIT CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Wrapper.Client#CLIENT CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - - --- Defines the core functions that defines a cargo object within MOOSE. - -- - -- A cargo is a **logical object** defined that is available for transport, and has a life status within a simulation. - -- - -- CARGO is not meant to be used directly by mission designers, but provides a base class for **concrete cargo implementation classes** to handle: - -- - -- * Cargo **group objects**, implemented by the @{Cargo.CargoGroup#CARGO_GROUP} class. - -- * Cargo **Unit objects**, implemented by the @{Cargo.CargoUnit#CARGO_UNIT} class. - -- * Cargo **Crate objects**, implemented by the @{Cargo.CargoCrate#CARGO_CRATE} class. - -- * Cargo **Sling Load objects**, implemented by the @{Cargo.CargoSlingload#CARGO_SLINGLOAD} class. - -- - -- The above cargo classes are used by the AI\_CARGO\_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC#AI_CARGO_APC} class. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter#AI_CARGO_HELICOPTER} class. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane#AI_CARGO_AIRPLANE} class. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK\_CARGO\_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players. - -- - -- - -- The CARGO is a state machine: it manages the different events and states of the cargo. - -- All derived classes from CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. - -- - -- ## CARGO Events: - -- - -- * @{#CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. - -- * @{#CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. - -- * @{#CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. - -- * @{#CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. - -- * @{#CARGO.Destroyed}( Controllable ): The cargo is dead. The cargo process will be ended. - -- - -- @field #CARGO - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - Reported = {}, - } - - -- @type CARGO.CargoObjects - -- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - --- CARGO Constructor. This class is an abstract class and should not be instantiated. - -- @param #CARGO self - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO - function CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) --R2.1 - - local self = BASE:Inherit( self, FSM:New() ) -- #CARGO - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - self:SetStartState( "UnLoaded" ) - self:AddTransition( { "UnLoaded", "Boarding" }, "Board", "Boarding" ) - self:AddTransition( "Boarding" , "Boarding", "Boarding" ) - self:AddTransition( "Boarding", "CancelBoarding", "UnLoaded" ) - self:AddTransition( "Boarding", "Load", "Loaded" ) - self:AddTransition( "UnLoaded", "Load", "Loaded" ) - self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) - self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) - self:AddTransition( "*", "Damaged", "Damaged" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - self:AddTransition( "*", "Respawn", "UnLoaded" ) - self:AddTransition( "*", "Reset", "UnLoaded" ) - - self.Type = Type - self.Name = Name - self.Weight = Weight or 0 - self.CargoObject = nil - self.CargoCarrier = nil -- Wrapper.Client#CLIENT - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - self.CargoLimit = 0 - - self.LoadRadius = LoadRadius or 500 - --self.NearRadius = NearRadius or 25 - - self:SetDeployed( false ) - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self - end - - --- Find a CARGO in the _DATABASE. - -- @param #CARGO self - -- @param #string CargoName The Cargo Name. - -- @return #CARGO self - function CARGO:FindByName( CargoName ) - - local CargoFound = _DATABASE:FindCargo( CargoName ) - return CargoFound - end - - --- Get the x position of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetX() - if self:IsLoaded() then - return self.CargoCarrier:GetCoordinate().x - else - return self.CargoObject:GetCoordinate().x - end - end - - --- Get the y position of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetY() - if self:IsLoaded() then - return self.CargoCarrier:GetCoordinate().z - else - return self.CargoObject:GetCoordinate().z - end - end - - --- Get the heading of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetHeading() - if self:IsLoaded() then - return self.CargoCarrier:GetHeading() - else - return self.CargoObject:GetHeading() - end - end - - --- Check if the cargo can be Slingloaded. - -- @param #CARGO self - function CARGO:CanSlingload() - return false - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO self - function CARGO:CanBoard() - return true - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO self - function CARGO:CanUnboard() - return true - end - - --- Check if the cargo can be Loaded. - -- @param #CARGO self - function CARGO:CanLoad() - return true - end - - --- Check if the cargo can be Unloaded. - -- @param #CARGO self - function CARGO:CanUnload() - return true - end - - --- Destroy the cargo. - -- @param #CARGO self - function CARGO:Destroy() - if self.CargoObject then - self.CargoObject:Destroy() - end - self:Destroyed() - end - - --- Get the name of the Cargo. - -- @param #CARGO self - -- @return #string The name of the Cargo. - function CARGO:GetName() --R2.1 - return self.Name - end - - --- Get the current active object representing or being the Cargo. - -- @param #CARGO self - -- @return Wrapper.Positionable#POSITIONABLE The object representing or being the Cargo. - function CARGO:GetObject() - if self:IsLoaded() then - return self.CargoCarrier - else - return self.CargoObject - end - end - - --- Get the object name of the Cargo. - -- @param #CARGO self - -- @return #string The object name of the Cargo. - function CARGO:GetObjectName() --R2.1 - if self:IsLoaded() then - return self.CargoCarrier:GetName() - else - return self.CargoObject:GetName() - end - end - - --- Get the amount of Cargo. - -- @param #CARGO self - -- @return #number The amount of Cargo. - function CARGO:GetCount() - return 1 - end - - --- Get the type of the Cargo. - -- @param #CARGO self - -- @return #string The type of the Cargo. - function CARGO:GetType() - return self.Type - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO self - -- @return #string The transportation method of the Cargo. - function CARGO:GetTransportationMethod() - return self.TransportationMethod - end - - --- Get the coalition of the Cargo. - -- @param #CARGO self - -- @return Coalition - function CARGO:GetCoalition() - if self:IsLoaded() then - return self.CargoCarrier:GetCoalition() - else - return self.CargoObject:GetCoalition() - end - end - - --- Get the current coordinates of the Cargo. - -- @param #CARGO self - -- @return Core.Point#COORDINATE The coordinates of the Cargo. - function CARGO:GetCoordinate() - return self.CargoObject:GetCoordinate() - end - - --- Check if cargo is destroyed. - -- @param #CARGO self - -- @return #boolean true if destroyed - function CARGO:IsDestroyed() - return self:Is( "Destroyed" ) - end - - --- Check if cargo is loaded. - -- @param #CARGO self - -- @return #boolean true if loaded - function CARGO:IsLoaded() - return self:Is( "Loaded" ) - end - - --- Check if cargo is loaded. - -- @param #CARGO self - -- @param Wrapper.Unit#UNIT Carrier - -- @return #boolean true if loaded - function CARGO:IsLoadedInCarrier( Carrier ) - return self.CargoCarrier and self.CargoCarrier:GetName() == Carrier:GetName() - end - - --- Check if cargo is unloaded. - -- @param #CARGO self - -- @return #boolean true if unloaded - function CARGO:IsUnLoaded() - return self:Is( "UnLoaded" ) - end - - --- Check if cargo is boarding. - -- @param #CARGO self - -- @return #boolean true if boarding - function CARGO:IsBoarding() - return self:Is( "Boarding" ) - end - - --- Check if cargo is unboarding. - -- @param #CARGO self - -- @return #boolean true if unboarding - function CARGO:IsUnboarding() - return self:Is( "UnBoarding" ) - end - - --- Check if cargo is alive. - -- @param #CARGO self - -- @return #boolean true if unloaded - function CARGO:IsAlive() - - if self:IsLoaded() then - return self.CargoCarrier:IsAlive() - else - return self.CargoObject:IsAlive() - end - end - - --- Set the cargo as deployed. - -- @param #CARGO self - -- @param #boolean Deployed true if the cargo is to be deployed. false or nil otherwise. - function CARGO:SetDeployed( Deployed ) - self.Deployed = Deployed - end - - --- Is the cargo deployed - -- @param #CARGO self - -- @return #boolean - function CARGO:IsDeployed() - return self.Deployed - end - - --- Template method to spawn a new representation of the CARGO in the simulator. - -- @param #CARGO self - -- @return #CARGO - function CARGO:Spawn( PointVec2 ) - self:T() - - end - - --- Signal a flare at the position of the CARGO. - -- @param #CARGO self - -- @param Utilities.Utils#FLARECOLOR FlareColor - function CARGO:Flare( FlareColor ) - if self:IsUnLoaded() then - trigger.action.signalFlare( self.CargoObject:GetVec3(), FlareColor , 0 ) - end - end - - --- Signal a white flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareWhite() - self:Flare( trigger.flareColor.White ) - end - - --- Signal a yellow flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareYellow() - self:Flare( trigger.flareColor.Yellow ) - end - - --- Signal a green flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareGreen() - self:Flare( trigger.flareColor.Green ) - end - - --- Signal a red flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareRed() - self:Flare( trigger.flareColor.Red ) - end - - --- Smoke the CARGO. - -- @param #CARGO self - -- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke. - -- @param #number Radius The radius of randomization around the center of the Cargo. - function CARGO:Smoke( SmokeColor, Radius ) - if self:IsUnLoaded() then - if Radius then - trigger.action.smoke( self.CargoObject:GetRandomVec3( Radius ), SmokeColor ) - else - trigger.action.smoke( self.CargoObject:GetVec3(), SmokeColor ) - end - end - end - - --- Smoke the CARGO Green. - -- @param #CARGO self - function CARGO:SmokeGreen() - self:Smoke( trigger.smokeColor.Green, Range ) - end - - --- Smoke the CARGO Red. - -- @param #CARGO self - function CARGO:SmokeRed() - self:Smoke( trigger.smokeColor.Red, Range ) - end - - --- Smoke the CARGO White. - -- @param #CARGO self - function CARGO:SmokeWhite() - self:Smoke( trigger.smokeColor.White, Range ) - end - - --- Smoke the CARGO Orange. - -- @param #CARGO self - function CARGO:SmokeOrange() - self:Smoke( trigger.smokeColor.Orange, Range ) - end - - --- Smoke the CARGO Blue. - -- @param #CARGO self - function CARGO:SmokeBlue() - self:Smoke( trigger.smokeColor.Blue, Range ) - end - - --- Set the Load radius, which is the radius till when the Cargo can be loaded. - -- @param #CARGO self - -- @param #number LoadRadius The radius till Cargo can be loaded. - -- @return #CARGO - function CARGO:SetLoadRadius( LoadRadius ) - self.LoadRadius = LoadRadius or 150 - end - - --- Get the Load radius, which is the radius till when the Cargo can be loaded. - -- @param #CARGO self - -- @return #number The radius till Cargo can be loaded. - function CARGO:GetLoadRadius() - return self.LoadRadius - end - - --- Check if Cargo is in the LoadRadius for the Cargo to be Boarded or Loaded. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the CargoGroup is within the loading radius. - function CARGO:IsInLoadRadius( Coordinate ) - self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - local CargoCoordinate = self.CargoObject:GetCoordinate() - Distance = Coordinate:Get2DDistance( CargoCoordinate ) - self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - --- Check if the Cargo can report itself to be Boarded or Loaded. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo can report itself. - function CARGO:IsInReportRadius( Coordinate ) - self:T( { Coordinate } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if CargoCarrier is near the coordinate within NearRadius. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - -- @return #boolean - function CARGO:IsNear( Coordinate, NearRadius ) - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius } ) - - if self.CargoObject:IsAlive() then - --local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) - --self:T( { CargoObjectName = self.CargoObject:GetName() } ) - --self:T( { CargoObjectVec2 = self.CargoObject:GetVec2() } ) - --self:T( { PointVec2 = PointVec2:GetVec2() } ) - local Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( { Distance = Distance, NearRadius = NearRadius or "nil" } ) - - if Distance <= NearRadius then - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = true } ) - return true - end - end - - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = false } ) - return false - end - - --- Check if Cargo is the given @{Core.Zone}. - -- @param #CARGO self - -- @param Core.Zone#ZONE_BASE Zone - -- @return #boolean **true** if cargo is in the Zone, **false** if cargo is not in the Zone. - function CARGO:IsInZone( Zone ) - --self:T( { Zone } ) - - if self:IsLoaded() then - return Zone:IsPointVec2InZone( self.CargoCarrier:GetPointVec2() ) - else - --self:T( { Size = self.CargoObject:GetSize(), Units = self.CargoObject:GetUnits() } ) - if self.CargoObject:GetSize() ~= 0 then - return Zone:IsPointVec2InZone( self.CargoObject:GetPointVec2() ) - else - return false - end - end - - return nil - - end - - --- Get the current PointVec2 of the cargo. - -- @param #CARGO self - -- @return Core.Point#POINT_VEC2 - function CARGO:GetPointVec2() - return self.CargoObject:GetPointVec2() - end - - --- Get the current Coordinate of the cargo. - -- @param #CARGO self - -- @return Core.Point#COORDINATE - function CARGO:GetCoordinate() - return self.CargoObject:GetCoordinate() - end - - --- Get the weight of the cargo. - -- @param #CARGO self - -- @return #number Weight The weight in kg. - function CARGO:GetWeight() - return self.Weight - end - - --- Set the weight of the cargo. - -- @param #CARGO self - -- @param #number Weight The weight in kg. - -- @return #CARGO - function CARGO:SetWeight( Weight ) - self.Weight = Weight - return self - end - - --- Get the volume of the cargo. - -- @param #CARGO self - -- @return #number Volume The volume in kg. - function CARGO:GetVolume() - return self.Volume - end - - --- Set the volume of the cargo. - -- @param #CARGO self - -- @param #number Volume The volume in kg. - -- @return #CARGO - function CARGO:SetVolume( Volume ) - self.Volume = Volume - return self - end - - --- Send a CC message to a @{Wrapper.Group}. - -- @param #CARGO self - -- @param #string Message - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group. - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO:MessageToGroup( Message, CarrierGroup, Name ) - - MESSAGE:New( Message, 20, "Cargo " .. self:GetName() ):ToGroup( CarrierGroup ) - - end - - --- Report to a Carrier Group. - -- @param #CARGO self - -- @param #string Action The string describing the action for the cargo. - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:Report( ReportText, Action, CarrierGroup ) - - if not self.Reported[CarrierGroup] or not self.Reported[CarrierGroup][Action] then - self.Reported[CarrierGroup] = {} - self.Reported[CarrierGroup][Action] = true - self:MessageToGroup( ReportText, CarrierGroup ) - if self.ReportFlareColor then - if not self.Reported[CarrierGroup]["Flaring"] then - self:Flare( self.ReportFlareColor ) - self.Reported[CarrierGroup]["Flaring"] = true - end - end - if self.ReportSmokeColor then - if not self.Reported[CarrierGroup]["Smoking"] then - self:Smoke( self.ReportSmokeColor ) - self.Reported[CarrierGroup]["Smoking"] = true - end - end - end - end - - --- Report to a Carrier Group with a Flaring signal. - -- @param #CARGO self - -- @param Utilities.Utils#UTILS.FlareColor FlareColor the color of the flare. - -- @return #CARGO - function CARGO:ReportFlare( FlareColor ) - - self.ReportFlareColor = FlareColor - end - - --- Report to a Carrier Group with a Smoking signal. - -- @param #CARGO self - -- @param Utilities.Utils#UTILS.SmokeColor SmokeColor the color of the smoke. - -- @return #CARGO - function CARGO:ReportSmoke( SmokeColor ) - - self.ReportSmokeColor = SmokeColor - end - - --- Reset the reporting for a Carrier Group. - -- @param #CARGO self - -- @param #string Action The string describing the action for the cargo. - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:ReportReset( Action, CarrierGroup ) - - self.Reported[CarrierGroup][Action] = nil - end - - --- Reset all the reporting for a Carrier Group. - -- @param #CARGO self - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:ReportResetAll( CarrierGroup ) - - self.Reported[CarrierGroup] = nil - end - - --- Respawn the cargo when destroyed - -- @param #CARGO self - -- @param #boolean RespawnDestroyed - function CARGO:RespawnOnDestroyed( RespawnDestroyed ) - - if RespawnDestroyed then - self.onenterDestroyed = function( self ) - self:Respawn() - end - else - self.onenterDestroyed = nil - end - - end - -end -- CARGO - -do -- CARGO_REPRESENTABLE - - -- @type CARGO_REPRESENTABLE - -- @extends #CARGO - -- @field test - - --- Models CARGO that is representable by a Unit. - -- @field #CARGO_REPRESENTABLE CARGO_REPRESENTABLE - CARGO_REPRESENTABLE = { - ClassName = "CARGO_REPRESENTABLE" - } - - --- CARGO_REPRESENTABLE Constructor. - -- @param #CARGO_REPRESENTABLE self - -- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object. - -- @param #string Type Type name - -- @param #string Name Name. - -- @param #number LoadRadius (optional) Radius in meters. - -- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier. - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CARGO. - local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE - self:T( { Type, Name, LoadRadius, NearRadius } ) - - -- Descriptors. - local Desc=CargoObject:GetDesc() - self:T({Desc=Desc}) - - -- Weight. - local Weight = math.random( 80, 120 ) - - -- Adjust weight.. - if Desc then - if Desc.typeName == "2B11 mortar" then - Weight = 210 - else - Weight = Desc.massEmpty - end - end - - -- Set weight. - self:SetWeight( Weight ) - - return self - end - - --- CARGO_REPRESENTABLE Destructor. - -- @param #CARGO_REPRESENTABLE self - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:Destroy() - - -- Cargo objects are deleted from the _DATABASE and SET_CARGO objects. - self:T( { CargoName = self:GetName() } ) - --_EVENTDISPATCHER:CreateEventDeleteCargo( self ) - - return self - end - - --- Route a cargo unit to a PointVec2. - -- @param #CARGO_REPRESENTABLE self - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number Speed - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) - self:F2( ToPointVec2 ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) - Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - return self - end - - --- Send a message to a @{Wrapper.Group} through a communication channel near the cargo. - -- @param #CARGO_REPRESENTABLE self - -- @param #string Message - -- @param Wrapper.Group#GROUP TaskGroup - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO_REPRESENTABLE:MessageToGroup( Message, TaskGroup, Name ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , self:GetCoordinate():GetVec2(), 500 ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:T({NearUnit=NearUnit}) - local NearUnitCoalition = NearUnit:GetCoalition() - local CargoCoalition = self:GetCoalition() - if NearUnitCoalition == CargoCoalition then - local Attributes = NearUnit:GetDesc() - self:T({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - MESSAGE:New( Message, 20, NearUnit:GetCallsign() .. " reporting - Cargo " .. self:GetName() ):ToGroup( TaskGroup ) - break - end - end - end - - end - -end -- CARGO_REPRESENTABLE - -do -- CARGO_REPORTABLE - - -- @type CARGO_REPORTABLE - -- @extends #CARGO - CARGO_REPORTABLE = { - ClassName = "CARGO_REPORTABLE" - } - - --- CARGO_REPORTABLE Constructor. - -- @param #CARGO_REPORTABLE self - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_REPORTABLE - function CARGO_REPORTABLE:New( Type, Name, Weight, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_REPORTABLE - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - return self - end - - --- Send a CC message to a @{Wrapper.Group}. - -- @param #CARGO_REPORTABLE self - -- @param #string Message - -- @param Wrapper.Group#GROUP TaskGroup - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO_REPORTABLE:MessageToGroup( Message, TaskGroup, Name ) - - MESSAGE:New( Message, 20, "Cargo " .. self:GetName() .. " reporting" ):ToGroup( TaskGroup ) - - end - -end - -do -- CARGO_PACKAGE - - -- @type CARGO_PACKAGE - -- @extends #CARGO_REPRESENTABLE - CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" - } - ---- CARGO_PACKAGE Constructor. --- @param #CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number LoadRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_PACKAGE - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - return self -end - ---- Board Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO_PACKAGE:onafterOnBoard( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:T() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier --- @return #boolean -function CARGO_PACKAGE:IsNear( CargoCarrier ) - self:T() - - local CargoCarrierPoint = CargoCarrier:GetCoordinate() - - local Distance = CargoCarrierPoint:Get2DDistance( self.CargoCarrier:GetCoordinate() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:T() - - if self:IsNear( CargoCarrier ) then - self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function CARGO_PACKAGE:onafterUnBoard( From, Event, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:T() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:__UnBoarded( 1 , CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed -function CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) - self:T() - - if self:IsNear( CargoCarrier ) then - self:__UnLoad( 1, CargoCarrier, Speed ) - else - self:__UnBoarded( 1, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:onafterLoad( From, Event, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:T() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number Distance --- @param #number Angle -function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) - self:T() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - -end diff --git a/Moose Development/Moose/Cargo/CargoCrate.lua b/Moose Development/Moose/Cargo/CargoCrate.lua deleted file mode 100644 index 430032d0b..000000000 --- a/Moose Development/Moose/Cargo/CargoCrate.lua +++ /dev/null @@ -1,337 +0,0 @@ ---- **Cargo** - Management of single cargo crates, which are based on a STATIC object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoCrate --- @image Cargo_Crates.JPG - -do -- CARGO_CRATE - - --- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters. - -- @type CARGO_CRATE - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers. - -- - -- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human 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 #CARGO_CRATE - CARGO_CRATE = { - ClassName = "CARGO_CRATE" - } - - --- CARGO_CRATE Constructor. - -- @param #CARGO_CRATE self - -- @param Wrapper.Static#STATIC CargoStatic - -- @param #string Type - -- @param #string Name - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_CRATE - function CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_CRATE - self:T( { Type, Name, NearRadius } ) - - self.CargoObject = CargoStatic -- Wrapper.Static#STATIC - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - self.NearRadius = NearRadius or 25 - - return self - end - - -- @param #CARGO_CRATE self - -- @param Core.Event#EVENTDATA EventData - function CARGO_CRATE:OnEventCargoDead( EventData ) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then - if self.CargoObject:GetName() == EventData.IniUnitName then - if not self.NoDestroy then - Destroyed = true - end - end - else - if self:IsLoaded() then - local CarrierName = self.CargoCarrier:GetName() - if CarrierName == EventData.IniDCSUnitName then - MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() - Destroyed = true - self.CargoCarrier:ClearCargo() - end - end - end - - if Destroyed then - self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } ) - self:Destroyed() - end - - end - - - --- Enter UnLoaded State. - -- @param #CARGO_CRATE self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 - function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 ) - --self:T( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 10 - - if From == "Loaded" then - local StartCoordinate = self.CargoCarrier:GetCoordinate() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawnAt( ToPointVec2, 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - - end - - - --- Loaded State. - -- @param #CARGO_CRATE self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier ) - --self:T( { From, Event, To, CargoCarrier } ) - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self:T("Destroying") - self.NoDestroy = true - self.CargoObject:Destroy( false ) -- Do not generate a remove unit event, because we want to keep the template for later respawn in the database. - --local Coordinate = self.CargoObject:GetCoordinate():GetRandomCoordinateInRadius( 50, 20 ) - --self.CargoObject:ReSpawnAt( Coordinate, 0 ) - end - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanBoard() - return false - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanUnboard() - return false - end - - --- Check if the cargo can be sling loaded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanSlingload() - return false - end - - --- Check if Cargo Crate is in the radius for the Cargo to be reported. - -- @param #CARGO_CRATE self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Crate is within the report radius. - function CARGO_CRATE:IsInReportRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if Cargo Crate is in the radius for the Cargo to be Boarded or Loaded. - -- @param #CARGO_CRATE self - -- @param Core.Point#Coordinate Coordinate - -- @return #boolean true if the Cargo Crate is within the loading radius. - function CARGO_CRATE:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.NearRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.NearRadius then - return true - end - end - - return false - end - - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_CRATE self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_CRATE:GetCoordinate() - --self:T() - - return self.CargoObject:GetCoordinate() - end - - --- Check if the CargoGroup is alive. - -- @param #CARGO_CRATE self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_CRATE:IsAlive() - - local Alive = true - - -- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive. - -- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive. - if self:IsLoaded() then - Alive = Alive == true and self.CargoCarrier:IsAlive() - else - Alive = Alive == true and self.CargoObject:IsAlive() - end - - return Alive - - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_CRATE self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_CRATE:RouteTo( Coordinate ) - self:T( {Coordinate = Coordinate } ) - - end - - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier within NearRadius. - -- @param #CARGO_CRATE self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier. - -- @return #nil The Cargo is not near to the Carrier. - function CARGO_CRATE:IsNear( CargoCarrier, NearRadius ) - self:T( {NearRadius = NearRadius } ) - - return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) - end - - --- Respawn the CargoGroup. - -- @param #CARGO_CRATE self - function CARGO_CRATE:Respawn() - - self:T( { "Respawning crate " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event. - self:__Reset( -0.1 ) - end - - - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_CRATE self - function CARGO_CRATE:onafterReset() - - self:T( { "Reset crate " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - self.CargoCarrier = nil - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - end - - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_CRATE self - -- @return #string The transportation method of the Cargo. - function CARGO_CRATE:GetTransportationMethod() - if self:IsLoaded() then - return "for unloading" - else - if self:IsUnLoaded() then - return "for loading" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end - diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua deleted file mode 100644 index 0af776c05..000000000 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ /dev/null @@ -1,773 +0,0 @@ ---- **Cargo** - Management of grouped cargo logistics, which are based on a GROUP object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoGroup --- @image Cargo_Groups.JPG - - -do -- CARGO_GROUP - - --- @type CARGO_GROUP - -- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects. - -- @field #string GroupName The name of the CargoGroup. - -- @extends Cargo.Cargo#CARGO_REPORTABLE - - --- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator. - -- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers. - -- - -- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human 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 #CARGO_GROUP CARGO_GROUP - -- - CARGO_GROUP = { - ClassName = "CARGO_GROUP", - } - - --- CARGO_GROUP constructor. - -- This make a new CARGO_GROUP from a @{Wrapper.Group} object. - -- It will "ungroup" the group object within the sim, and will create a @{Core.Set} of individual Unit objects. - -- @param #CARGO_GROUP self - -- @param Wrapper.Group#GROUP CargoGroup Group to be transported as cargo. - -- @param #string Type Cargo type, e.g. "Infantry". This is the type used in SET_CARGO:New():FilterTypes("Infantry") to define the valid cargo groups of the set. - -- @param #string Name A user defined name of the cargo group. This name CAN be the same as the group object but can also have a different name. This name MUST be unique! - -- @param #number LoadRadius (optional) Distance in meters until which a cargo is loaded into the carrier. Cargo outside this radius has to be routed by other means to within the radius to be loaded. - -- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene. - -- @return #CARGO_GROUP Cargo group object. - function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CAROG_REPORTABLE - local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP - self:T( { Type, Name, LoadRadius } ) - - self.CargoSet = SET_CARGO:New() - self.CargoGroup = CargoGroup - self.Grouped = true - self.CargoUnitTemplate = {} - - self.NearRadius = NearRadius - - self:SetDeployed( false ) - - local WeightGroup = 0 - local VolumeGroup = 0 - - self.CargoGroup:Destroy() -- destroy and generate a unit removal event, so that the database gets cleaned, and the linked sets get properly cleaned. - - local GroupName = CargoGroup:GetName() - self.CargoName = Name - self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) ) - - -- Deactivate late activation. - self.CargoTemplate.lateActivation=false - - self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - self.GroupTemplate.name = self.CargoName .. "#CARGO" - self.GroupTemplate.groupId = nil - - self.GroupTemplate.units = {} - - for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do - UnitTemplate.name = UnitTemplate.name .. "#CARGO" - local CargoUnitName = UnitTemplate.name - self.CargoUnitTemplate[CargoUnitName] = UnitTemplate - - self.GroupTemplate.units[#self.GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] - self.GroupTemplate.units[#self.GroupTemplate.units].unitId = nil - - -- And we register the spawned unit as part of the CargoSet. - local Unit = UNIT:Register( CargoUnitName ) - - end - - -- Then we register the new group in the database - self.CargoGroup = GROUP:NewTemplate( self.GroupTemplate, self.GroupTemplate.CoalitionID, self.GroupTemplate.CategoryID, self.GroupTemplate.CountryID ) - - -- Now we spawn the new group based on the template created. - self.CargoObject = _DATABASE:Spawn( self.GroupTemplate ) - - for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do - - - local CargoUnitName = CargoUnit:GetName() - - local Cargo = CARGO_UNIT:New( CargoUnit, Type, CargoUnitName, LoadRadius, NearRadius ) - self.CargoSet:Add( CargoUnitName, Cargo ) - - WeightGroup = WeightGroup + Cargo:GetWeight() - - end - - self:SetWeight( WeightGroup ) - - self:T( { "Weight Cargo", WeightGroup } ) - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - return self - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Respawn() - - self:T( { "Respawning" } ) - - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - Cargo:Destroy() -- Destroy the cargo and generate a remove unit event to update the sets. - Cargo:SetStartState( "UnLoaded" ) - end - - -- Now we spawn the new group based on the template created. - _DATABASE:Spawn( self.GroupTemplate ) - - for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do - - local CargoUnitName = CargoUnit:GetName() - - local Cargo = CARGO_UNIT:New( CargoUnit, self.Type, CargoUnitName, self.LoadRadius ) - self.CargoSet:Add( CargoUnitName, Cargo ) - - end - - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - - end - - --- Ungroup the cargo group into individual groups with one unit. - -- This is required because by default a group will move in formation and this is really an issue for group control. - -- Therefore this method is made to be able to ungroup a group. - -- This works for ground only groups. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Ungroup() - - if self.Grouped == true then - - self.Grouped = false - - self.CargoGroup:Destroy() - - for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do - local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT - - if CargoUnit:IsUnLoaded() then - local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - --local GroupName = env.getValueDictByKey( GroupTemplate.name ) - - -- We create a new group object with one unit... - -- First we prepare the template... - GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName - GroupTemplate.groupId = nil - - if CargoUnit:IsUnLoaded() then - GroupTemplate.units = {} - GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName] - GroupTemplate.units[#GroupTemplate.units].unitId = nil - GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX() - GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY() - GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading() - end - - - -- Then we register the new group in the database - local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID) - - -- Now we spawn the new group based on the template created. - _DATABASE:Spawn( GroupTemplate ) - end - end - - self.CargoObject = nil - end - - - end - - --- Regroup the cargo group into one group with multiple unit. - -- This is required because by default a group will move in formation and this is really an issue for group control. - -- Therefore this method is made to be able to regroup a group. - -- This works for ground only groups. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Regroup() - - self:T("Regroup") - - if self.Grouped == false then - - self.Grouped = true - - local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - GroupTemplate.name = self.CargoName .. "#CARGO" - GroupTemplate.groupId = nil - GroupTemplate.units = {} - - for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do - local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT - - self:T( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } ) - - if CargoUnit:IsUnLoaded() then - - CargoUnit.CargoObject:Destroy() - - GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] - GroupTemplate.units[#GroupTemplate.units].unitId = nil - GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX() - GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY() - GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading() - end - end - - -- Then we register the new group in the database - self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID ) - - self:T( { "Regroup", GroupTemplate } ) - - -- Now we spawn the new group based on the template created. - self.CargoObject = _DATABASE:Spawn( GroupTemplate ) - end - - end - - - --- @param #CARGO_GROUP self - -- @param Core.Event#EVENTDATA EventData - function CARGO_GROUP:OnEventCargoDead( EventData ) - - self:T(EventData) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then - Destroyed = true - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - if Cargo:IsAlive() then - Destroyed = false - else - Cargo:Destroyed() - end - end - else - local CarrierName = self.CargoCarrier:GetName() - if CarrierName == EventData.IniDCSUnitName then - MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() - Destroyed = true - self.CargoCarrier:ClearCargo() - end - end - - if Destroyed then - self:Destroyed() - self:T( { "Cargo group destroyed" } ) - end - - end - - --- After Board Event. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { CargoCarrier.UnitName, From, Event, To, NearRadius = NearRadius } ) - - NearRadius = NearRadius or self.NearRadius - - -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo, ... ) - self:T( { "Board Unit", Cargo:GetName( ), Cargo:IsDestroyed(), Cargo.CargoObject:IsAlive() } ) - local CargoGroup = Cargo.CargoObject --Wrapper.Group#GROUP - CargoGroup:OptionAlarmStateGreen() - Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) - end, ... - ) - - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - - end - - --- Enter Loaded State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_GROUP:onafterLoad( From, Event, To, CargoCarrier, ... ) - --self:T( { From, Event, To, CargoCarrier, ...} ) - - if From == "UnLoaded" then - -- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier. - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - if not Cargo:IsDestroyed() then - Cargo:Load( CargoCarrier ) - end - end - end - - --self.CargoObject:Destroy() - self.CargoCarrier = CargoCarrier - self.CargoCarrier:AddCargo( self ) - - end - - --- Leave Boarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - --self:T( { CargoCarrier.UnitName, From, Event, To } ) - - local Boarded = true - local Cancelled = false - local Dead = true - - self.CargoSet:Flush() - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - --self:T( { Cargo:GetName(), Cargo.current } ) - - - if not Cargo:is( "Loaded" ) - and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function. - Boarded = false - end - - if Cargo:is( "UnLoaded" ) then - Cancelled = true - end - - if not Cargo:is( "Destroyed" ) then - Dead = false - end - - end - - if not Dead then - - if not Cancelled then - if not Boarded then - self:__Boarding( -5, CargoCarrier, NearRadius, ... ) - else - self:T("Group Cargo is loaded") - self:__Load( 1, CargoCarrier, ... ) - end - else - self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... ) - end - else - self:__Destroyed( 1, CargoCarrier, NearRadius, ... ) - end - - end - - --- Enter UnBoarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterUnBoard( From, Event, To, ToPointVec2, NearRadius, ... ) - self:T( {From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local Timer = 1 - - if From == "Loaded" then - - if self.CargoObject then - self.CargoObject:Destroy() - end - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - --- @param Cargo.Cargo#CARGO Cargo - function( Cargo, NearRadius ) - if not Cargo:IsDestroyed() then - local ToVec=nil - if ToPointVec2==nil then - ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius, NearRadius) - else - ToVec=ToPointVec2 - end - Cargo:__UnBoard( Timer, ToVec, NearRadius ) - Timer = Timer + 1 - end - end, { NearRadius } - ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - - end - - --- Leave UnBoarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - --self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - --local NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - local UnBoarded = true - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( { Cargo:GetName(), Cargo.current } ) - if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then - UnBoarded = false - end - end - - if UnBoarded then - self:__UnLoad( 1, ToPointVec2, ... ) - else - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - - return false - end - - end - - --- Enter UnLoaded State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) - --self:T( { From, Event, To, ToPointVec2 } ) - - if From == "Loaded" then - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - --Cargo:UnLoad( ToPointVec2 ) - local RandomVec2=nil - if ToPointVec2 then - RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) - end - Cargo:UnBoard( RandomVec2 ) - end - ) - - end - - self.CargoCarrier:RemoveCargo( self ) - self.CargoCarrier = nil - - end - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_GROUP self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_GROUP:GetCoordinate() - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo.CargoObject:GetCoordinate() - end - - return nil - end - - --- Get the x position of the cargo. - -- @param #CARGO_GROUP self - -- @return #number - function CARGO:GetX() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:GetCoordinate().x - end - - return nil - end - - --- Get the y position of the cargo. - -- @param #CARGO_GROUP self - -- @return #number - function CARGO:GetY() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:GetCoordinate().z - end - - return nil - end - - - - --- Check if the CargoGroup is alive. - -- @param #CARGO_GROUP self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_GROUP:IsAlive() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - return Cargo ~= nil - - end - - - --- Get the first alive Cargo Unit of the Cargo Group. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetFirstAlive() - - local CargoFirstAlive = nil - - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - if not Cargo:IsDestroyed() then - CargoFirstAlive = Cargo - break - end - end - return CargoFirstAlive - end - - - --- Get the amount of cargo units in the group. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetCount() - return self.CargoSet:Count() - end - - - --- Get the underlying GROUP object from the CARGO_GROUP. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetGroup( Cargo ) - local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO - return Cargo.CargoObject:GetGroup() - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_GROUP self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_GROUP:RouteTo( Coordinate ) - --self:T( {Coordinate = Coordinate } ) - - -- For each Cargo within the CargoSet, route each object to the Coordinate - self.CargoSet:ForEach( - function( Cargo ) - Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 ) - end - ) - - end - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius. - -- @param #CARGO_GROUP self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier or #nil if the Cargo is not near to the Carrier. - function CARGO_GROUP:IsNear( CargoCarrier, NearRadius ) - self:T( {NearRadius = NearRadius } ) - - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsAlive() then - if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then - self:T( "Near" ) - return true - end - end - end - - return nil - end - - --- Check if Cargo Group is in the radius for the Cargo to be Boarded. - -- @param #CARGO_GROUP self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Group is within the load radius. - function CARGO_GROUP:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - local Distance = 0 - local CargoCoordinate - if Cargo:IsLoaded() then - CargoCoordinate = Cargo.CargoCarrier:GetCoordinate() - else - CargoCoordinate = Cargo.CargoObject:GetCoordinate() - end - - -- FF check if coordinate could be obtained. This was commented out for some (unknown) reason. But the check seems valid! - if CargoCoordinate then - Distance = Coordinate:Get2DDistance( CargoCoordinate ) - else - return false - end - - self:T( { Distance = Distance, LoadRadius = self.LoadRadius } ) - if Distance <= self.LoadRadius then - return true - else - return false - end - end - - return nil - - end - - - --- Check if Cargo Group is in the report radius. - -- @param #CARGO_GROUP self - -- @param Core.Point#Coordinate Coordinate - -- @return #boolean true if the Cargo Group is within the report radius. - function CARGO_GROUP:IsInReportRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - self:T( { Cargo } ) - local Distance = 0 - if Cargo:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - end - - return nil - - end - - - --- Signal a flare at the position of the CargoGroup. - -- @param #CARGO_GROUP self - -- @param Utilities.Utils#FLARECOLOR FlareColor - function CARGO_GROUP:Flare( FlareColor ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - if Cargo then - Cargo:Flare( FlareColor ) - end - end - - --- Smoke the CargoGroup. - -- @param #CARGO_GROUP self - -- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke. - -- @param #number Radius The radius of randomization around the center of the first element of the CargoGroup. - function CARGO_GROUP:Smoke( SmokeColor, Radius ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - - if Cargo then - Cargo:Smoke( SmokeColor, Radius ) - end - end - - --- Check if the first element of the CargoGroup is the given @{Core.Zone}. - -- @param #CARGO_GROUP self - -- @param Core.Zone#ZONE_BASE Zone - -- @return #boolean **true** if the first element of the CargoGroup is in the Zone - -- @return #boolean **false** if there is no element of the CargoGroup in the Zone. - function CARGO_GROUP:IsInZone( Zone ) - --self:T( { Zone } ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:IsInZone( Zone ) - end - - return nil - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_GROUP self - -- @return #string The transportation method of the Cargo. - function CARGO_GROUP:GetTransportationMethod() - if self:IsLoaded() then - return "for unboarding" - else - if self:IsUnLoaded() then - return "for boarding" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - - - -end -- CARGO_GROUP diff --git a/Moose Development/Moose/Cargo/CargoSlingload.lua b/Moose Development/Moose/Cargo/CargoSlingload.lua deleted file mode 100644 index 81bc5d95e..000000000 --- a/Moose Development/Moose/Cargo/CargoSlingload.lua +++ /dev/null @@ -1,275 +0,0 @@ ---- **Cargo** - Management of single cargo crates, which are based on a STATIC object. The cargo can only be slingloaded. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoSlingload --- @image Cargo_Slingload.JPG - - -do -- CARGO_SLINGLOAD - - --- Models the behaviour of cargo crates, which can only be slingloaded. - -- @type CARGO_SLINGLOAD - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human 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 #CARGO_SLINGLOAD - CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" - } - - --- CARGO_SLINGLOAD Constructor. - -- @param #CARGO_SLINGLOAD self - -- @param Wrapper.Static#STATIC CargoStatic - -- @param #string Type - -- @param #string Name - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_SLINGLOAD - function CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_SLINGLOAD - self:T( { Type, Name, NearRadius } ) - - self.CargoObject = CargoStatic - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - self.NearRadius = NearRadius or 25 - - return self - end - - - -- @param #CARGO_SLINGLOAD self - -- @param Core.Event#EVENTDATA EventData - function CARGO_SLINGLOAD:OnEventCargoDead( EventData ) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() then - if self.CargoObject:GetName() == EventData.IniUnitName then - if not self.NoDestroy then - Destroyed = true - end - end - end - - if Destroyed then - self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } ) - self:Destroyed() - end - - end - - - --- Check if the cargo can be Slingloaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanSlingload() - return true - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanBoard() - return false - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanUnboard() - return false - end - - --- Check if the cargo can be Loaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanLoad() - return false - end - - --- Check if the cargo can be Unloaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanUnload() - return false - end - - - --- Check if Cargo Crate is in the radius for the Cargo to be reported. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Crate is within the report radius. - function CARGO_SLINGLOAD:IsInReportRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Slingload is within the loading radius. - function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - if Distance <= self.NearRadius then - return true - end - end - - return false - end - - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_SLINGLOAD self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_SLINGLOAD:GetCoordinate() - --self:T() - - return self.CargoObject:GetCoordinate() - end - - --- Check if the CargoGroup is alive. - -- @param #CARGO_SLINGLOAD self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_SLINGLOAD:IsAlive() - - local Alive = true - - -- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive. - -- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive. - if self:IsLoaded() then - Alive = Alive == true and self.CargoCarrier:IsAlive() - else - Alive = Alive == true and self.CargoObject:IsAlive() - end - - return Alive - - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_SLINGLOAD:RouteTo( Coordinate ) - --self:T( {Coordinate = Coordinate } ) - - end - - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier within NearRadius. - -- @param #CARGO_SLINGLOAD self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier. - -- @return #nil The Cargo is not near to the Carrier. - function CARGO_SLINGLOAD:IsNear( CargoCarrier, NearRadius ) - --self:T( {NearRadius = NearRadius } ) - - return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:Respawn() - - --self:T( { "Respawning slingload " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event. - self:__Reset( -0.1 ) - end - - - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:onafterReset() - - --self:T( { "Reset slingload " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - self.CargoCarrier = nil - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - end - - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_SLINGLOAD self - -- @return #string The transportation method of the Cargo. - function CARGO_SLINGLOAD:GetTransportationMethod() - if self:IsLoaded() then - return "for sling loading" - else - if self:IsUnLoaded() then - return "for sling loading" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua deleted file mode 100644 index a1d86dd49..000000000 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ /dev/null @@ -1,395 +0,0 @@ ---- **Cargo** - Management of single cargo logistics, which are based on a UNIT object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoUnit --- @image Cargo_Units.JPG - -do -- CARGO_UNIT - - --- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded. - -- @type CARGO_UNIT - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO_UNIT objects to and from carriers. - -- Note that ground forces behave in a group, and thus, act in formation, regardless if one unit is commanded to move. - -- - -- This class is used in CARGO_GROUP, and is not meant to be used by mission designers 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 - -- - -- === - -- - -- @field #CARGO_UNIT CARGO_UNIT - -- - CARGO_UNIT = { - ClassName = "CARGO_UNIT" - } - - --- CARGO_UNIT Constructor. - -- @param #CARGO_UNIT self - -- @param Wrapper.Unit#UNIT CargoUnit - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_UNIT - function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CARGO_REPRESENTABLE. - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT - - -- Debug info. - self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius}) - - -- Set cargo object. - self.CargoObject = CargoUnit - - -- Set event prio. - self:SetEventPriority( 5 ) - - return self - end - - --- Enter UnBoarding State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 25 m. - function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - local Angle = 180 - local Speed = 60 - local DeployDistance = 9 - local RouteDistance = 60 - - if From == "Loaded" then - - if not self:IsDestroyed() then - - local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE - - if CargoCarrier:IsAlive() then - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - - - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 - local FromDirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3( ToPointVec2 or CargoRoutePointVec2 ) - local FromAngle = CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3) - local FromPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, FromAngle ) - --local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( 10, 5 ) - - ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, DeployDistance ) - - -- Respawn the group... - if self.CargoObject then - if CargoCarrier:IsShip() then - -- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead. - self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading ) - else - self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) - end - self:T( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) - self.CargoCarrier = nil - - local Points = {} - - -- From - Points[#Points+1] = FromPointVec2:WaypointGround( Speed, "Vee" ) - - -- To - Points[#Points+1] = ToPointVec2:WaypointGround( Speed, "Vee" ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) - end - else - -- the Carrier is dead. This cargo is dead too! - self:Destroyed() - end - end - end - - end - - --- Leave UnBoarding State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 100 m. - function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - --if self:IsNear( ToPointVec2, NearRadius ) then - return true - --else - - --self:__UnBoarding( 1, ToPointVec2, NearRadius ) - --end - --return false - end - - end - - --- UnBoard Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 100 m. - function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:__UnLoad( 1, ToPointVec2, NearRadius ) - - end - - - - --- Enter UnLoaded State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 - function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:T( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawnAt( ToPointVec2, 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - - end - - --- Board Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { From, Event, To, CargoCarrier, NearRadius = NearRadius } ) - - self.CargoInAir = self.CargoObject:InAir() - - local Desc = self.CargoObject:GetDesc() - local MaxSpeed = Desc.speedMaxOffRoad - local TypeName = Desc.typeName - - --self:T({Unit=self.CargoObject:GetName()}) - - -- A cargo unit can only be boarded if it is not dead - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - -- If NearRadius is given, then use the given NearRadius, otherwise calculate the NearRadius - -- based upon the Carrier bounding radius, which is calculated from the bounding rectangle on the Y axis. - local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius() + 5 - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:Load( CargoCarrier, NearRadius, ... ) - else - if MaxSpeed and MaxSpeed == 0 or TypeName and TypeName == "Stinger comm" then - self:Load( CargoCarrier, NearRadius, ... ) - else - - local Speed = 90 - local Angle = 180 - local Distance = 0 - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Set the CargoObject to state Green to ensure it is boarding! - self.CargoObject:OptionAlarmStateGreen() - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - self:__Boarding( -5, CargoCarrier, NearRadius, ... ) - self.RunCount = 0 - end - end - end - end - - - --- Boarding Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Client#CLIENT CargoCarrier - -- @param #number NearRadius Default 25 m. - function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { From, Event, To, CargoCarrier:GetName(), NearRadius = NearRadius } ) - - self:T( { IsAlive=self.CargoObject:IsAlive() } ) - - if CargoCarrier and CargoCarrier:IsAlive() then -- and self.CargoObject and self.CargoObject:IsAlive() then - if (CargoCarrier:IsAir() and not CargoCarrier:InAir()) or true then - local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius( NearRadius ) + 5 - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:__Load( -1, CargoCarrier, ... ) - else - if self:IsNear( CargoCarrier:GetPointVec2(), 20 ) then - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 1 - else - self:__Boarding( -2, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 2 - end - if self.RunCount >= 40 then - self.RunCount = 0 - local Speed = 90 - local Angle = 180 - local Distance = 0 - - --self:T({Unit=self.CargoObject:GetName()}) - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Set the CargoObject to state Green to ensure it is boarding! - self.CargoObject:OptionAlarmStateGreen() - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 0.2 ) - end - end - else - self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() ) - self:CancelBoarding( CargoCarrier, NearRadius, ... ) - self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) ) - end - else - self:T("Something is wrong") - end - - end - - - --- Loaded State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) - self:T( { From, Event, To, CargoCarrier } ) - - self.CargoCarrier = CargoCarrier - - --self:T({Unit=self.CargoObject:GetName()}) - - -- Only destroy the CargoObject if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self.CargoObject:Destroy( false ) - --self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 ) - end - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_UNIT self - -- @return #string The transportation method of the Cargo. - function CARGO_UNIT:GetTransportationMethod() - if self:IsLoaded() then - return "for unboarding" - else - if self:IsUnLoaded() then - return "for boarding" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end -- CARGO_UNIT diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 969dde2e6..91cfd2830 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -2,7 +2,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' ) ---__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) @@ -119,43 +118,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Engage.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Cap.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Gci.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_BAI.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_CAS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_SEAD.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAP.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_BAI.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Formation.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Request.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher_Request.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_APC.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Helicopter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Airplane.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Ship.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_APC.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' ) - -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assign.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Route.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' ) - __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Cube.lua' ) @@ -171,21 +133,4 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioQueue.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioSpeech.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SRS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/CommandCenter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Mission.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/TaskInfo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Manager.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/DetectionManager.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_CARGO.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Transport.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_CSAR.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' ) - __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' ) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 5a6b6c0e9..b6e3d1b74 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -1,7 +1,6 @@ __Moose.Include( 'Utilities\\Enums.lua' ) __Moose.Include( 'Utilities\\Utils.lua' ) __Moose.Include( 'Utilities\\Profiler.lua' ) ---__Moose.Include( 'Utilities\\STTS.lua' ) __Moose.Include( 'Utilities\\FiFo.lua' ) __Moose.Include( 'Utilities\\Socket.lua' ) @@ -116,43 +115,6 @@ __Moose.Include( 'Ops\\FlightControl.lua' ) __Moose.Include( 'Ops\\PlayerRecce.lua' ) __Moose.Include( 'Ops\\EasyGCICAP.lua' ) -__Moose.Include( 'AI\\AI_Balancer.lua' ) -__Moose.Include( 'AI\\AI_Air.lua' ) -__Moose.Include( 'AI\\AI_Air_Patrol.lua' ) -__Moose.Include( 'AI\\AI_Air_Engage.lua' ) -__Moose.Include( 'AI\\AI_A2A_Patrol.lua' ) -__Moose.Include( 'AI\\AI_A2A_Cap.lua' ) -__Moose.Include( 'AI\\AI_A2A_Gci.lua' ) -__Moose.Include( 'AI\\AI_A2A_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_A2G_BAI.lua' ) -__Moose.Include( 'AI\\AI_A2G_CAS.lua' ) -__Moose.Include( 'AI\\AI_A2G_SEAD.lua' ) -__Moose.Include( 'AI\\AI_A2G_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Patrol.lua' ) -__Moose.Include( 'AI\\AI_Cap.lua' ) -__Moose.Include( 'AI\\AI_Cas.lua' ) -__Moose.Include( 'AI\\AI_Bai.lua' ) -__Moose.Include( 'AI\\AI_Formation.lua' ) -__Moose.Include( 'AI\\AI_Escort.lua' ) -__Moose.Include( 'AI\\AI_Escort_Request.lua' ) -__Moose.Include( 'AI\\AI_Escort_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Escort_Dispatcher_Request.lua' ) -__Moose.Include( 'AI\\AI_Cargo.lua' ) -__Moose.Include( 'AI\\AI_Cargo_APC.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Helicopter.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Airplane.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Ship.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_APC.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Helicopter.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Airplane.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Ship.lua' ) - -__Moose.Include( 'Actions\\Act_Assign.lua' ) -__Moose.Include( 'Actions\\Act_Route.lua' ) -__Moose.Include( 'Actions\\Act_Account.lua' ) -__Moose.Include( 'Actions\\Act_Assist.lua' ) - __Moose.Include( 'Sound\\UserSound.lua' ) __Moose.Include( 'Sound\\SoundOutput.lua' ) __Moose.Include( 'Sound\\Radio.lua' ) @@ -160,21 +122,6 @@ __Moose.Include( 'Sound\\RadioQueue.lua' ) __Moose.Include( 'Sound\\RadioSpeech.lua' ) __Moose.Include( 'Sound\\SRS.lua' ) -__Moose.Include( 'Tasking\\CommandCenter.lua' ) -__Moose.Include( 'Tasking\\Mission.lua' ) -__Moose.Include( 'Tasking\\Task.lua' ) -__Moose.Include( 'Tasking\\TaskInfo.lua' ) -__Moose.Include( 'Tasking\\Task_Manager.lua' ) -__Moose.Include( 'Tasking\\DetectionManager.lua' ) -__Moose.Include( 'Tasking\\Task_A2G_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_A2G.lua' ) -__Moose.Include( 'Tasking\\Task_A2A_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_A2A.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_Transport.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_CSAR.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_Capture_Zone.lua' ) __Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' ) __Moose.Include( 'Globals.lua' ) diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua deleted file mode 100644 index 59ab36a3c..000000000 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ /dev/null @@ -1,821 +0,0 @@ ---- **Tasking** - A command center governs multiple missions, and takes care of the reporting and communications. --- --- **Features:** --- --- * Govern multiple missions. --- * Communicate to coalitions, groups. --- * Assign tasks. --- * Manage the menus. --- * Manage reference zones. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.CommandCenter --- @image Task_Command_Center.JPG - - ---- The COMMANDCENTER class --- @type COMMANDCENTER --- @field Wrapper.Group#GROUP HQ --- @field DCS#coalition CommandCenterCoalition --- @list Missions --- @extends Core.Base#BASE - - ---- Governs multiple missions, the tasking and the reporting. --- --- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow. --- It can assign a random task to a player when requested. --- The commandcenter provides the facilitites to communicate between human players online, executing a task. --- --- ## 1. Create a command center object. --- --- * @{#COMMANDCENTER.New}(): Creates a new COMMANDCENTER object. --- --- ## 2. Command center mission management. --- --- Command centers manage missions. These can be added, removed and provides means to retrieve missions. --- These methods are heavily used by the task dispatcher classes. --- --- * @{#COMMANDCENTER.AddMission}(): Adds a mission to the commandcenter control. --- * @{#COMMANDCENTER.RemoveMission}(): Removes a mission to the commandcenter control. --- * @{#COMMANDCENTER.GetMissions}(): Retrieves the missions table controlled by the commandcenter. --- --- ## 3. Communication management between players. --- --- Command center provide means of communication between players. --- Because a command center is a central object governing multiple missions, --- there are several levels at which communication needs to be done. --- Within MOOSE, communication is facilitated using the message system within the DCS simulator. --- --- Messages can be sent between players at various levels: --- --- - On a global level, to all players. --- - On a coalition level, only to the players belonging to the same coalition. --- - On a group level, to the players belonging to the same group. --- --- Messages can be sent to **all players** by the command center using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToAll}(). --- --- To send messages to **the coalition of the command center**, there are two methods available: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToCoalition}() to send a specific message to the coalition, with a given message display duration. --- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToCoalition}(). --- This will send a message of a specific type to the coalition, and as a result its display duration will be flexible according the message display time selection by the human player. --- --- To send messages **to the group** of human players, there are also two methods available: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToGroup}() to send a specific message to a group, with a given message display duration. --- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToGroup}(). --- This will send a message of a specific type to the group, and as a result its display duration will be flexible according the message display time selection by the human player . --- --- Messages are considered to be sometimes disturbing for human players, therefore, the settings menu provides the means to activate or deactivate messages. --- For more information on the message types and display timings that can be selected and configured using the menu, refer to the @{Core.Settings} menu description. --- --- ## 4. Command center detailed methods. --- --- Various methods are added to manage command centers. --- --- ### 4.1. Naming and description. --- --- There are 3 methods that can be used to retrieve the description of a command center: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.GetName}() to retrieve the name of the command center. --- This is the name given as part of the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. --- The returned name using this method, is not to be used for message communication. --- --- A textual description can be retrieved that provides the command center name to be used within message communication: --- --- - @{Tasking.CommandCenter#COMMANDCENTER.GetShortText}() returns the command center name as `CC [CommandCenterName]`. --- - @{Tasking.CommandCenter#COMMANDCENTER.GetText}() returns the command center name as `Command Center [CommandCenterName]`. --- --- ### 4.2. The coalition of the command center. --- --- The method @{Tasking.CommandCenter#COMMANDCENTER.GetCoalition}() returns the coalition of the command center. --- The return value is an enumeration of the type @{DCS#coalition.side}, which contains the RED, BLUE and NEUTRAL coalition. --- --- ### 4.3. The command center is a real object. --- --- The command center must be represented by a live object within the DCS simulator. As a result, the command center --- can be a @{Wrapper.Unit}, a @{Wrapper.Group}, an @{Wrapper.Airbase} or a @{Wrapper.Static} object. --- --- Using the method @{Tasking.CommandCenter#COMMANDCENTER.GetPositionable}() you retrieve the polymorphic positionable object representing --- the command center, but just be aware that you should be able to use the representable object derivation methods. --- --- ### 5. Command center reports. --- --- Because a command center giverns multiple missions, there are several reports available that are generated by command centers. --- These reports are generated using the following methods: --- --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportSummary}(): Creates a summary report of all missions governed by the command center. --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportDetails}(): Creates a detailed report of all missions governed by the command center. --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportMissionPlayers}(): Creates a report listing the players active at the missions governed by the command center. --- --- ## 6. Reference Zones. --- --- Command Centers may be aware of certain Reference Zones within the battleground. These Reference Zones can refer to --- known areas, recognizable buildings or sites, or any other point of interest. --- Command Centers will use these Reference Zones to help pilots with defining coordinates in terms of navigation --- during the WWII era. --- The Reference Zones are related to the WWII mode that the Command Center will operate in. --- Use the method @{#COMMANDCENTER.SetModeWWII}() to set the mode of communication to the WWII mode. --- --- In WWII mode, the Command Center will receive detected targets, and will select for each target the closest --- nearby Reference Zone. This allows pilots to navigate easier through the battle field readying for combat. --- --- The Reference Zones need to be set by the Mission Designer in the Mission Editor. --- Reference Zones are set by normal trigger zones. One can color the zones in a specific color, --- and the radius of the zones doesn't matter, only the point is important. Place the center of these Reference Zones at --- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc). --- The trigger zones indicating a Reference Zone need to follow a specific syntax. --- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object, --- followed by a #, followed by a symbolic name of the Reference Zone. --- A few examples: --- --- * A church at Tskinvali would be indicated as: *Church#Tskinvali* --- * A train station near Kobuleti would be indicated as: *Station#Kobuleti* --- --- The COMMANDCENTER class contains a method to indicate which trigger zones need to be used as Reference Zones. --- This is done by using the method @{#COMMANDCENTER.SetReferenceZones}(). --- For the moment, only one Reference Zone class can be specified, but in the future, more classes will become possible. --- --- ## 7. Tasks. --- --- ### 7.1. Automatically assign tasks. --- --- One of the most important roles of the command center is the management of tasks. --- The command center can assign automatically tasks to the players using the @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAssignTasks}() method. --- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned. --- For those players; the tasking is enabled to assign automatically a task. --- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off. --- --- ### 7.2. Automatically accept assigned tasks. --- --- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task. --- Use the method @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAcceptTasks}() to configure this behaviour. --- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and --- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds. --- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned. --- --- --- # 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 #COMMANDCENTER -COMMANDCENTER = { - ClassName = "COMMANDCENTER", - CommandCenterName = "", - CommandCenterCoalition = nil, - CommandCenterPositionable = nil, - Name = "", - ReferencePoints = {}, - ReferenceNames = {}, - CommunicationMode = "80", -} - - ---- --- @type COMMANDCENTER.AutoAssignMethods -COMMANDCENTER.AutoAssignMethods = { - ["Random"] = 1, - ["Distance"] = 2, - ["Priority"] = 3, - } - ---- The constructor takes an IDENTIFIABLE as the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable --- @param #string CommandCenterName --- @return #COMMANDCENTER -function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) - - local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER - - self.CommandCenterPositionable = CommandCenterPositionable - self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() - self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() - - self.Missions = {} - - self:SetAutoAssignTasks( false ) - self:SetAutoAcceptTasks( true ) - self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) - self:SetFlashStatus( false ) - self:SetMessageDuration(10) - - self:HandleEvent( EVENTS.Birth, - -- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - if EventData.IniObjectCategory == 1 then - local EventGroup = GROUP:Find( EventData.IniDCSGroup ) - --self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } ) - if EventGroup and EventGroup:IsAlive() and self:HasGroup( EventGroup ) then - local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() ) - local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu ) - local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup ) - local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup ) - --self:ReportSummary( EventGroup ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - end - self:SetMenu() - end - end - - end - ) - --- -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. --- -- For these elements, it will= --- -- - Set the correct menu. --- -- - Assign the PlayerUnit to the Task if required. --- -- - Send a message to the other players in the group that this player has joined. --- self:HandleEvent( EVENTS.PlayerEnterUnit, --- -- @param #COMMANDCENTER self --- -- @param Core.Event#EVENTDATA EventData --- function( self, EventData ) --- local PlayerUnit = EventData.IniUnit --- for MissionID, Mission in pairs( self:GetMissions() ) do --- local Mission = Mission -- Tasking.Mission#MISSION --- local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! --- Mission:JoinUnit( PlayerUnit, PlayerGroup ) --- end --- self:SetMenu() --- end --- ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.MissionEnd, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:Stop() - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.PlayerLeaveUnit, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsENGAGED() then - Mission:AbortUnit( PlayerUnit ) - end - end - end - ) - - -- Handle when a player crashes ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.Crash, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsENGAGED() then - Mission:CrashUnit( PlayerUnit ) - end - end - end - ) - - self:SetMenu() - - _SETTINGS:SetSystemMenu( CommandCenterPositionable ) - - self:SetCommandMenu() - - return self -end - ---- Gets the name of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetName() - - return self.CommandCenterName -end - ---- Gets the text string of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetText() - - return "Command Center [" .. self.CommandCenterName .. "]" -end - ---- Gets the short text string of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetShortText() - - return "CC [" .. self.CommandCenterName .. "]" -end - - ---- Gets the coalition of the command center. --- @param #COMMANDCENTER self --- @return #number Coalition of the command center. -function COMMANDCENTER:GetCoalition() - - return self.CommandCenterCoalition -end - - ---- Gets the POSITIONABLE of the HQ command center. --- @param #COMMANDCENTER self --- @return Wrapper.Positionable#POSITIONABLE -function COMMANDCENTER:GetPositionable() - return self.CommandCenterPositionable -end - ---- Get the Missions governed by the HQ command center. --- @param #COMMANDCENTER self --- @return #list -function COMMANDCENTER:GetMissions() - - return self.Missions or {} -end - ---- Add a MISSION to be governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:AddMission( Mission ) - - self.Missions[Mission] = Mission - - return Mission -end - ---- Removes a MISSION to be governed by the HQ command center. --- The given Mission is not nilified. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:RemoveMission( Mission ) - - self.Missions[Mission] = nil - - return Mission -end - ---- Set special Reference Zones known by the Command Center to guide airborne pilots during WWII. --- --- These Reference Zones are normal trigger zones, with a special naming. --- The Reference Zones need to be set by the Mission Designer in the Mission Editor. --- Reference Zones are set by normal trigger zones. One can color the zones in a specific color, --- and the radius of the zones doesn't matter, only the center of the zone is important. Place the center of these Reference Zones at --- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc). --- The trigger zones indicating a Reference Zone need to follow a specific syntax. --- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object, --- followed by a #, followed by a symbolic name of the Reference Zone. --- A few examples: --- --- * A church at Tskinvali would be indicated as: *Church#Tskinvali* --- * A train station near Kobuleti would be indicated as: *Station#Kobuleti* --- --- Taking the above example, this is how this method would be used: --- --- CC:SetReferenceZones( "Church" ) --- CC:SetReferenceZones( "Station" ) --- --- --- @param #COMMANDCENTER self --- @param #string ReferenceZonePrefix The name before the #-mark indicating the class of the Reference Zones. --- @return #COMMANDCENTER -function COMMANDCENTER:SetReferenceZones( ReferenceZonePrefix ) - local MatchPattern = "(.*)#(.*)" - self:F( { MatchPattern = MatchPattern } ) - for ReferenceZoneName in pairs( _DATABASE.ZONENAMES ) do - local ZoneName, ReferenceName = string.match( ReferenceZoneName, MatchPattern ) - self:F( { ZoneName = ZoneName, ReferenceName = ReferenceName } ) - if ZoneName and ReferenceName and ZoneName == ReferenceZonePrefix then - self.ReferencePoints[ReferenceZoneName] = ZONE:New( ReferenceZoneName ) - self.ReferenceNames[ReferenceZoneName] = ReferenceName - end - end - return self -end - ---- Set the commandcenter operations in WWII mode --- This will disable LL, MGRS, BRA, BULLS navigatin messages sent by the Command Center, --- and will be replaced by a navigation using Reference Zones. --- It will also disable the settings at the settings menu for these. --- @param #COMMANDCENTER self --- @return #COMMANDCENTER -function COMMANDCENTER:SetModeWWII() - self.CommunicationMode = "WWII" - return self -end - - ---- Returns if the commandcenter operations is in WWII mode --- @param #COMMANDCENTER self --- @return #boolean true if in WWII mode. -function COMMANDCENTER:IsModeWWII() - return self.CommunicationMode == "WWII" -end - - - - ---- Sets the menu structure of the Missions governed by the HQ command center. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetMenu() - self:F2() - - local MenuTime = timer.getTime() - for MissionID, Mission in pairs( self:GetMissions() or {} ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:SetMenu( MenuTime ) - end - - for MissionID, Mission in pairs( self:GetMissions() or {} ) do - Mission = Mission -- Tasking.Mission#MISSION - Mission:RemoveMenu( MenuTime ) - end - -end - ---- Gets the commandcenter menu structure governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Group#Group TaskGroup Task Group. --- @return Core.Menu#MENU_COALITION -function COMMANDCENTER:GetMenu( TaskGroup ) - - local MenuTime = timer.getTime() - - self.CommandCenterMenus = self.CommandCenterMenus or {} - local CommandCenterMenu - - local CommandCenterText = self:GetText() - CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ):SetTime(MenuTime) - self.CommandCenterMenus[TaskGroup] = CommandCenterMenu - - if self.AutoAssignTasks == false then - local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask") - end - CommandCenterMenu:Remove( MenuTime, "AutoTask" ) - - return self.CommandCenterMenus[TaskGroup] -end - - ---- Assigns a random task to a TaskGroup. --- @param #COMMANDCENTER self --- @return #COMMANDCENTER -function COMMANDCENTER:AssignTask( TaskGroup ) - - local Tasks = {} - local AssignPriority = 99999999 - local AutoAssignMethod = self.AutoAssignMethod - - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local MissionTasks = Mission:GetGroupTasks( TaskGroup ) - for MissionTaskName, MissionTask in pairs( MissionTasks or {} ) do - local MissionTask = MissionTask -- Tasking.Task#TASK - if MissionTask:IsStatePlanned() or MissionTask:IsStateReplanned() or MissionTask:IsStateAssigned() then - local TaskPriority = MissionTask:GetAutoAssignPriority( self.AutoAssignMethod, self, TaskGroup ) - if TaskPriority < AssignPriority then - AssignPriority = TaskPriority - Tasks = {} - end - if TaskPriority == AssignPriority then - Tasks[#Tasks+1] = MissionTask - end - end - end - end - - local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK - - if Task then - - self:T( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority ) - - if not self.AutoAcceptTasks == true then - Task:SetAutoAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) ) - end - - Task:AssignToGroup( TaskGroup ) - - end - -end - - ---- Sets the menu of the command center. --- This command is called within the :New() method. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetCommandMenu() - - local MenuTime = timer.getTime() - - if self.CommandCenterPositionable and self.CommandCenterPositionable:IsInstanceOf(GROUP) then - local CommandCenterText = self:GetText() - local CommandCenterMenu = MENU_GROUP:New( self.CommandCenterPositionable, CommandCenterText ):SetTime(MenuTime) - - if self.AutoAssignTasks == false then - local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask") - else - local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):SetTime(MenuTime):SetTag("AutoTask") - end - CommandCenterMenu:Remove( MenuTime, "AutoTask" ) - end - -end - - - ---- Automatically assigns tasks to all TaskGroups. --- One of the most important roles of the command center is the management of tasks. --- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned. --- For those players; the tasking is enabled to assign automatically a task. --- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off. --- @param #COMMANDCENTER self --- @param #boolean AutoAssign true for ON and false or nil for OFF. -function COMMANDCENTER:SetAutoAssignTasks( AutoAssign ) - - self.AutoAssignTasks = AutoAssign or false - - if self.AutoAssignTasks == true then - self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self ) - else - self:ScheduleStop() - -- FF this is not the schedule ID - --self:ScheduleStop( self.AssignTasks ) - end - -end - ---- Automatically accept tasks for all TaskGroups. --- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task. --- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and --- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds. --- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned. --- @param #COMMANDCENTER self --- @param #boolean AutoAccept true for ON and false or nil for OFF. -function COMMANDCENTER:SetAutoAcceptTasks( AutoAccept ) - - self.AutoAcceptTasks = AutoAccept or false - -end - - ---- Define the method to be used to assign automatically a task from the available tasks in the mission. --- There are 3 types of methods that can be applied for the moment: --- --- 1. Random - assigns a random task in the mission to the player. --- 2. Distance - assigns a task based on a distance evaluation from the player. The closest are to be assigned first. --- 3. Priority - assigns a task based on the priority as defined by the mission designer, using the SetTaskPriority parameter. --- --- The different task classes implement the logic to determine the priority of automatic task assignment to a player, depending on one of the above methods. --- The method @{Tasking.Task#TASK.GetAutoAssignPriority} calculate the priority of the tasks to be assigned. --- @param #COMMANDCENTER self --- @param #COMMANDCENTER.AutoAssignMethods AutoAssignMethod A selection of an assign method from the COMMANDCENTER.AutoAssignMethods enumeration. -function COMMANDCENTER:SetAutoAssignMethod( AutoAssignMethod ) - - self.AutoAssignMethod = AutoAssignMethod or COMMANDCENTER.AutoAssignMethods.Random - -end - ---- Automatically assigns tasks to all TaskGroups. --- @param #COMMANDCENTER self -function COMMANDCENTER:AssignTasks() - - local GroupSet = self:AddGroups() - - for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - - if TaskGroup:IsAlive() then - self:GetMenu( TaskGroup ) - - if self:IsGroupAssigned( TaskGroup ) then - else - -- Only groups with planes or helicopters will receive automatic tasks. - -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 - if TaskGroup:IsAir() then - self:AssignTask( TaskGroup ) - end - end - end - end - -end - - ---- Get all the Groups active within the command center. --- @param #COMMANDCENTER self --- @return Core.Set#SET_GROUP The set of groups active within the command center. -function COMMANDCENTER:AddGroups() - - local GroupSet = SET_GROUP:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - GroupSet = Mission:AddGroups( GroupSet ) - end - - return GroupSet -end - - ---- Checks of the TaskGroup has a Task. --- @param #COMMANDCENTER self --- @return #boolean When true, the TaskGroup has a Task, otherwise the returned value will be false. -function COMMANDCENTER:IsGroupAssigned( TaskGroup ) - - local Assigned = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsGroupAssigned( TaskGroup ) then - Assigned = true - break - end - end - - return Assigned -end - - ---- Checks of the command center has the given MissionGroup. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP MissionGroup The group active within one of the missions governed by the command center. --- @return #boolean -function COMMANDCENTER:HasGroup( MissionGroup ) - - local Has = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:HasGroup( MissionGroup ) then - Has = true - break - end - end - - return Has -end - ---- Let the command center send a Message to all players. --- @param #COMMANDCENTER self --- @param #string Message The message text. -function COMMANDCENTER:MessageToAll( Message ) - - self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) - -end - ---- Let the command center send a message to the MessageGroup. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. -function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - - self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) - -end - ---- Let the command center send a message to the MessageGroup. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. --- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message. -function COMMANDCENTER:MessageTypeToGroup( Message, MessageGroup, MessageType ) - - self:GetPositionable():MessageTypeToGroup( Message, MessageType, MessageGroup, self:GetShortText() ) - -end - ---- Let the command center send a message to the coalition of the command center. --- @param #COMMANDCENTER self --- @param #string Message The message text. -function COMMANDCENTER:MessageToCoalition( Message ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - - self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) - -end - - ---- Let the command center send a message of a specified type to the coalition of the command center. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message. -function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - - self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() ) - -end - - ---- Let the command center send a report of the status of all missions to a group. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. -function COMMANDCENTER:ReportSummary( ReportGroup ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - Report:Add( string.format( '%s - Report Summary Missions', Name ) ) - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportSummary( ReportGroup ) ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - ---- Let the command center send a report of the players of all missions to a group. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. -function COMMANDCENTER:ReportMissionsPlayers( ReportGroup ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - Report:Add( "Players active in all missions." ) - - for MissionID, MissionData in pairs( self.Missions ) do - local Mission = MissionData -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportPlayersPerTask(ReportGroup) ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - ---- Let the command center send a report of the status of a task to a group. --- Report the details of a Mission, listing the Mission, and all the Task details. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. --- @param Tasking.Task#TASK Task The task to be reported. -function COMMANDCENTER:ReportDetails( ReportGroup, Task ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportDetails() ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - - ---- Let the command center flash a report of the status of the subscribed task to a group. --- @param #COMMANDCENTER self --- @param Flash #boolean -function COMMANDCENTER:SetFlashStatus( Flash ) - self:F() - - self.FlashStatus = Flash and true -end - ---- Duration a command center message is shown. --- @param #COMMANDCENTER self --- @param seconds #number -function COMMANDCENTER:SetMessageDuration(seconds) - self:F() - - self.MessageDuration = 10 or seconds -end diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua deleted file mode 100644 index 81dd1f882..000000000 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ /dev/null @@ -1,402 +0,0 @@ ---- **Tasking** - This module contains the DETECTION_MANAGER class and derived classes. --- --- === --- --- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. --- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. --- --- 1.1) DETECTION_MANAGER constructor: --- ----------------------------------- --- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. --- --- 1.2) DETECTION_MANAGER reporting: --- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{#DETECTION_MANAGER.SetRefreshTimeInterval}(). --- To control how long a reporting message is displayed, use @{#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{#DETECTION_MANAGER.StartReporting}() and @{#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{#DETECTION_MANAGER.ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{#DETECTION_REPORTING} class, extends @{#DETECTION_MANAGER} --- === --- The @{#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class. --- --- 2.1) DETECTION_REPORTING constructor: --- ------------------------------- --- The @{#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. --- --- --- # 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 --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module Tasking.DetectionManager --- @image Task_Detection_Manager.JPG - -do -- DETECTION MANAGER - - -- @type DETECTION_MANAGER - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players. - -- @extends Core.Fsm#FSM - - --- DETECTION_MANAGER class. - -- @field #DETECTION_MANAGER - DETECTION_MANAGER = { - ClassName = "DETECTION_MANAGER", - SetGroup = nil, - Detection = nil, - } - - -- @field Tasking.CommandCenter#COMMANDCENTER - DETECTION_MANAGER.CC = nil - - --- FAC constructor. - -- @param #DETECTION_MANAGER self - -- @param Core.Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:New( SetGroup, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_MANAGER - - self.SetGroup = SetGroup - self.Detection = Detection - - self:SetStartState( "Stopped" ) - self:AddTransition( "Stopped", "Start", "Started" ) - - --- Start Handler OnBefore for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnBeforeStart - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Start Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterStart - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Start Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] Start - -- @param #DETECTION_MANAGER self - - --- Start Asynchronous Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] __Start - -- @param #DETECTION_MANAGER self - -- @param #number Delay - - - - self:AddTransition( "Started", "Stop", "Stopped" ) - - --- Stop Handler OnBefore for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnBeforeStop - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Stop Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterStop - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Stop Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] Stop - -- @param #DETECTION_MANAGER self - - --- Stop Asynchronous Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] __Stop - -- @param #DETECTION_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "Success", "Started" ) - - --- Success Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterSuccess - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Failed", "Started" ) - - --- Failed Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterFailed - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Aborted", "Started" ) - - --- Aborted Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterAborted - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Cancelled", "Started" ) - - --- Cancelled Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterCancelled - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Report", "Started" ) - - self:SetRefreshTimeInterval( 30 ) - self:SetReportDisplayTime( 25 ) - - Detection:__Start( 3 ) - - return self - end - - function DETECTION_MANAGER:onafterStart( From, Event, To ) - self:Report() - end - - function DETECTION_MANAGER:onafterReport( From, Event, To ) - - self:__Report( -self._RefreshTimeInterval ) - - self:ProcessDetected( self.Detection ) - end - - --- Set the reporting time interval. - -- @param #DETECTION_MANAGER self - -- @param #number RefreshTimeInterval The interval in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval ) - self:F2() - - self._RefreshTimeInterval = RefreshTimeInterval - end - - - --- Set the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime - end - - --- Get the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. - function DETECTION_MANAGER:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime - end - - - --- Set a command center to communicate actions to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText ) - - local DispatcherMainMenu = MENU_MISSION:New( DispatcherMainMenuText, nil ) - local DispatcherMenu = MENU_MISSION_COMMAND:New( DispatcherMenuText, DispatcherMainMenu, - function() - self:ShowTacticalDisplay( self.Detection ) - end - ) - - return self - end - - - - - --- Set a command center to communicate actions to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:SetCommandCenter( CommandCenter ) - - self.CC = CommandCenter - - return self - end - - - --- Get the command center to communicate actions to the players. - -- @param #DETECTION_MANAGER self - -- @return Tasking.CommandCenter#COMMANDCENTER The command center. - function DETECTION_MANAGER:GetCommandCenter() - - return self.CC - end - - - --- Send an information message to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @param #table Squadron The squadron table. - -- @param #string Message The message to be sent. - -- @param #string SoundFile The name of the sound file .wav or .ogg. - -- @param #number SoundDuration The duration of the sound. - -- @param #string SoundPath The path pointing to the folder in the mission file. - -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Squadron, Message, DefenderGroup ) - - self:F( { Message = Message } ) - --- if not self.PreviousMessage or self.PreviousMessage ~= Message then --- self.PreviousMessage = Message --- if self.CC then --- self.CC:MessageToCoalition( Message ) --- end --- end - - if self.CC then - self.CC:MessageToCoalition( Message ) - end - - Message = Message:gsub( "°", " degrees " ) - Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" ) - - -- Here we handle the transmission of the voice over. - -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. - local RadioQueue = Squadron.RadioQueue -- Core.RadioSpeech#RADIOSPEECH - if RadioQueue then - local DefenderUnit = DefenderGroup:GetUnit(1) - if DefenderUnit and DefenderUnit:IsAlive() then - RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) - end - RadioQueue:Speak( Message, Squadron.Language ) - end - - return self - end - - - - --- Reports the detected items to the @{Core.Set#SET_GROUP}. - -- @param #DETECTION_MANAGER self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:ProcessDetected( Detection ) - - end - -end - - -do -- DETECTION_REPORTING - - --- DETECTION_REPORTING class. - -- @type DETECTION_REPORTING - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends #DETECTION_MANAGER - DETECTION_REPORTING = { - ClassName = "DETECTION_REPORTING", - } - - - --- DETECTION_REPORTING constructor. - -- @param #DETECTION_REPORTING self - -- @param Core.Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_AREAS Detection - -- @return #DETECTION_REPORTING self - function DETECTION_REPORTING:New( SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING - - self:Schedule( 1, 30 ) - return self - end - - --- Creates a string of the detected items in a @{Functional.Detection} object. - -- @param #DETECTION_MANAGER self - -- @param Core.Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object. - -- @return #DETECTION_MANAGER self - function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - local UnitType = DetectedUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) - end - - - - --- Reports the detected items to the @{Core.Set#SET_GROUP}. - -- @param #DETECTION_REPORTING self - -- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} object to where the report needs to go. - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object. - -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. - function DETECTION_REPORTING:ProcessDetected( Group, Detection ) - self:F2( Group ) - - local DetectedMsg = {} - for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) - end - local FACGroup = Detection:GetDetectionGroups() - FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) - - return true - end - -end - diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua deleted file mode 100644 index 6fa3da747..000000000 --- a/Moose Development/Moose/Tasking/Mission.lua +++ /dev/null @@ -1,1211 +0,0 @@ ---- **Tasking** - A mission models a goal to be achieved through the execution and completion of tasks by human players. --- --- **Features:** --- --- * A mission has a goal to be achieved, through the execution and completion of tasks of different categories by human players. --- * A mission manages these tasks. --- * A mission has a state, that indicates the fase of the mission. --- * A mission has a menu structure, that facilitates mission reports and tasking menus. --- * A mission can assign a task to a player. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Mission --- @image Task_Mission.JPG - ---- --- @type MISSION --- @field #MISSION.Clients _Clients --- @field Core.Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing --- @extends Core.Fsm#FSM - ---- Models goals to be achieved and can contain multiple tasks to be executed to achieve the goals. --- --- A mission contains multiple tasks and can be of different task types. --- These tasks need to be assigned to human players to be executed. --- --- A mission can have multiple states, which will evolve as the mission progresses during the DCS simulation. --- --- - **IDLE**: The mission is defined, but not started yet. No task has yet been joined by a human player as part of the mission. --- - **ENGAGED**: The mission is ongoing, players have joined tasks to be executed. --- - **COMPLETED**: The goals of the mission has been successfully reached, and the mission is flagged as completed. --- - **FAILED**: For a certain reason, the goals of the mission has not been reached, and the mission is flagged as failed. --- - **HOLD**: The mission was enaged, but for some reason it has been put on hold. --- --- Note that a mission goals need to be checked by a goal check trigger: @{#MISSION.OnBeforeMissionGoals}(), which may return false if the goal has not been reached. --- This goal is checked automatically by the mission object every x seconds. --- --- - @{#MISSION.Start}() or @{#MISSION.__Start}() will start the mission, and will bring it from **IDLE** state to **ENGAGED** state. --- - @{#MISSION.Stop}() or @{#MISSION.__Stop}() will stop the mission, and will bring it from **ENGAGED** state to **IDLE** state. --- - @{#MISSION.Complete}() or @{#MISSION.__Complete}() will complete the mission, and will bring the mission state to **COMPLETED**. --- Note that the mission must be in state **ENGAGED** to be able to complete the mission. --- - @{#MISSION.Fail}() or @{#MISSION.__Fail}() will fail the mission, and will bring the mission state to **FAILED**. --- Note that the mission must be in state **ENGAGED** to be able to fail the mission. --- - @{#MISSION.Hold}() or @{#MISSION.__Hold}() will hold the mission, and will bring the mission state to **HOLD**. --- Note that the mission must be in state **ENGAGED** to be able to hold the mission. --- Re-engage the mission using the engage trigger. --- --- The following sections provide an overview of the most important methods that can be used as part of a mission object. --- Note that the @{Tasking.CommandCenter} system is using most of these methods to manage the missions in its system. --- --- ## 1. Create a mission object. --- --- - @{#MISSION.New}(): Creates a new MISSION object. --- --- ## 2. Mission task management. --- --- Missions maintain tasks, which can be added or removed, or enquired. --- --- - @{#MISSION.AddTask}(): Adds a task to the mission. --- - @{#MISSION.RemoveTask}(): Removes a task from the mission. --- --- ## 3. Mission detailed methods. --- --- Various methods are added to manage missions. --- --- ### 3.1. Naming and description. --- --- There are several methods that can be used to retrieve the properties of a mission: --- --- - Use the method @{#MISSION.GetName}() to retrieve the name of the mission. --- This is the name given as part of the @{#MISSION.New}() constructor. --- --- A textual description can be retrieved that provides the mission name to be used within message communication: --- --- - @{#MISSION.GetShortText}() returns the mission name as `Mission "MissionName"`. --- - @{#MISSION.GetText}() returns the mission name as `Mission "MissionName (MissionPriority)"`. A longer version including the priority text of the mission. --- --- ### 3.2. Get task information. --- --- - @{#MISSION.GetTasks}(): Retrieves a list of the tasks controlled by the mission. --- - @{#MISSION.GetTask}(): Retrieves a specific task controlled by the mission. --- - @{#MISSION.GetTasksRemaining}(): Retrieve a list of the tasks that aren't finished or failed, and are governed by the mission. --- - @{#MISSION.GetGroupTasks}(): Retrieve a list of the tasks that can be assigned to a @{Wrapper.Group}. --- - @{#MISSION.GetTaskTypes}(): Retrieve a list of the different task types governed by the mission. --- --- ### 3.3. Get the command center. --- --- - @{#MISSION.GetCommandCenter}(): Retrieves the @{Tasking.CommandCenter} governing the mission. --- --- ### 3.4. Get the groups active in the mission as a @{Core.Set}. --- --- - @{#MISSION.GetGroups}(): Retrieves a @{Core.Set#SET_GROUP} of all the groups active in the mission (as part of the tasks). --- --- ### 3.5. Get the names of the players. --- --- - @{#MISSION.GetPlayerNames}(): Retrieves the list of the players that were active within th mission.. --- --- ## 4. Menu management. --- --- A mission object is able to manage its own menu structure. Use the @{#MISSION.GetMenu}() and @{#MISSION.SetMenu}() to manage the underlying submenu --- structure managing the tasks of the mission. --- --- ## 5. Reporting management. --- --- Several reports can be generated for a mission, and will return a text string that can be used to display using the @{Core.Message} system. --- --- - @{#MISSION.ReportBriefing}(): Generates the briefing for the mission. --- - @{#MISSION.ReportOverview}(): Generates an overview of the tasks and status of the mission. --- - @{#MISSION.ReportDetails}(): Generates a detailed report of the tasks of the mission. --- - @{#MISSION.ReportSummary}(): Generates a summary report of the tasks of the mission. --- - @{#MISSION.ReportPlayersPerTask}(): Generates a report showing the active players per task. --- - @{#MISSION.ReportPlayersProgress}(): Generates a report showing the task progress per player. --- --- --- # 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 #MISSION -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", - AssignedGroups = {}, -} - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{Wrapper.Client#CLIENT}. --- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. --- @return #MISSION self -function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM - - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.CommandCenter = CommandCenter - CommandCenter:AddMission( self ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - self.Tasks = {} - self.TaskNumber = 0 - self.PlayerNames = {} -- These are the players that achieved progress in the mission. - - self:SetStartState( "IDLE" ) - - self:AddTransition( "IDLE", "Start", "ENGAGED" ) - - --- OnLeave Transition Handler for State IDLE. - -- @function [parent=#MISSION] OnLeaveIDLE - -- @param #MISSION self - -- @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 IDLE. - -- @function [parent=#MISSION] OnEnterIDLE - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnLeave Transition Handler for State ENGAGED. - -- @function [parent=#MISSION] OnLeaveENGAGED - -- @param #MISSION self - -- @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 ENGAGED. - -- @function [parent=#MISSION] OnEnterENGAGED - -- @param #MISSION self - -- @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 Start. - -- @function [parent=#MISSION] OnBeforeStart - -- @param #MISSION self - -- @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=#MISSION] OnAfterStart - -- @param #MISSION self - -- @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=#MISSION] Start - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] __Start - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "ENGAGED", "Stop", "IDLE" ) - - --- OnLeave Transition Handler for State IDLE. - -- @function [parent=#MISSION] OnLeaveIDLE - -- @param #MISSION self - -- @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 IDLE. - -- @function [parent=#MISSION] OnEnterIDLE - -- @param #MISSION self - -- @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=#MISSION] OnBeforeStop - -- @param #MISSION self - -- @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=#MISSION] OnAfterStop - -- @param #MISSION self - -- @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=#MISSION] Stop - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] __Stop - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "ENGAGED", "Complete", "COMPLETED" ) - - --- OnLeave Transition Handler for State COMPLETED. - -- @function [parent=#MISSION] OnLeaveCOMPLETED - -- @param #MISSION self - -- @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 COMPLETED. - -- @function [parent=#MISSION] OnEnterCOMPLETED - -- @param #MISSION self - -- @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 Complete. - -- @function [parent=#MISSION] OnBeforeComplete - -- @param #MISSION self - -- @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 Complete. - -- @function [parent=#MISSION] OnAfterComplete - -- @param #MISSION self - -- @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 Complete. - -- @function [parent=#MISSION] Complete - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] __Complete - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Fail", "FAILED" ) - - --- OnLeave Transition Handler for State FAILED. - -- @function [parent=#MISSION] OnLeaveFAILED - -- @param #MISSION self - -- @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 FAILED. - -- @function [parent=#MISSION] OnEnterFAILED - -- @param #MISSION self - -- @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 Fail. - -- @function [parent=#MISSION] OnBeforeFail - -- @param #MISSION self - -- @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 Fail. - -- @function [parent=#MISSION] OnAfterFail - -- @param #MISSION self - -- @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 Fail. - -- @function [parent=#MISSION] Fail - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] __Fail - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "*", "MissionGoals", "*" ) - - --- MissionGoals Handler OnBefore for MISSION - -- @function [parent=#MISSION] OnBeforeMissionGoals - -- @param #MISSION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- MissionGoals Handler OnAfter for MISSION - -- @function [parent=#MISSION] OnAfterMissionGoals - -- @param #MISSION self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- MissionGoals Trigger for MISSION - -- @function [parent=#MISSION] MissionGoals - -- @param #MISSION self - - --- MissionGoals Asynchronous Trigger for MISSION - -- @function [parent=#MISSION] __MissionGoals - -- @param #MISSION self - -- @param #number Delay - - -- Private implementations - - CommandCenter:SetMenu() - - return self -end - - - - ---- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onenterCOMPLETED( From, Event, To ) - - self:GetCommandCenter():MessageTypeToCoalition( self:GetText() .. " has been completed! Good job guys!", MESSAGE.Type.Information ) -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - - ---- Gets the mission text. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetText() - return string.format( 'Mission "%s (%s)"', self.Name, self.MissionPriority ) -end - - ---- Gets the short mission text. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetShortText() - return string.format( 'Mission "%s"', self.Name ) -end - - ---- Add a Unit to join the Mission. --- For each Task within the Mission, the Unit is joined with the Task. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:JoinUnit( PlayerUnit, PlayerGroup ) - self:T( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:JoinUnit( PlayerUnit, PlayerGroup ) then - PlayerUnitAdded = true - end - end - - return PlayerUnitAdded -end - ---- Aborts a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @return #MISSION -function MISSION:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerGroup = PlayerUnit:GetGroup() - Task:AbortGroup( PlayerGroup ) - end - - return self -end - ---- Handles a crash of a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. --- @return #MISSION -function MISSION:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerGroup = PlayerUnit:GetGroup() - Task:CrashGroup( PlayerGroup ) - end - - return self -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - ---- Gets the groups for which TASKS are given in the mission --- @param #MISSION self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function MISSION:GetGroups() - - return self:AddGroups() - -end - ---- Adds the groups for which TASKS are given in the mission --- @param #MISSION self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function MISSION:AddGroups( GroupSet ) - - GroupSet = GroupSet or SET_GROUP:New() - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - GroupSet = Task:AddGroups( GroupSet ) - end - - return GroupSet - -end - - ---- Sets the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:SetMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - local MenuCount = {} - --for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskType = Task:GetType() - MenuCount[TaskType] = MenuCount[TaskType] or 1 - if MenuCount[TaskType] <= 10 then - Task:SetMenu( MenuTime ) - MenuCount[TaskType] = MenuCount[TaskType] + 1 - end - end -end - ---- Removes the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:RemoveMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - for _, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Task:RemoveMenu( MenuTime ) - end -end - - - -do -- Group Assignment - - --- Returns if the @{Tasking.Mission} is assigned to the Group. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #boolean - function MISSION:IsGroupAssigned( MissionGroup ) - - local MissionGroupName = MissionGroup:GetName() - - if self.AssignedGroups[MissionGroupName] == MissionGroup then - self:T2( { "Mission is assigned to:", MissionGroup:GetName() } ) - return true - end - - self:T2( { "Mission is not assigned to:", MissionGroup:GetName() } ) - return false - end - - - --- Set @{Wrapper.Group} assigned to the @{Tasking.Mission}. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #MISSION - function MISSION:SetGroupAssigned( MissionGroup ) - - local MissionName = self:GetName() - local MissionGroupName = MissionGroup:GetName() - - self.AssignedGroups[MissionGroupName] = MissionGroup - self:T( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) ) - - return self - end - - --- Clear the @{Wrapper.Group} assignment from the @{Tasking.Mission}. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #MISSION - function MISSION:ClearGroupAssignment( MissionGroup ) - - local MissionName = self:GetName() - local MissionGroupName = MissionGroup:GetName() - - self.AssignedGroups[MissionGroupName] = nil - --self:E( string.format( "Mission %s is unassigned to %s", MissionName, MissionGroupName ) ) - - return self - end - -end - ---- Gets the COMMANDCENTER. --- @param #MISSION self --- @return Tasking.CommandCenter#COMMANDCENTER -function MISSION:GetCommandCenter() - return self.CommandCenter -end - - ---- Removes a Task menu. --- @param #MISSION self --- @param Tasking.Task#TASK Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the root mission menu for the TaskGroup. Obsolete?! Originally no reference to TaskGroup parameter! --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup Task group. --- @return Core.Menu#MENU_COALITION self -function MISSION:GetRootMenu( TaskGroup ) -- R2.2 - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup ) - - local MissionName = self:GetText() - --local MissionMenu = CommandCenterMenu:GetMenu( MissionName ) - - self.MissionMenu = MENU_COALITION:New( self.MissionCoalition, MissionName, CommandCenterMenu ) - - return self.MissionMenu -end - ---- Gets the mission menu for the TaskGroup. --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup Task group. --- @return Core.Menu#MENU_COALITION self -function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup ) - - self.MissionGroupMenu = self.MissionGroupMenu or {} - self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {} - - local GroupMenu = self.MissionGroupMenu[TaskGroup] - - local MissionText = self:GetText() - self.MissionMenu = MENU_GROUP:New( TaskGroup, MissionText, CommandCenterMenu ) - - GroupMenu.BriefingMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", self.MissionMenu, self.MenuReportBriefing, self, TaskGroup ) - - GroupMenu.MarkTasks = MENU_GROUP_COMMAND:New( TaskGroup, "Mark Task Locations on Map", self.MissionMenu, self.MarkTargetLocations, self, TaskGroup ) - GroupMenu.TaskReportsMenu = MENU_GROUP:New( TaskGroup, "Task Reports", self.MissionMenu ) - GroupMenu.ReportTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks Summary", GroupMenu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup ) - GroupMenu.ReportPlannedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" ) - GroupMenu.ReportAssignedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" ) - GroupMenu.ReportSuccessTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" ) - GroupMenu.ReportFailedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" ) - GroupMenu.ReportHeldTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" ) - - GroupMenu.PlayerReportsMenu = MENU_GROUP:New( TaskGroup, "Statistics Reports", self.MissionMenu ) - GroupMenu.ReportMissionHistory = MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup ) - GroupMenu.ReportPlayersPerTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup ) - - return self.MissionMenu -end - - - - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskName The Name of the @{Tasking.Task} within the @{Tasking.Mission}. --- @return Tasking.Task#TASK The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Return the next @{Tasking.Task} ID to be completed within the @{Tasking.Mission}. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:GetNextTaskID( Task ) - - self.TaskNumber = self.TaskNumber + 1 - - return self.TaskNumber -end - - ---- Register a @{Tasking.Task} to be completed within the @{Tasking.Mission}. --- Note that there can be multiple @{Tasking.Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:T( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } ) - - self.Tasks[TaskName] = Task - - self:GetCommandCenter():SetMenu() - - return Task -end - - ---- Removes a @{Tasking.Task} to be completed within the @{Tasking.Mission}. --- Note that there can be multiple @{Tasking.Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - self:T( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } ) - - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - collectgarbage() - - self:GetCommandCenter():SetMenu() - - return nil -end - ---- Is the @{Tasking.Mission} **COMPLETED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsCOMPLETED() - return self:Is( "COMPLETED" ) -end - ---- Is the @{Tasking.Mission} **IDLE**. --- @param #MISSION self --- @return #boolean -function MISSION:IsIDLE() - return self:Is( "IDLE" ) -end - ---- Is the @{Tasking.Mission} **ENGAGED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsENGAGED() - return self:Is( "ENGAGED" ) -end - ---- Is the @{Tasking.Mission} **FAILED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsFAILED() - return self:Is( "FAILED" ) -end - ---- Is the @{Tasking.Mission} **HOLD**. --- @param #MISSION self --- @return #boolean -function MISSION:IsHOLD() - return self:Is( "HOLD" ) -end - ---- Validates if the Mission has a Group --- @param #MISSION --- @return #boolean true if the Mission has a Group. -function MISSION:HasGroup( TaskGroup ) - local Has = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Has = true - break - end - end - - return Has -end - --- @param #MISSION self --- @return #number -function MISSION:GetTasksRemaining() - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:IsStateSuccess() or Task:IsStateFailed() then - else - TasksRemaining = TasksRemaining + 1 - end - end - return TasksRemaining -end - --- @param #MISSION self --- @return #number -function MISSION:GetTaskTypes() - -- Determine how many tasks are remaining. - local TaskTypeList = {} - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskType = Task:GetType() - TaskTypeList[TaskType] = TaskType - end - return TaskTypeList -end - - -function MISSION:AddPlayerName( PlayerName ) - self.PlayerNames = self.PlayerNames or {} - self.PlayerNames[PlayerName] = PlayerName - return self -end - -function MISSION:GetPlayerNames() - return self.PlayerNames -end - - ---- Create a briefing report of the Mission. --- @param #MISSION self --- @return #string -function MISSION:ReportBriefing() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Mission Briefing Report', Name, Status ) ) - - Report:Add( self.MissionBriefing ) - - return Report:Text() -end - - ------ Create a status report of the Mission. ----- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks. ----- ----- Mission "" - Status "" ----- - Task Types: , ----- - Planned Tasks (xp) ----- - Assigned Tasks(xp) ----- - Success Tasks (xp) ----- - Hold Tasks (xp) ----- - Cancelled Tasks (xp) ----- - Aborted Tasks (xp) ----- - Failed Tasks (xp) ----- --- @param #MISSION self ----- @return #string ---function MISSION:ReportSummary() --- --- local Report = REPORT:New() --- --- -- List the name of the mission. --- local Name = self:GetText() --- --- -- Determine the status of the mission. --- local Status = "<" .. self:GetState() .. ">" --- --- Report:Add( string.format( '%s - Status "%s"', Name, Status ) ) --- --- local TaskTypes = self:GetTaskTypes() --- --- Report:Add( string.format( " - Task Types: %s", table.concat(TaskTypes, ", " ) ) ) --- --- local TaskStatusList = { "Planned", "Assigned", "Success", "Hold", "Cancelled", "Aborted", "Failed" } --- --- for TaskStatusID, TaskStatus in pairs( TaskStatusList ) do --- local TaskCount = 0 --- local TaskPlayerCount = 0 --- -- Determine how many tasks are remaining. --- for TaskID, Task in pairs( self:GetTasks() ) do --- local Task = Task -- Tasking.Task#TASK --- if Task:Is( TaskStatus ) then --- TaskCount = TaskCount + 1 --- TaskPlayerCount = TaskPlayerCount + Task:GetPlayerCount() --- end --- end --- if TaskCount > 0 then --- Report:Add( string.format( " - %02d %s Tasks (%dp)", TaskCount, TaskStatus, TaskPlayerCount ) ) --- end --- end --- --- return Report:Text() ---end - - ---- Create an active player report of the Mission. --- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks. --- --- Mission "" - - Active Players Report --- - Player ": Task , Task --- - Player : Task , Task --- - .. --- --- @param #MISSION self --- @return #string -function MISSION:ReportPlayersPerTask( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Players per Task Report', Name, Status ) ) - - local PlayerList = {} - - -- Determine how many tasks are remaining. - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerNames = Task:GetPlayerNames() - for PlayerName, PlayerGroup in pairs( PlayerNames ) do - PlayerList[PlayerName] = Task:GetName() - end - - end - - for PlayerName, TaskName in pairs( PlayerList ) do - Report:Add( string.format( ' - Player (%s): Task "%s"', PlayerName, TaskName ) ) - end - - return Report:Text() -end - ---- Create an Mission Progress report of the Mission. --- This reports provides a one liner per player of the mission achievements per task. --- --- Mission "" - - Active Players Report --- - Player : Task : --- - Player : Task : --- - .. --- --- @param #MISSION self --- @return #string -function MISSION:ReportPlayersProgress( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Players per Task Progress Report', Name, Status ) ) - - local PlayerList = {} - - -- Determine how many tasks are remaining. - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskName = Task:GetName() - local Goal = Task:GetGoal() - PlayerList[TaskName] = PlayerList[TaskName] or {} - if Goal then - local TotalContributions = Goal:GetTotalContributions() - local PlayerContributions = Goal:GetPlayerContributions() - self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } ) - for PlayerName, PlayerContribution in pairs( PlayerContributions ) do - PlayerList[TaskName][PlayerName] = string.format( 'Player (%s): Task "%s": %d%%', PlayerName, TaskName, PlayerContributions[PlayerName] * 100 / TotalContributions ) - end - else - PlayerList[TaskName]["_"] = string.format( 'Player (---): Task "%s": %d%%', TaskName, 0 ) - end - end - - for TaskName, TaskData in pairs( PlayerList ) do - for PlayerName, TaskText in pairs( TaskData ) do - Report:Add( string.format( ' - %s', TaskText ) ) - end - end - - return Report:Text() -end - - ---- Mark all the target locations on the Map. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function MISSION:MarkTargetLocations( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!', Name, Status ) ) - - -- Determine how many tasks are remaining. - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - Task:MenuMarkToGroup( ReportGroup ) - end - - return Report:Text() -end - - ---- Create a summary report of the Mission (one line). --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function MISSION:ReportSummary( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Task Overview Report', Name, Status ) ) - - -- Determine how many tasks are remaining. - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( "- " .. Task:ReportSummary( ReportGroup ) ) - end - - return Report:Text() -end - ---- Create a overview report of the Mission (multiple lines). --- @param #MISSION self --- @return #string -function MISSION:ReportOverview( ReportGroup, TaskStatus ) - - self:F( { TaskStatus = TaskStatus } ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - %s Tasks Report', Name, Status, TaskStatus ) ) - - -- Determine how many tasks are remaining. - local Tasks = 0 - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - if Task:Is( TaskStatus ) then - Report:Add( string.rep( "-", 140 ) ) - Report:Add( Task:ReportOverview( ReportGroup ) ) - end - Tasks = Tasks + 1 - if Tasks >= 8 then - break - end - end - - return Report:Text() -end - ---- Create a detailed report of the Mission, listing all the details of the Task. --- @param #MISSION self --- @return #string -function MISSION:ReportDetails( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Task Detailed Report', Name, Status ) ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( string.rep( "-", 140 ) ) - Report:Add( Task:ReportDetails( ReportGroup ) ) - end - - return Report:Text() -end - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{Tasking.Task#TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - - return self.Tasks or {} -end - ---- Get the relevant tasks of a TaskGroup. --- @param #MISSION --- @param Wrapper.Group#GROUP TaskGroup --- @return #list -function MISSION:GetGroupTasks( TaskGroup ) - - local Tasks = {} - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Tasks[#Tasks+1] = Task - end - end - - return Tasks -end - - ---- Reports the briefing. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup The group to which the report needs to be sent. -function MISSION:MenuReportBriefing( ReportGroup ) - - local Report = self:ReportBriefing() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Briefing ) -end - - ---- Mark all the targets of the Mission on the Map. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuMarkTargetLocations( ReportGroup ) - - local Report = self:MarkTargetLocations( ReportGroup ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - ---- Report the task summary. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportTasksSummary( ReportGroup ) - - local Report = self:ReportSummary( ReportGroup ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - - --- @param #MISSION self --- @param #string TaskStatus The status --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus ) - - local Report = self:ReportOverview( ReportGroup, TaskStatus ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportPlayersPerTask( ReportGroup ) - - local Report = self:ReportPlayersPerTask() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportPlayersProgress( ReportGroup ) - - local Report = self:ReportPlayersProgress() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - - - diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua deleted file mode 100644 index 452f88242..000000000 --- a/Moose Development/Moose/Tasking/Task.lua +++ /dev/null @@ -1,2058 +0,0 @@ ---- **Tasking** - A task object governs the main engine to administer human taskings. --- --- **Features:** --- --- * A base class for other task classes filling in the details and making a concrete task process. --- * Manage the overall task execution, following-up the progression made by the pilots and actors. --- * Provide a mechanism to set a task status, depending on the progress made within the task. --- * Manage a task briefing. --- * Manage the players executing the task. --- * Manage the task menu system. --- * Manage the task goal and scoring. --- --- === --- --- # 1) Tasking from a player perspective. --- --- Tasking can be controlled by using the "other" menu in the radio menu of the player group. --- --- ![Other Menu](../Tasking/Menu_Main.JPG) --- --- ## 1.1) Command Centers govern multiple Missions. --- --- Depending on the tactical situation, your coalition may have one (or multiple) command center(s). --- These command centers govern one (or multiple) mission(s). --- --- For each command center, there will be a separate **Command Center Menu** that focuses on the missions governed by that command center. --- --- ![Command Center](../Tasking/Menu_CommandCenter.JPG) --- --- In the above example menu structure, there is one command center with the name **`[Lima]`**. --- The command center has one @{Tasking.Mission}, named **`"Overlord"`** with **`High`** priority. --- --- ## 1.2) Missions govern multiple Tasks. --- --- A mission has a mission goal to be achieved by the players within the coalition. --- The mission goal is actually dependent on the tactical situation of the overall battlefield and the conditions set to achieve the goal. --- So a mission can be much more than just shoot stuff ... It can be a combination of different conditions or events to complete a mission goal. --- --- A mission can be in a specific state during the simulation run. For more information about these states, please check the @{Tasking.Mission} section. --- --- To achieve the mission goal, a mission administers @{#TASK}s that are set to achieve the mission goal by the human players. --- Each of these tasks can be **dynamically created** using a task dispatcher, or **coded** by the mission designer. --- Each mission has a separate **Mission Menu**, that focuses on the administration of these tasks. --- --- On top, a mission has a mission briefing, can help to allocate specific points of interest on the map, and provides various reports. --- --- ![Mission](../Tasking/Menu_Mission.JPG) --- --- The above shows a mission menu in detail of **`"Overlord"`**. --- --- The two other menus are related to task assignment. Which will be detailed later. --- --- ### 1.2.1) Mission briefing. --- --- The task briefing will show a message containing a description of the mission goal, and other tactical information. --- --- ![Mission](../Tasking/Report_Briefing.JPG) --- --- ### 1.2.2) Mission Map Locations. --- --- Various points of interest as part of the mission can be indicated on the map using the *Mark Task Locations on Map* menu. --- As a result, the map will contain various points of interest for the player (group). --- --- ![Mission](../Tasking/Report_Mark_Task_Location.JPG) --- --- ### 1.2.3) Mission Task Reports. --- --- Various reports can be generated on the status of each task governed within the mission. --- --- ![Mission](../Tasking/Report_Task_Summary.JPG) --- --- The Task Overview Report will show each task, with its task status and a short coordinate information. --- --- ![Mission](../Tasking/Report_Tasks_Planned.JPG) --- --- The other Task Menus will show for each task more details, for example here the planned tasks report. --- Note that the order of the tasks are shortest distance first to the unit position seated by the player. --- --- ### 1.2.4) Mission Statistics. --- --- Various statistics can be displayed regarding the mission. --- --- ![Mission](../Tasking/Report_Statistics_Progress.JPG) --- --- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task. --- --- ## 1.3) Join a Task. --- --- The mission menu contains a very important option, that is to join a task governed within the mission. --- In order to join a task, select the **Join Planned Task** menu, and a new menu will be given. --- --- ![Mission](../Tasking/Menu_Join_Planned_Tasks.JPG) --- --- A mission governs multiple tasks, as explained earlier. Each task is of a certain task type. --- This task type was introduced to have some sort of task classification system in place for the player. --- A short acronym is shown that indicates the task type. The meaning of each acronym can be found in the task types explanation. --- --- ![Mission](../Tasking/Menu_Join_Tasks.JPG) --- --- When the player selects a task type, a list of the available tasks of that type are listed... --- In this case the **`SEAD`** task type was selected and a list of available **`SEAD`** tasks can be selected. --- --- ![Mission](../Tasking/Menu_Join_Planned_Task.JPG) --- --- A new list of menu options are now displayed that allow to join the task selected, but also to obtain first some more information on the task. --- --- ### 1.3.1) Report Task Details. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- When selected, a message is displayed that shows detailed information on the task, like the coordinate, enemy target information, threat level etc. --- --- ### 1.3.2) Mark Task Location on Map. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- When selected, the target location on the map is indicated with specific information on the task. --- --- ### 1.3.3) Join Task. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- By joining a task, the player will indicate that the task is assigned to him, and the task is started. --- The Command Center will communicate several task details to the player and the coalition of the player. --- --- ## 1.4) Task Control and Actions. --- --- ![Mission](../Tasking/Menu_Main_Task.JPG) --- --- When a player has joined a task, a **Task Action Menu** is available to be used by the player. --- --- ![Mission](../Tasking/Menu_Task.JPG) --- --- The task action menu contains now menu items specific to the task, but also one generic menu item, which is to control the task. --- This **Task Control Menu** allows to display again the task details and the task map location information. --- But it also allows to abort a task! --- --- Depending on the task type, the task action menu can contain more menu items which are specific to the task. --- For example, cargo transportation tasks will contain various additional menu items to select relevant cargo coordinates, --- or to load/unload cargo. --- --- ## 1.5) Automatic task assignment. --- --- ![Command Center](../Tasking/Menu_CommandCenter.JPG) --- --- When we take back the command center menu, you see two additional **Assign Task** menu items. --- The menu **Assign Task On** will automatically allocate a task to the player. --- After the selection of this menu, the menu will change into **Assign Task Off**, --- and will need to be selected again by the player to switch of the automatic task assignment. --- --- The other option is to select **Assign Task**, which will assign a new random task to the player. --- --- When a task is automatically assigned to a player, the task needs to be confirmed as accepted within 30 seconds. --- If this is not the case, the task will be cancelled automatically, and a new random task will be assigned to the player. --- This will continue to happen until the player accepts the task or switches off the automatic task assignment process. --- --- The player can accept the task using the menu **Confirm Task Acceptance** ... --- --- ## 1.6) Task states. --- --- A task has a state, reflecting the progress or completion status of the task: --- --- - **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet to a pilot. --- - **Assigned**: Expresses that the task is assigned to a group of pilots, and that the task is in execution mode. --- - **Success**: Expresses the successful execution and finalization of the task. --- - **Failed**: Expresses the failure of a task. --- - **Abort**: Expresses that the task is aborted by by the player using the abort menu. --- - **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- ### 1.6.1) Task progress. --- --- The task governor takes care of the **progress** and **completion** of the task **goal(s)**. --- Tasks are executed by **human pilots** and actors within a DCS simulation. --- Pilots can use a **menu system** to engage or abort a task, and provides means to --- understand the **task briefing** and goals, and the relevant **task locations** on the map and --- obtain **various reports** related to the task. --- --- ### 1.6.2) Task completion. --- --- As the task progresses, the **task status** will change over time, from Planned state to Completed state. --- **Multiple pilots** can execute the same task, as such, the tasking system provides a **co-operative model** for joint task execution. --- Depending on the task progress, a **scoring** can be allocated to award pilots of the achievements made. --- The scoring is fully flexible, and different levels of awarding can be provided depending on the task type and complexity. --- --- A normal flow of task status would evolve from the **Planned** state, to the **Assigned** state ending either in a **Success** or a **Failed** state. --- --- Planned -> Assigned -> Success --- -> Failed --- -> Cancelled --- --- The state completion is by default set to **Success**, if the goals of the task have been reached, but can be overruled by a goal method. --- --- Depending on the tactical situation, a task can be **Cancelled** by the mission governor. --- It is actually the mission designer who has the flexibility to decide at which conditions a task would be set to **Success**, **Failed** or **Cancelled**. --- This decision all depends on the task goals, and the phase/evolution of the task conditions that would accomplish the goals. --- --- For example, if the task goal is to merely destroy a target, and the target is mid-mission destroyed by another event than the pilot destroying the target, --- the task goal could be set to **Failed**, or .. **Cancelled** ... --- However, it could very well be also acceptable that the task would be flagged as **Success**. --- --- The tasking mechanism governs beside the progress also a scoring mechanism, and in case of goal completion without any active pilot involved --- in the execution of the task, could result in a **Success** task completion status, but no score would be awarded, as there were no players involved. --- --- These different completion states are important for the mission designer to reflect scoring to a player. --- A success could mean a positive score to be given, while a failure could mean a negative score or penalties to be awarded. --- --- # 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(s): **FlightControl** --- --- ### Contribution(s): --- --- === --- --- @module Tasking.Task --- @image MOOSE.JPG - ---- --- @type TASK --- @field Core.Scheduler#SCHEDULER TaskScheduler --- @field Tasking.Mission#MISSION Mission --- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @field Core.Fsm#FSM_PROCESS FsmTemplate --- @field Tasking.Mission#MISSION Mission --- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @field Tasking.TaskInfo#TASKINFO TaskInfo --- @extends Core.Fsm#FSM_TASK - ---- Governs the main engine to administer human taskings. --- --- A task is governed by a @{Tasking.Mission} object. Tasks are of different types. --- The @{#TASK} object is used or derived by more detailed tasking classes that will implement the task execution mechanisms --- and goals. --- --- # 1) Derived task classes. --- --- The following TASK_ classes are derived from @{#TASK}. --- --- TASK --- TASK_A2A --- TASK_A2A_ENGAGE --- TASK_A2A_INTERCEPT --- TASK_A2A_SWEEP --- TASK_A2G --- TASK_A2G_SEAD --- TASK_A2G_CAS --- TASK_A2G_BAI --- TASK_CARGO --- TASK_CARGO_TRANSPORT --- TASK_CARGO_CSAR --- --- ## 1.1) A2A Tasks --- --- - @{Tasking.Task_A2A#TASK_A2A_ENGAGE} - Models an A2A engage task of a target group of airborne intruders mid-air. --- - @{Tasking.Task_A2A#TASK_A2A_INTERCEPT} - Models an A2A ground intercept task of a target group of airborne intruders mid-air. --- - @{Tasking.Task_A2A#TASK_A2A_SWEEP} - Models an A2A sweep task to clean an area of previously detected intruders mid-air. --- --- ## 1.2) A2G Tasks --- --- - @{Tasking.Task_A2G#TASK_A2G_SEAD} - Models an A2G Suppression or Extermination of Air Defenses task to clean an area of air to ground defense threats. --- - @{Tasking.Task_A2G#TASK_A2G_CAS} - Models an A2G Close Air Support task to provide air support to nearby friendlies near the front-line. --- - @{Tasking.Task_A2G#TASK_A2G_BAI} - Models an A2G Battlefield Air Interdiction task to provide air support to nearby friendlies near the front-line. --- --- ## 1.3) Cargo Tasks --- --- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT} - Models the transportation of cargo to deployment zones. --- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR} - Models the rescue of downed friendly pilots from behind enemy lines. --- --- --- # 2) Task status events. --- --- The task statuses can be set by using the following methods: --- --- - @{#TASK.Success}() - Set the task to **Success** state. --- - @{#TASK.Fail}() - Set the task to **Failed** state. --- - @{#TASK.Hold}() - Set the task to **Hold** state. --- - @{#TASK.Abort}() - Set the task to **Aborted** state, aborting the task. The task may be replanned. --- - @{#TASK.Cancel}() - Set the task to **Cancelled** state, cancelling the task. --- --- The mentioned derived TASK_ classes are implementing the task status transitions out of the box. --- So no extra logic needs to be written. --- --- # 3) Goal conditions for a task. --- --- Every 30 seconds, a @{#Task.Goal} trigger method is fired. --- You as a mission designer, can capture the **Goal** event trigger to check your own task goal conditions and take action! --- --- ## 3.1) Goal event handler `OnAfterGoal()`. --- --- And this is a really great feature! Imagine a task which has **several conditions to check** before the task can move into **Success** state. --- You can do this with the OnAfterGoal method. --- --- The following code provides an example of such a goal condition check implementation. --- --- function Task:OnAfterGoal() --- if condition == true then --- self:Success() -- This will flag the task to Success when the condition is true. --- else --- if condition2 == true and condition3 == true then --- self:Fail() -- This will flag the task to Failed, when condition2 and condition3 would be true. --- end --- end --- end --- --- So the @{#TASK.OnAfterGoal}() event handler would be called every 30 seconds automatically, --- and within this method, you can now check the conditions and take respective action. --- --- ## 3.2) Goal event trigger `Goal()`. --- --- If you would need to check a goal at your own defined event timing, then just call the @{#TASK.Goal}() method within your logic. --- The @{#TASK.OnAfterGoal}() event handler would then directly be called and would execute the logic. --- Note that you can also delay the goal check by using the delayed event trigger syntax `:__Goal( Delay )`. --- --- --- # 4) Score task completion. --- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK.AddScore}() to add scores when a status is reached. --- --- # 5) Task briefing. --- --- A task briefing is a text that is shown to the player when he is assigned to the task. --- The briefing is broadcasted by the command center owning the mission. --- --- The briefing is part of the parameters in the @{#TASK.New}() constructor, --- but can separately be modified later in your mission using the --- @{#TASK.SetBriefing}() method. --- --- --- @field #TASK TASK --- -TASK = { - ClassName = "TASK", - TaskScheduler = nil, - ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. - Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, - FsmTemplate = nil, - Mission = nil, - CommandCenter = nil, - TimeOut = 0, - AssignedGroups = {}, -} - ---- FSM PlayerAborted event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerAborted --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerCrashed event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerCrashed --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerDead event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerDead --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM Fail synchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] Fail --- @param #TASK self - ---- FSM Fail asynchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] __Fail --- @param #TASK self - ---- FSM Abort synchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] Abort --- @param #TASK self - ---- FSM Abort asynchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] __Abort --- @param #TASK self - ---- FSM Success synchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] Success --- @param #TASK self - ---- FSM Success asynchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] __Success --- @param #TASK self - ---- FSM Cancel synchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] Cancel --- @param #TASK self - ---- FSM Cancel asynchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] __Cancel --- @param #TASK self - ---- FSM Replan synchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] Replan --- @param #TASK self - ---- FSM Replan asynchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] __Replan --- @param #TASK self - - ---- Instantiates a new TASK. Should never be used. Interface Class. --- @param #TASK self --- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. --- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @return #TASK self -function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) - - local self = BASE:Inherit( self, FSM_TASK:New( TaskName ) ) -- Tasking.Task#TASK - - self:SetStartState( "Planned" ) - self:AddTransition( "Planned", "Assign", "Assigned" ) - self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) - self:AddTransition( "Assigned", "Success", "Success" ) - self:AddTransition( "Assigned", "Hold", "Hold" ) - self:AddTransition( "Assigned", "Fail", "Failed" ) - self:AddTransition( { "Planned", "Assigned" }, "Abort", "Aborted" ) - self:AddTransition( "Assigned", "Cancel", "Cancelled" ) - self:AddTransition( "Assigned", "Goal", "*" ) - - self.Fsm = {} - - local Fsm = self:GetUnitProcess() - Fsm:SetStartState( "Planned" ) - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Assigned", Rejected = "Reject" } ) - Fsm:AddTransition( "Assigned", "Assigned", "*" ) - - --- Goal Handler OnBefore for TASK - -- @function [parent=#TASK] OnBeforeGoal - -- @param #TASK self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - -- @return #boolean - - --- Goal Handler OnAfter for TASK - -- @function [parent=#TASK] OnAfterGoal - -- @param #TASK self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - --- Goal Trigger for TASK - -- @function [parent=#TASK] Goal - -- @param #TASK self - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - --- Goal Asynchronous Trigger for TASK - -- @function [parent=#TASK] __Goal - -- @param #TASK self - -- @param #number Delay - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - - - self:AddTransition( "*", "PlayerCrashed", "*" ) - self:AddTransition( "*", "PlayerAborted", "*" ) - self:AddTransition( "*", "PlayerRejected", "*" ) - self:AddTransition( "*", "PlayerDead", "*" ) - self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) - self:AddTransition( "*", "TimeOut", "Cancelled" ) - - self:F( "New TASK " .. TaskName ) - - self.Processes = {} - - self.Mission = Mission - self.CommandCenter = Mission:GetCommandCenter() - - self.SetGroup = SetGroupAssign - - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self:SetBriefing( TaskBriefing ) - - - self.TaskInfo = TASKINFO:New( self ) - - self.TaskProgress = {} - - return self -end - ---- Get the Task FSM Process Template --- @param #TASK self --- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess( TaskUnit ) - - if TaskUnit then - return self:GetStateMachine( TaskUnit ) - else - self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - return self.FsmTemplate - end -end - ---- Sets the Task FSM Process Template --- @param #TASK self --- @param Core.Fsm#FSM_PROCESS -function TASK:SetUnitProcess( FsmTemplate ) - - self.FsmTemplate = FsmTemplate -end - ---- Add a PlayerUnit to join the Task. --- For each Group within the Task, the Unit is checked if it can join the Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of the Task. -function TASK:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. - -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. - if self:IsStatePlanned() or self:IsStateReplanned() then - --self:SetMenuForGroup( PlayerGroup ) - --self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) - end - if self:IsStateAssigned() then - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - self:F( { IsGroupAssigned = IsGroupAssigned } ) - if IsGroupAssigned then - self:AssignToUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) - end - end - end - - return PlayerUnitAdded -end - ---- A group rejecting a planned task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group rejecting the task. --- @return #TASK -function TASK:RejectGroup( PlayerGroup ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task. - -- If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStatePlanned() then - - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:GetMission():GetCommandCenter():MessageToGroup( "Task " .. self:GetName() .. " has been rejected! We will select another task.", PlayerGroup ) - self:UnAssignFromGroup( PlayerGroup ) - - self:PlayerRejected( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - ---- A group aborting the task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task. --- @return #TASK -function TASK:AbortGroup( PlayerGroup ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task. - -- If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:UnAssignFromGroup( PlayerGroup ) - - -- Now check if the task needs to go to hold... - -- It will go to hold, if there are no players in the mission... - PlayerGroups:Flush( self ) - local IsRemaining = false - for GroupName, AssignedGroup in pairs( PlayerGroups:GetSet() or {} ) do - if self:IsGroupAssigned( AssignedGroup ) == true then - IsRemaining = true - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - break - end - end - - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - if IsRemaining == false then - self:Abort() - end - - self:PlayerAborted( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - ---- A group crashing and thus aborting from the task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task. --- @return #TASK -function TASK:CrashGroup( PlayerGroup ) - self:F( { PlayerGroup = PlayerGroup } ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - self:F( { IsGroupAssigned = IsGroupAssigned } ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:MessageToGroups( PlayerName .. " crashed! " ) - self:UnAssignFromGroup( PlayerGroup ) - - -- Now check if the task needs to go to hold... - -- It will go to hold, if there are no players in the mission... - - PlayerGroups:Flush( self ) - local IsRemaining = false - for GroupName, AssignedGroup in pairs( PlayerGroups:GetSet() or {} ) do - if self:IsGroupAssigned( AssignedGroup ) == true then - IsRemaining = true - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - break - end - end - - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - if IsRemaining == false then - self:Abort() - end - - self:PlayerCrashed( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - - ---- Gets the Mission to where the TASK belongs. --- @param #TASK self --- @return Tasking.Mission#MISSION -function TASK:GetMission() - - return self.Mission -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @return Core.Set#SET_GROUP -function TASK:GetGroups() - - return self.SetGroup -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function TASK:AddGroups( GroupSet ) - - GroupSet = GroupSet or SET_GROUP:New() - - self.SetGroup:ForEachGroup( - -- @param Wrapper.Group#GROUP GroupSet - function( GroupItem ) - GroupSet:Add( GroupItem:GetName(), GroupItem) - end - ) - - return GroupSet -end - -do -- Group Assignment - - --- Returns if the @{#TASK} is assigned to the Group. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #boolean - function TASK:IsGroupAssigned( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self.AssignedGroups[TaskGroupName] then - --self:T( { "Task is assigned to:", TaskGroup:GetName() } ) - return true - end - - --self:T( { "Task is not assigned to:", TaskGroup:GetName() } ) - return false - end - - - --- Set @{Wrapper.Group} assigned to the @{#TASK}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:SetGroupAssigned( TaskGroup ) - - local TaskName = self:GetName() - local TaskGroupName = TaskGroup:GetName() - - self.AssignedGroups[TaskGroupName] = TaskGroup - self:F( string.format( "Task %s is assigned to %s", TaskName, TaskGroupName ) ) - - -- Set the group to be assigned at mission level. This allows to decide the menu options on mission level for this group. - self:GetMission():SetGroupAssigned( TaskGroup ) - - local SetAssignedGroups = self:GetGroups() - --- SetAssignedGroups:ForEachGroup( --- function( AssignedGroup ) --- if self:IsGroupAssigned(AssignedGroup) then --- self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is assigned to group %s.", TaskName, TaskGroupName ), AssignedGroup ) --- else --- self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is assigned to your group.", TaskName ), AssignedGroup ) --- end --- end --- ) - - return self - end - - --- Clear the @{Wrapper.Group} assignment from the @{#TASK}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:ClearGroupAssignment( TaskGroup ) - - local TaskName = self:GetName() - local TaskGroupName = TaskGroup:GetName() - - self.AssignedGroups[TaskGroupName] = nil - --self:F( string.format( "Task %s is unassigned to %s", TaskName, TaskGroupName ) ) - - -- Set the group to be assigned at mission level. This allows to decide the menu options on mission level for this group. - self:GetMission():ClearGroupAssignment( TaskGroup ) - - local SetAssignedGroups = self:GetGroups() - - SetAssignedGroups:ForEachGroup( - function( AssignedGroup ) - if self:IsGroupAssigned(AssignedGroup) then - --self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from group %s.", TaskName, TaskGroupName ), AssignedGroup ) - else - --self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from your group.", TaskName ), AssignedGroup ) - end - end - ) - - return self - end - -end - -do -- Group Assignment - - -- @param #TASK self - -- @param Actions.Act_Assign#ACT_ASSIGN AcceptClass - function TASK:SetAssignMethod( AcceptClass ) - - local ProcessTemplate = self:GetUnitProcess() - - ProcessTemplate:SetProcess( "Planned", "Accept", AcceptClass ) -- Actions.Act_Assign#ACT_ASSIGN - end - - - --- Assign the @{#TASK} to a @{Wrapper.Group}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:AssignToGroup( TaskGroup ) - self:F( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - local Mission = self:GetMission() - local CommandCenter = Mission:GetCommandCenter() - - self:SetGroupAssigned( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - self:F(PlayerName) - if PlayerName ~= nil and PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - CommandCenter:MessageToGroup( - string.format( 'Task "%s": Briefing for player (%s):\n%s', - self:GetName(), - PlayerName, - self:GetBriefing() - ), TaskGroup - ) - end - end - - CommandCenter:SetMenu() - - self:MenuFlashTaskStatus( TaskGroup, self:GetMission():GetCommandCenter().FlashStatus ) - - return self - end - - --- UnAssign the @{#TASK} from a @{Wrapper.Group}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - function TASK:UnAssignFromGroup( TaskGroup ) - self:F2( { TaskGroup = TaskGroup:GetName() } ) - - self:ClearGroupAssignment( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil and PlayerName ~= "" then -- Only remove units that have players! - self:UnAssignFromUnit( TaskUnit ) - end - end - - local Mission = self:GetMission() - local CommandCenter = Mission:GetCommandCenter() - CommandCenter:SetMenu() - - self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312 - - end -end - - ---- --- @param #TASK self --- @param Wrapper.Group#GROUP FindGroup --- @return #boolean -function TASK:HasGroup( FindGroup ) - - local SetAttackGroup = self:GetGroups() - return SetAttackGroup:FindGroup( FindGroup:GetName() ) - -end - ---- Assign the @{#TASK} to an alive @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local FsmTemplate = self:GetUnitProcess() - - -- Assign a new FsmUnit to TaskUnit. - local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS - - FsmUnit:SetStartState( "Planned" ) - - FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. - - return self -end - ---- UnAssign the @{#TASK} from an alive @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:UnAssignFromUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - self:RemoveStateMachine( TaskUnit ) - - -- If a Task Control Menu had been set, then this will be removed. - self:RemoveTaskControlMenu( TaskUnit ) - return self -end - ---- Sets the TimeOut for the @{#TASK}. If @{#TASK} stayed planned for longer than TimeOut, it gets into Cancelled status. --- @param #TASK self --- @param #integer Timer in seconds --- @return #TASK self -function TASK:SetTimeOut ( Timer ) - self:F( Timer ) - self.TimeOut = Timer - self:__TimeOut( self.TimeOut ) - return self -end - ---- Send a message of the @{#TASK} to the assigned @{Wrapper.Group}s. --- @param #TASK self -function TASK:MessageToGroups( Message ) - self:F( { Message = Message } ) - - local Mission = self:GetMission() - local CC = Mission:GetCommandCenter() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true then - CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() ) - end - end -end - - ---- Send the briefing message of the @{#TASK} to the assigned @{Wrapper.Group}s. --- @param #TASK self -function TASK:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() then - if self:IsGroupAssigned( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end - end -end - - ---- UnAssign the @{#TASK} from the @{Wrapper.Group}s. --- @param #TASK self -function TASK:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - if self:IsGroupAssigned(TaskGroup) then - self:UnAssignFromGroup( TaskGroup ) - end - end - end -end - - - ---- Returns if the @{#TASK} has still alive and assigned Units. --- @param #TASK self --- @return #boolean -function TASK:HasAliveUnits() - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - if self:IsStateAssigned() then - if self:IsGroupAssigned( TaskGroup ) then - for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do - if TaskUnit:IsAlive() then - self:T( { HasAliveUnits = true } ) - return true - end - end - end - end - end - end - - self:T( { HasAliveUnits = false } ) - return false -end - ---- Set the menu options of the @{#TASK} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424. - self:F( { self:GetName(), MenuTime } ) - - --self.SetGroup:Flush() - --for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetAliveSet() ) do - for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroupData -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then - - -- Set Mission Menus - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - if MissionMenu then - self:SetMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - - ---- Set the Menu for a Group --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenuForGroup( TaskGroup, MenuTime ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - self:SetPlannedMenuForGroup( TaskGroup, MenuTime ) - if self:IsGroupAssigned( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - end - end -end - - ---- Set the planned menu option of the @{#TASK}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @param #number MenuTime --- @return #TASK self -function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime ) - self:F( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskType = self:GetType() - local TaskPlayerCount = self:GetPlayerCount() - local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount ) - local TaskText = string.format( "%s", self:GetName() ) - local TaskName = string.format( "%s", self:GetName() ) - - self.MenuPlanned = self.MenuPlanned or {} - self.MenuPlanned[TaskGroup] = MENU_GROUP_DELAYED:New( TaskGroup, "Join Planned Task", MissionMenu, Mission.MenuReportTasksPerStatus, Mission, TaskGroup, "Planned" ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_DELAYED:New( TaskGroup, TaskType, self.MenuPlanned[TaskGroup] ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_DELAYED:New( TaskGroup, TaskText, TaskTypeMenu ):SetTime( MenuTime ):SetTag( "Tasking" ) - - if not Mission:IsGroupAssigned( TaskGroup ) then - --self:F( { "Replacing Join Task menu" } ) - local JoinTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local MarkTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskTypeMenu, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - - local ReportTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Report Task Details" ), TaskTypeMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - - return self -end - ---- Set the assigned menu options of the @{#TASK}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - self:F( { TaskGroup:GetName(), MenuTime } ) - - local TaskType = self:GetType() - local TaskPlayerCount = self:GetPlayerCount() - local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount ) - local TaskText = string.format( "%s%s", self:GetName(), TaskPlayerString ) --, TaskThreatLevelString ) - local TaskName = string.format( "%s", self:GetName() ) - - for UnitName, TaskUnit in pairs( TaskGroup:GetPlayerUnits() ) do - local TaskUnit = TaskUnit -- Wrapper.Unit#UNIT - if TaskUnit then - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - local TaskControl = MENU_GROUP:New( TaskGroup, "Control Task", MenuControl ):SetTime( MenuTime ):SetTag( "Tasking" ) - if self:IsStateAssigned() then - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Abort Task" ), TaskControl, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - if not self.FlashTaskStatus then - local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, true ):SetTime( MenuTime ):SetTag( "Tasking" ) - else - local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Stop Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, nil ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - end - end - - return self -end - ---- Remove the menu options of the @{#TASK} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:RemoveMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then - self:RefreshMenus( TaskGroup, MenuTime ) - end - end - end -end - - ---- Remove the menu option of the @{#TASK} for a @{Wrapper.Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RefreshMenus( TaskGroup, MenuTime ) - self:F( { TaskGroup:GetName(), MenuTime } ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskName = self:GetName() - self.MenuPlanned = self.MenuPlanned or {} - local PlannedMenu = self.MenuPlanned[TaskGroup] - - self.MenuAssigned = self.MenuAssigned or {} - local AssignedMenu = self.MenuAssigned[TaskGroup] - - if PlannedMenu then - self.MenuPlanned[TaskGroup] = PlannedMenu:Remove( MenuTime , "Tasking" ) - PlannedMenu:Set() - end - - if AssignedMenu then - self.MenuAssigned[TaskGroup] = AssignedMenu:Remove( MenuTime, "Tasking" ) - AssignedMenu:Set() - end - -end - ---- Remove the assigned menu option of the @{#TASK} for a @{Wrapper.Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemoveAssignedMenuForGroup( TaskGroup ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - MissionMenu:RemoveSubMenus() - end - -end - --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuAssignToGroup( TaskGroup ) - - self:F( "Join Task menu selected") - - self:AssignToGroup( TaskGroup ) -end - --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuMarkToGroup( TaskGroup ) - self:F() - - self:UpdateTaskInfo( self.DetectedItem ) - - local TargetCoordinates = self.TaskInfo:GetData( "Coordinates" ) -- Core.Point#COORDINATE - if TargetCoordinates then - for TargetCoordinateID, TargetCoordinate in pairs( TargetCoordinates ) do - local Report = REPORT:New():SetIndent( 0 ) - self.TaskInfo:Report( Report, "M", TaskGroup, self ) - local MarkText = Report:Text( ", " ) - self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } ) - TargetCoordinate:MarkToGroup( MarkText, TaskGroup ) - --Coordinate:MarkToAll( Briefing ) - end - else - local TargetCoordinate = self.TaskInfo:GetData( "Coordinate" ) -- Core.Point#COORDINATE - if TargetCoordinate then - local Report = REPORT:New():SetIndent( 0 ) - self.TaskInfo:Report( Report, "M", TaskGroup, self ) - local MarkText = Report:Text( ", " ) - self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } ) - TargetCoordinate:MarkToGroup( MarkText, TaskGroup ) - end - end - -end - ---- Report the task status. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuTaskStatus( TaskGroup ) - - if TaskGroup:IsAlive() then - - local ReportText = self:ReportDetails( TaskGroup ) - - self:T( ReportText ) - self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed ) - end - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuFlashTaskStatus( TaskGroup, Flash ) - - self.FlashTaskStatus = Flash - - if self.FlashTaskStatus then - self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages - else - if self.FlashTaskScheduler then - self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID ) - self.FlashTaskScheduler = nil - self.FlashTaskScheduleID = nil - end - end - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskAbort( TaskGroup ) - - self:AbortGroup( TaskGroup ) -end - - - ---- Returns the @{#TASK} name. --- @param #TASK self --- @return #string TaskName -function TASK:GetTaskName() - return self.TaskName -end - ---- Returns the @{#TASK} briefing. --- @param #TASK self --- @return #string Task briefing. -function TASK:GetTaskBriefing() - return self.TaskBriefing -end - - - - ---- Get the default or currently assigned @{Core.Fsm#FSM_PROCESS} template with key ProcessName. --- @param #TASK self --- @param #string ProcessName --- @return Core.Fsm#FSM_PROCESS -function TASK:GetProcessTemplate( ProcessName ) - - local ProcessTemplate = self.ProcessClasses[ProcessName] - - return ProcessTemplate -end - - - --- TODO: Obsolete? ---- Fail processes from @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param #string TaskUnitName --- @return #TASK self -function TASK:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @param Core.Fsm#FSM_PROCESS Fsm --- @return #TASK self -function TASK:SetStateMachine( TaskUnit, Fsm ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil, Fsm:GetClassNameAndID() } ) - - self.Fsm[TaskUnit] = Fsm - - return Fsm -end - ---- Gets the FiniteStateMachine of @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return Core.Fsm#FSM_PROCESS -function TASK:GetStateMachine( TaskUnit ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return self.Fsm[TaskUnit] -end - ---- Remove FiniteStateMachines from @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:RemoveStateMachine( TaskUnit ) - self:F( { TaskUnit = TaskUnit:GetName(), HasFsm = ( self.Fsm[TaskUnit] ~= nil ) } ) - - --self:F( self.Fsm ) - --for TaskUnitT, Fsm in pairs( self.Fsm ) do - --local Fsm = Fsm -- Core.Fsm#FSM_PROCESS - --self:F( TaskUnitT ) - --self.Fsm[TaskUnit] = nil - --end - - if self.Fsm[TaskUnit] then - self.Fsm[TaskUnit]:Remove() - self.Fsm[TaskUnit] = nil - end - - collectgarbage() - self:F( "Garbage Collected, Processes should be finalized now ...") -end - - ---- Checks if there is a FiniteStateMachine assigned to @{Wrapper.Unit} for @{#TASK}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:HasStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return ( self.Fsm[TaskUnit] ~= nil ) -end - - ---- Gets the Scoring of the task --- @param #TASK self --- @return Functional.Scoring#SCORING Scoring -function TASK:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task type, the Task name. --- @param #TASK self --- @return #string The Task ID -function TASK:GetTaskIndex() - - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK self --- @param #string TaskName -function TASK:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK self --- @return #string The Task Name -function TASK:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK self --- @param #string TaskType -function TASK:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK self --- @return #string TaskType -function TASK:GetType() - return self.TaskType -end - ---- Sets the ID of the Task --- @param #TASK self --- @param #string TaskID -function TASK:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK self --- @return #string TaskID -function TASK:GetID() - return self.TaskID -end - - ---- Sets a @{#TASK} to status **Success**. --- @param #TASK self -function TASK:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{#TASK} status **Success**. --- @param #TASK self -function TASK:IsStateSuccess() - return self:Is( "Success" ) -end - ---- Sets a @{#TASK} to status **Failed**. --- @param #TASK self -function TASK:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{#TASK} status **Failed**. --- @param #TASK self -function TASK:IsStateFailed() - return self:Is( "Failed" ) -end - ---- Sets a @{#TASK} to status **Planned**. --- @param #TASK self -function TASK:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{#TASK} status **Planned**. --- @param #TASK self -function TASK:IsStatePlanned() - return self:Is( "Planned" ) -end - ---- Sets a @{#TASK} to status **Aborted**. --- @param #TASK self -function TASK:StateAborted() - self:SetState( self, "State", "Aborted" ) - return self -end - ---- Is the @{#TASK} status **Aborted**. --- @param #TASK self -function TASK:IsStateAborted() - return self:Is( "Aborted" ) -end - ---- Sets a @{#TASK} to status **Cancelled**. --- @param #TASK self -function TASK:StateCancelled() - self:SetState( self, "State", "Cancelled" ) - return self -end - ---- Is the @{#TASK} status **Cancelled**. --- @param #TASK self -function TASK:IsStateCancelled() - return self:Is( "Cancelled" ) -end - ---- Sets a @{#TASK} to status **Assigned**. --- @param #TASK self -function TASK:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{#TASK} status **Assigned**. --- @param #TASK self -function TASK:IsStateAssigned() - return self:Is( "Assigned" ) -end - ---- Sets a @{#TASK} to status **Hold**. --- @param #TASK self -function TASK:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{#TASK} status **Hold**. --- @param #TASK self -function TASK:IsStateHold() - return self:Is( "Hold" ) -end - ---- Sets a @{#TASK} to status **Replanned**. --- @param #TASK self -function TASK:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{#TASK} status **Replanned**. --- @param #TASK self -function TASK:IsStateReplanned() - return self:Is( "Replanned" ) -end - ---- Gets the @{#TASK} status. --- @param #TASK self -function TASK:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{#TASK} briefing. --- @param #TASK self --- @param #string TaskBriefing --- @return #TASK self -function TASK:SetBriefing( TaskBriefing ) - self:F(TaskBriefing) - self.TaskBriefing = TaskBriefing - return self -end - ---- Gets the @{#TASK} briefing. --- @param #TASK self --- @return #string The briefing text. -function TASK:GetBriefing() - return self.TaskBriefing -end - - - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName ) - - --- This test is required, because the state transition will be fired also when the state does not change in case of an event. - if From ~= "Assigned" then - - local PlayerNames = self:GetPlayerNames() - local PlayerText = REPORT:New() - for PlayerName, TaskName in pairs( PlayerNames ) do - PlayerText:Add( PlayerName ) - end - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned to players " .. PlayerText:Text(",") .. ". Good Luck!" ) - - -- Set the total Progress to be achieved. - self:SetGoalTotal() -- Polymorphic to set the initial goal total! - - if self.Dispatcher then - self:F( "Firing Assign event " ) - self.Dispatcher:Assign( self, PlayerUnit, PlayerName ) - end - - self:GetMission():__Start( 1 ) - - -- When the task is assigned, the task goal needs to be checked of the derived classes. - self:__Goal( -10, PlayerUnit, PlayerName ) -- Polymorphic - - self:SetMenu() - - self:F( { "--> Task Assigned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "--> Task Player Names", PlayerNames = PlayerNames } ) - - end -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterSuccess( From, Event, To ) - - self:F( { "<-> Task Replanned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-> Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is successful! Good job!" ) - self:UnAssignFromGroups() - - self:GetMission():__MissionGoals( 1 ) - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterAborted( From, Event, To ) - - self:F( { "<-- Task Aborted", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-- Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - if From ~= "Aborted" then - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) - self:__Replan( 5 ) - self:SetMenu() - end - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterCancelled( From, Event, To ) - - self:F( { "<-- Task Cancelled", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-- Player Names", PlayerNames = self:GetPlayerNames() } ) - - if From ~= "Cancelled" then - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been cancelled! The tactical situation has changed." ) - self:UnAssignFromGroups() - self:SetMenu() - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onafterReplan( From, Event, To ) - - self:F( { "Task Replanned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Replanning Task " .. self:GetName() .. "." ) - - self:SetMenu() - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterFailed( From, Event, To ) - - self:F( { "Task Failed", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) - - self:UnAssignFromGroups() -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onstatechange( From, Event, To ) - - if self:IsTrace() then - --MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. From .. " changed to " .. To .. " by " .. Event, 2 ):ToAll() - end - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - self:F( { self.Scores[To].ScoreText, self.Scores[To].Score } ) - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterPlanned( From, Event, To) - if not self.TimeOut == 0 then - self.__TimeOut( self.TimeOut ) - end -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onbeforeTimeOut( From, Event, To ) - if From == "Planned" then - self:RemoveMenu() - return true - end - return false -end - -do -- Links - - --- Set goal of a task - -- @param #TASK self - -- @param Core.Goal#GOAL Goal - -- @return #TASK - function TASK:SetGoal( Goal ) - self.Goal = Goal - end - - - --- Get goal of a task - -- @param #TASK self - -- @return Core.Goal#GOAL The Goal - function TASK:GetGoal() - return self.Goal - end - - - --- Set dispatcher of a task - -- @param #TASK self - -- @param Tasking.DetectionManager#DETECTION_MANAGER Dispatcher - -- @return #TASK - function TASK:SetDispatcher( Dispatcher ) - self.Dispatcher = Dispatcher - end - - --- Set detection of a task - -- @param #TASK self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @param DetectedItem - -- @return #TASK - function TASK:SetDetection( Detection, DetectedItem ) - - self:F( { DetectedItem, Detection } ) - - self.Detection = Detection - self.DetectedItem = DetectedItem - end - -end - -do -- Reporting - ---- Create a summary report of the Task. --- List the Task Name and Status --- @param #TASK self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function TASK:ReportSummary( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - local Report = REPORT:New() - - -- List the name of the Task. - Report:Add( "Task " .. self:GetName() ) - - -- Determine the status of the Task. - Report:Add( "State: <" .. self:GetState() .. ">" ) - - self.TaskInfo:Report( Report, "S", ReportGroup, self ) - - return Report:Text( ', ' ) -end - ---- Create an overiew report of the Task. --- List the Task Name and Status --- @param #TASK self --- @return #string -function TASK:ReportOverview( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - -- List the name of the Task. - local TaskName = self:GetName() - local Report = REPORT:New() - - self.TaskInfo:Report( Report, "O", ReportGroup, self ) - - return Report:Text() -end - ---- Create a count of the players in the Task. --- @param #TASK self --- @return #number The total number of players in the task. -function TASK:GetPlayerCount() --R2.1 Get a count of the players. - - local PlayerCount = 0 - - -- Loop each Unit active in the Task, and find Player Names. - for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if PlayerGroup:IsAlive() == true then - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - PlayerCount = PlayerCount + ((PlayerNames) and #PlayerNames or 0) -- PlayerNames can be nil when there are no players. - end - end - end - - return PlayerCount -end - - ---- Create a list of the players in the Task. --- @param #TASK self --- @return #map<#string,Wrapper.Group#GROUP> A map of the players -function TASK:GetPlayerNames() --R2.1 Get a map of the players. - - local PlayerNameMap = {} - - -- Loop each Unit active in the Task, and find Player Names. - for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if PlayerGroup:IsAlive() == true then - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - for PlayerNameID, PlayerName in pairs( PlayerNames or {} ) do - PlayerNameMap[PlayerName] = PlayerGroup - end - end - end - end - - return PlayerNameMap -end - - ---- Create a detailed report of the Task. --- List the Task Status, and the Players assigned to the Task. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #string -function TASK:ReportDetails( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - local Report = REPORT:New():SetIndent( 3 ) - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( "Task " .. Name .. " - " .. Status .. " - Detailed Report" ) - - -- Loop each Unit active in the Task, and find Player Names. - local PlayerNames = self:GetPlayerNames() - - local PlayerReport = REPORT:New() - for PlayerName, PlayerGroup in pairs( PlayerNames ) do - PlayerReport:Add( "Players group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName ) - end - local Players = PlayerReport:Text() - - if Players ~= "" then - Report:AddIndent( "Players assigned:", "-" ) - Report:AddIndent( Players ) - end - - self.TaskInfo:Report( Report, "D", ReportGroup, self ) - - return Report:Text() -end - - -end -- Reporting - - -do -- Additional Task Scoring and Task Progress - - --- Add Task Progress for a Player Name - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #string ProgressText The text that explains the Progress achieved. - -- @param #number ProgressTime The time the progress was achieved. - -- @oaram #number ProgressPoints The amount of points of magnitude granted. This will determine the shared Mission Success scoring. - -- @return #TASK - function TASK:AddProgress( PlayerName, ProgressText, ProgressTime, ProgressPoints ) - self.TaskProgress = self.TaskProgress or {} - self.TaskProgress[ProgressTime] = self.TaskProgress[ProgressTime] or {} - self.TaskProgress[ProgressTime].PlayerName = PlayerName - self.TaskProgress[ProgressTime].ProgressText = ProgressText - self.TaskProgress[ProgressTime].ProgressPoints = ProgressPoints - self:GetMission():AddPlayerName( PlayerName ) - return self - end - - function TASK:GetPlayerProgress( PlayerName ) - local ProgressPlayer = 0 - for ProgressTime, ProgressData in pairs( self.TaskProgress ) do - if PlayerName == ProgressData.PlayerName then - ProgressPlayer = ProgressPlayer + ProgressData.ProgressPoints - end - end - return ProgressPlayer - end - - --- Set a score when progress has been made by the player. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountPlayer", "Player " .. PlayerName .. " has achieved progress.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "The task is a success!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The task is a failure!", Penalty ) - - return self - end - -end - -do -- Task Control Menu - - -- The Task Control Menu is a menu attached to the task at the main menu to quickly be able to do actions in the task. - -- The Task Control Menu can only be shown when the task is assigned to the player. - -- The Task Control Menu is linked to the process executing the task, so no task menu can be set to the main static task definition. - - --- Init Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @return Task Control Menu Refresh ID - function TASK:InitTaskControlMenu( TaskUnit ) - - self.TaskControlMenuTime = timer.getTime() - - return self.TaskControlMenuTime - end - - --- Get Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @return Core.Menu#MENU_GROUP TaskControlMenu The Task Control Menu - function TASK:GetTaskControlMenu( TaskUnit, TaskName ) - - TaskName = TaskName or "" - - local TaskGroup = TaskUnit:GetGroup() - local TaskPlayerCount = TaskGroup:GetPlayerCount() - - if TaskPlayerCount <= 1 then - self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control" ):SetTime( self.TaskControlMenuTime ) - else - self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control for " .. TaskUnit:GetPlayerName() ):SetTime( self.TaskControlMenuTime ) - end - - return self.TaskControlMenu - end - - --- Remove Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - function TASK:RemoveTaskControlMenu( TaskUnit ) - - if self.TaskControlMenu then - self.TaskControlMenu:Remove() - self.TaskControlMenu = nil - end - end - - --- Refresh Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @param MenuTime The refresh time that was used to refresh the Task Control Menu items. - -- @param MenuTag The tag. - function TASK:RefreshTaskControlMenu( TaskUnit, MenuTime, MenuTag ) - - if self.TaskControlMenu then - self.TaskControlMenu:Remove( MenuTime, MenuTag ) - end - end - -end diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua deleted file mode 100644 index 428e629c9..000000000 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ /dev/null @@ -1,377 +0,0 @@ ---- **Tasking** - Controls the information of a Task. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.TaskInfo --- @image MOOSE.JPG - ---- --- @type TASKINFO --- @extends Core.Base#BASE - ---- --- # TASKINFO class, extends @{Core.Base#BASE} --- --- ## The TASKINFO class implements the methods to contain information and display information of a task. --- --- # 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 #TASKINFO -TASKINFO = { - ClassName = "TASKINFO", -} - ---- --- @type TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report. --- --- - "M" for Markings on the Map (F10). --- - "S" for Summary Reports. --- - "O" for Overview Reports. --- - "D" for Detailed Reports. -TASKINFO.Detail = "" - ---- Instantiates a new TASKINFO. --- @param #TASKINFO self --- @param Tasking.Task#TASK Task The task owning the information. --- @return #TASKINFO self -function TASKINFO:New( Task ) - - local self = BASE:Inherit( self, BASE:New() ) -- Core.Base#BASE - - self.Task = Task - self.VolatileInfo = SET_BASE:New() - self.PersistentInfo = SET_BASE:New() - - self.Info = self.VolatileInfo - - return self -end - - ---- Add taskinfo. --- @param #TASKINFO self --- @param #string Key The info key. --- @param Data The data of the info. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep, ShowKey, Type ) - self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } ) - if Keep == true then - self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } ) - end - return self -end - - ---- Get taskinfo. --- @param #TASKINFO self --- @param #string The info key. --- @return Data The data of the info. --- @return #number Order The display order, which is a number from 0 to 100. --- @return #TASKINFO.Detail Detail The detail Level. -function TASKINFO:GetInfo( Key ) - local Object = self:Get( Key ) - return Object.Data, Object.Order, Object.Detail -end - - ---- Get data. --- @param #TASKINFO self --- @param #string The info key. --- @return Data The data of the info. -function TASKINFO:GetData( Key ) - local Object = self.Info:Get( Key ) - return Object and Object.Data -end - - ---- Add Text. --- @param #TASKINFO self --- @param #string Key The key. --- @param #string Text The text. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddText( Key, Text, Order, Detail, Keep ) - self:AddInfo( Key, Text, Order, Detail, Keep ) - return self -end - - ---- Add the task name. --- @param #TASKINFO self --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTaskName( Order, Detail, Keep ) - self:AddInfo( "TaskName", self.Task:GetName(), Order, Detail, Keep ) - return self -end - - - - ---- Add a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep, ShowKey, Name ) - self:AddInfo( Name or "Coordinate", Coordinate, Order, Detail, Keep, ShowKey, "Coordinate" ) - return self -end - - ---- Get the Coordinate. --- @param #TASKINFO self --- @return Core.Point#COORDINATE Coordinate -function TASKINFO:GetCoordinate( Name ) - return self:GetData( Name or "Coordinate" ) -end - - - ---- Add Coordinates. --- @param #TASKINFO self --- @param #list Coordinates --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCoordinates( Coordinates, Order, Detail, Keep ) - self:AddInfo( "Coordinates", Coordinates, Order, Detail, Keep ) - return self -end - - - ---- Add Threat. --- @param #TASKINFO self --- @param #string ThreatText The text of the Threat. --- @param #string ThreatLevel The level of the Threat. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddThreat( ThreatText, ThreatLevel, Order, Detail, Keep ) - self:AddInfo( "Threat", " [" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]:" .. ThreatText, Order, Detail, Keep ) - return self -end - - ---- Get Threat. --- @param #TASKINFO self --- @return #string The threat -function TASKINFO:GetThreat() - self:GetInfo( "Threat" ) - return self -end - - - ---- Add the Target count. --- @param #TASKINFO self --- @param #number TargetCount The amount of targets. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTargetCount( TargetCount, Order, Detail, Keep ) - self:AddInfo( "Counting", string.format( "%d", TargetCount ), Order, Detail, Keep ) - return self -end - ---- Add the Targets. --- @param #TASKINFO self --- @param #number TargetCount The amount of targets. --- @param #string TargetTypes The text containing the target types. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTargets( TargetCount, TargetTypes, Order, Detail, Keep ) - self:AddInfo( "Targets", string.format( "%d of %s", TargetCount, TargetTypes ), Order, Detail, Keep ) - return self -end - ---- Get Targets. --- @param #TASKINFO self --- @return #string The targets -function TASKINFO:GetTargets() - self:GetInfo( "Targets" ) - return self -end - - - - ---- Add the QFE at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddQFEAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "QFE", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add the Temperature at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTemperatureAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "Temperature", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add the Wind at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddWindAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "Wind", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add Cargo. --- @param #TASKINFO self --- @param Cargo.Cargo#CARGO Cargo --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCargo( Cargo, Order, Detail, Keep ) - self:AddInfo( "Cargo", Cargo, Order, Detail, Keep ) - return self -end - - ---- Add Cargo set. --- @param #TASKINFO self --- @param Core.Set#SET_CARGO SetCargo --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep ) - - local CargoReport = REPORT:New() - CargoReport:Add( "" ) - SetCargo:ForEachCargo( - -- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) ) - end - ) - - self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep ) - - - return self -end - - - ---- Create the taskinfo Report --- @param #TASKINFO self --- @param Core.Report#REPORT Report --- @param #TASKINFO.Detail Detail The detail Level. --- @param Wrapper.Group#GROUP ReportGroup --- @param Tasking.Task#TASK Task --- @return #TASKINFO self -function TASKINFO:Report( Report, Detail, ReportGroup, Task ) - - local Line = 0 - local LineReport = REPORT:New() - - if not self.Task:IsStatePlanned() and not self.Task:IsStateAssigned() then - self.Info = self.PersistentInfo - end - - for Key, Data in UTILS.spairs( self.Info.Set, function( t, a, b ) return t[a].Order < t[b].Order end ) do - - if Data.Detail:find( Detail ) then - local Text = "" - local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true ) - if Key == "TaskName" then - Key = nil - Text = Data.Data - elseif Data.Type and Data.Type == "Coordinate" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Threat" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Counting" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Targets" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "QFE" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringPressure( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Temperature" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringTemperature( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Wind" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Cargo" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Friendlies" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Players" then - local DataText = Data.Data -- #string - Text = DataText - else - local DataText = Data.Data -- #string - if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string - Text = DataText - end - end - - if Line < math.floor( Data.Order / 10 ) then - if Line == 0 then - Report:AddIndent( LineReport:Text( ", " ), "-" ) - else - Report:AddIndent( LineReport:Text( ", " ) ) - end - LineReport = REPORT:New() - Line = math.floor( Data.Order / 10 ) - end - - if Text ~= "" then - LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text ) - end - - end - end - - Report:AddIndent( LineReport:Text( ", " ) ) -end diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua deleted file mode 100644 index 9309526bb..000000000 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ /dev/null @@ -1,654 +0,0 @@ ---- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2A --- @image MOOSE.JPG - -do -- TASK_A2A - - --- The TASK_A2A class - -- @type TASK_A2A - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines Air To Air tasks for a @{Core.Set} of Target Units, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- The TASK_A2A is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2A task is planned. - -- * **Assigned**: The A2A task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2A task is successfully completed. - -- * **Failed**: The A2A task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- # 1) Set the scoring of achievements in an A2A attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed. - -- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed. - -- - -- # 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 #TASK_A2A - TASK_A2A = { - ClassName = "TASK_A2A" - } - - --- Instantiates a new TASK_A2A. - -- @param #TASK_A2A self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2A self - function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - -- Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnLeaveAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:SelectAction() - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousCoordinate( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2A Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2A Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:F( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget( 0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local Coordinate = TargetUnit:GetPointVec3() - self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetAlt(), Coordinate:GetZ() } ) - Task:SetTargetCoordinate( Coordinate, TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - -- @param #TASK_A2A self - -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. - function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) - - self.TargetSetUnit = TargetSetUnit - end - - -- @param #TASK_A2A self - function TASK_A2A:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_A2A self - -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2A:GetRendezVousCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() - end - - -- @param #TASK_A2A self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2A:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - -- @param #TASK_A2A self - -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetCoordinate( TargetCoordinate ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. - function TASK_A2A:GetTargetCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetCoordinate() - end - - -- @param #TASK_A2A self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2A:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - function TASK_A2A:SetGoalTotal() - - self.GoalTotal = self.TargetSetUnit:Count() - end - - function TASK_A2A:GetGoalTotal() - - return self.GoalTotal - end - - --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. - -- @param #TASK_A2A self - function TASK_A2A:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - --- This method checks every 10 seconds if the goal has been reached of the task. - -- @param #TASK_A2A self - function TASK_A2A:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:Count() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - -- @param #TASK_A2A self - function TASK_A2A:UpdateTaskInfo( DetectedItem ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) - - local ThreatLevel, ThreatText - if DetectedItem then - ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) - else - ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() - end - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) - - if self.Detection then - local DetectedItemsCount = self.TargetSetUnit:Count() - local ReportTypes = REPORT:New() - local TargetTypes = {} - for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do - local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit ) - if not TargetTypes[TargetType] then - TargetTypes[TargetType] = TargetType - ReportTypes:Add( TargetType ) - end - end - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) - else - local DetectedItemsCount = self.TargetSetUnit:Count() - local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) - end - end - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_A2A self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - -do -- TASK_A2A_INTERCEPT - - --- The TASK_A2A_INTERCEPT class - -- @type TASK_A2A_INTERCEPT - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an intercept task for a human player to be executed. - -- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there! - -- - -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks - -- based on detected airborne enemy targets intruding friendly airspace. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_INTERCEPT - TASK_A2A_INTERCEPT = { - ClassName = "TASK_A2A_INTERCEPT" - } - - --- Instantiates a new TASK_A2A_INTERCEPT. - -- @param #TASK_A2A_INTERCEPT self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" ) - - return self - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2A_SWEEP - - --- The TASK_A2A_SWEEP class - -- @type TASK_A2A_SWEEP - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines a sweep task for a human player to be executed. - -- A sweep task needs to be given when targets were detected but somehow the detection was lost. - -- Most likely, these enemy planes are hidden in the mountains or are flying under radar. - -- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters. - -- - -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks - -- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_SWEEP - TASK_A2A_SWEEP = { - ClassName = "TASK_A2A_SWEEP" - } - - --- Instantiates a new TASK_A2A_SWEEP. - -- @param #TASK_A2A_SWEEP self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_SWEEP self - function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" ) - - return self - end - - -- @param #TASK_A2A_SWEEP self - function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:Count() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2A_ENGAGE - - --- The TASK_A2A_ENGAGE class - -- @type TASK_A2A_ENGAGE - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an engage task for a human player to be executed. - -- When enemy planes are close to human players, use this task type is used urge the players to get out there! - -- - -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks - -- based on detected airborne enemy targets intruding friendly airspace. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_ENGAGE - TASK_A2A_ENGAGE = { - ClassName = "TASK_A2A_ENGAGE" - } - - --- Instantiates a new TASK_A2A_ENGAGE. - -- @param #TASK_A2A_ENGAGE self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_ENGAGE self - function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" ) - - return self - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed . - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty ) - - return self - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua deleted file mode 100644 index d592ad4a6..000000000 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ /dev/null @@ -1,620 +0,0 @@ ---- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. --- --- **Features:** --- --- * Dynamically assign tasks to human players based on detected targets. --- * Dynamically change the tasks as the tactical situation evolves during the mission. --- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP. --- * Dynamically assign (GCI) Ground Control Intercept tasks for human players to perform GCI. --- * Dynamically assign Engage tasks for human players to engage on close-by airborne bogeys. --- * Define and use an EWR (Early Warning Radar) network. --- * Define different ranges to engage upon intruders. --- * Keep task achievements. --- * Score task achievements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2A_Dispatcher --- @image Task_A2A_Dispatcher.JPG - -do -- TASK_A2A_DISPATCHER - - --- TASK_A2A_DISPATCHER class. - -- @type TASK_A2A_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Core.Set} of EWR installation groups. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) - -- - -- The EWR will detect units, will group them, and will dispatch @{Tasking.Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. - -- Find a summary below describing for which situation a task type is created: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG) - -- - -- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range. - -- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range. - -- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task. - -- - -- ## 1. TASK\_A2A\_DISPATCHER constructor: - -- - -- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance. - -- - -- ### 1.1. Define or set the **Mission**: - -- - -- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter. - -- - -- local HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" ) - -- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED ) - -- - -- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission. - -- Create the MISSION object, and hook it under the command center. - -- - -- ### 1.2. Build a set of the groups seated by human players: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG) - -- - -- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into. - -- - -- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart() - -- - -- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission. - -- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available. - -- - -- ### 1.3. Define the **EWR network**: - -- - -- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third 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\TASK_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\TASK_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 TASK\_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. - -- - -- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart() - -- - -- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 ) - -- EWRDetection:SetFriendliesRange( 10000 ) - -- EWRDetection:SetRefreshTimeInterval(30) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection ) - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**. - -- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set. - -- **EWRSet** 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 **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km. - -- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism. - -- - -- ### 2. Define the detected **target grouping radius**: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG) - -- - -- 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 radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG) - -- - -- So, 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. - -- - -- ## 4. Set **Scoring** and **Messages**: - -- - -- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Tasking.Task} dispatched by the TASK\_A2A\_DISPATCHER. - -- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_, - -- when the player reaches certain achievements in the task. - -- - -- The prototype to handle the **Assign** event needs to be developed as follows: - -- - -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) - -- - -- -- @param #TaskDispatcher self - -- -- @param #string From Contains the name of the state from where the Event was triggered. - -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. - -- -- @param #string To Contains the name of the state that will be transitioned to. - -- -- @param Tasking.Task_A2A#TASK_A2A Task The Task object, which is any derived object from TASK_A2A. - -- -- @param Wrapper.Unit#UNIT TaskUnit The Unit or Client that contains the Player. - -- -- @param #string PlayerName The name of the Player that joined the TaskUnit. - -- function TaskDispatcher:OnAfterAssign( From, Event, To, Task, TaskUnit, PlayerName ) - -- Task:SetScoreOnProgress( PlayerName, 20, TaskUnit ) - -- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit ) - -- Task:SetScoreOnFail( PlayerName, -100, TaskUnit ) - -- end - -- - -- The **OnAfterAssign** method (function) is added to the TaskDispatcher object. - -- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher. - -- So, this method will be called only **ONCE** when a player joins a unit in scope of the task. - -- - -- The TASK class implements various methods to additional **set scoring** for player achievements: - -- - -- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task. - -- Examples of **task progress** can be destroying units, arriving at zones etc. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. - -- This means the **task has been successfully completed**. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. - -- This means the **task has not been successfully completed**, and the scores must be given with a negative value! - -- - -- # 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 #TASK_A2A_DISPATCHER - TASK_A2A_DISPATCHER = { - ClassName = "TASK_A2A_DISPATCHER", - Mission = nil, - Detection = nil, - Tasks = {}, - SweepZones = {}, - } - - --- TASK_A2A_DISPATCHER constructor. - -- @param #TASK_A2A_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. - -- @return #TASK_A2A_DISPATCHER self - function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER - - self.Detection = Detection - self.Mission = Mission - self.FlashNewTask = false - - -- TODO: Check detection through radar. - self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) - self.Detection:InitDetectRadar( true ) - self.Detection:SetRefreshTimeInterval( 30 ) - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign - -- @param #TASK_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#TASK_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:__Start( 5 ) - - return self - end - - --- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission. - -- So, 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). - -- An ENGAGE task will be created for those pilots. - -- 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. - -- @param #TASK_A2A_DISPATCHER self - -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. - -- @return #TASK_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- TaskA2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) - - self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - - return self - end - - --- Set flashing player messages on or off - -- @param #TASK_A2A_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function TASK_A2A_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff - end - - --- Creates an INTERCEPT task when there are targets for it. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Check if there is at least one UNIT in the DetectedSet is visible. - - if DetectedItem.IsDetected == true 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 - - --- Creates an SWEEP task when there are targets for it. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? - - 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 - - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? - - local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - - -- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone. - if PlayersCount > 0 and DetectedItem.IsDetected == true 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 - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2A_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @param #boolean DetectedItemID - -- @param #boolean DetectedItemChange - -- @return Tasking.Task#TASK - function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged ) - - if Task then - - if Task:IsStatePlanned() then - local TaskName = Task:GetName() - local TaskType = TaskName:match( "(%u+)%.%d+" ) - - self:T2( { TaskType = TaskType } ) - - local Remove = false - - local IsPlayers = Detection:IsPlayersNearBy( DetectedItem ) - if TaskType == "ENGAGE" then - if IsPlayers == false then - Remove = true - end - end - - if TaskType == "INTERCEPT" then - if IsPlayers == true then - Remove = true - end - if DetectedItem.IsDetected == false then - Remove = true - end - end - - if TaskType == "SWEEP" then - if DetectedItem.IsDetected == true then - Remove = true - end - end - - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - -- DetectedSet:Flush( self ) - -- self:F( { DetectedSetCount = DetectedSet:Count() } ) - if DetectedSet:Count() == 0 then - Remove = true - end - - if DetectedItemChanged == true or Remove then - Task = self:RemoveTask( DetectedItemIndex ) - end - end - end - - return Task - end - - --- Calculates which friendlies are nearby the area - -- @param #TASK_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Tasking.CommandCenter#REPORT - function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - - 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 - - --- Calculates which HUMAN friendlies are nearby the area - -- @param #TASK_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Tasking.CommandCenter#REPORT - function TASK_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 - - 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 - - function TASK_A2A_DISPATCHER:RemoveTask( TaskIndex ) - self.Mission:RemoveTask( self.Tasks[TaskIndex] ) - self.Tasks[TaskIndex] = nil - end - - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. - -- @param #TASK_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 TASK_A2A_DISPATCHER:ProcessDetected( Detection ) - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) - if not DetectedItem then - local TaskText = Task:GetName() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) - end - Task = self:RemoveTask( TaskIndex ) - end - end - end - - -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - -- DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local TaskIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - local Task = self.Tasks[TaskIndex] - Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed. - - -- Evaluate INTERCEPT - if not Task and DetectedCount > 0 then - local TargetSetUnit = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... - if TargetSetUnit then - Task = TASK_A2A_ENGAGE:New( Mission, self.SetGroup, string.format( "ENGAGE.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - local TargetSetUnit = self:EvaluateINTERCEPT( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... - if TargetSetUnit then - Task = TASK_A2A_INTERCEPT:New( Mission, self.SetGroup, string.format( "INTERCEPT.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - local TargetSetUnit = self:EvaluateSWEEP( DetectedItem ) -- Returns a SetUnit - if TargetSetUnit then - Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - end - end - end - - if Task then - self.Tasks[TaskIndex] = Task - Task:SetTargetZone( DetectedZone, DetectedItem.Coordinate.y, DetectedItem.Coordinate.Heading ) - Task:SetDispatcher( self ) - Mission:AddTask( Task ) - - function Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - TaskReport:Add( Task:GetName() ) - else - self:F( "This should not happen" ) - end - - end - - if Task then - local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" ) - local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) - end - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text( ", " ) - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua deleted file mode 100644 index 84bdcf360..000000000 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ /dev/null @@ -1,635 +0,0 @@ ---- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2G --- @image MOOSE.JPG - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- The TASK_A2G class defines Air To Ground tasks for a @{Core.Set} of Target Units, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2G task is planned. - -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2G task is successfully completed. - -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- ## 1) Set the scoring of achievements in an A2G attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. - -- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- - -- # 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 #TASK_A2G - TASK_A2G = { - ClassName = "TASK_A2G" - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - -- Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - self:RouteToRendezVous() - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousCoordinate( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:F( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget( 0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local Coordinate = TargetUnit:GetPointVec3() - self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetY(), Coordinate:GetZ() } ) - Task:SetTargetCoordinate( Coordinate, TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - -- @param #TASK_A2G self - -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. - function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) - - self.TargetSetUnit = TargetSetUnit - end - - -- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_A2G self - -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2G:GetRendezVousCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() - end - - -- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2G:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - -- @param #TASK_A2G self - -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetCoordinate( TargetCoordinate ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. - function TASK_A2G:GetTargetCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetCoordinate() - end - - -- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2G:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - function TASK_A2G:SetGoalTotal() - - self.GoalTotal = self.TargetSetUnit:CountAlive() - end - - function TASK_A2G:GetGoalTotal() - - return self.GoalTotal - end - - --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. - -- @param #TASK_A2G self - function TASK_A2G:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - --- This method checks every 10 seconds if the goal has been reached of the task. - -- @param #TASK_A2G self - function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:CountAlive() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - -- @param #TASK_A2G self - function TASK_A2G:UpdateTaskInfo( DetectedItem ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) - - local ThreatLevel, ThreatText - if DetectedItem then - ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) - else - ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() - end - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) - - if self.Detection then - local DetectedItemsCount = self.TargetSetUnit:CountAlive() - local ReportTypes = REPORT:New() - local TargetTypes = {} - for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do - local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit ) - if not TargetTypes[TargetType] then - TargetTypes[TargetType] = TargetType - ReportTypes:Add( TargetType ) - end - end - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) - else - local DetectedItemsCount = self.TargetSetUnit:CountAlive() - local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) - end - self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" ) - self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" ) - self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" ) - end - - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_A2G self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - self:F( { Distance = Distance } ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - -do -- TASK_A2G_SEAD - - --- The TASK_A2G_SEAD class - -- @type TASK_A2G_SEAD - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an Suppression or Extermination of Air Defenses task for a human player to be executed. - -- These tasks are important to be executed as they will help to achieve air superiority at the vicinity. - -- - -- The TASK_A2G_SEAD is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_SEAD - TASK_A2G_SEAD = { - ClassName = "TASK_A2G_SEAD" - } - - --- Instantiates a new TASK_A2G_SEAD. - -- @param #TASK_A2G_SEAD self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_SEAD self - function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2G_BAI - - --- The TASK_A2G_BAI class - -- @type TASK_A2G_BAI - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines a Battlefield Air Interdiction task for a human player to be executed. - -- These tasks are more strategic in nature and are most of the time further away from friendly forces. - -- BAI tasks can also be used to express the abscence of friendly forces near the vicinity. - -- - -- The TASK_A2G_BAI is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create BAI tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_BAI - TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" } - - --- Instantiates a new TASK_A2G_BAI. - -- @param #TASK_A2G_BAI self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_BAI self - function TASK_A2G_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI", TaskBriefing ) ) -- #TASK_A2G_BAI - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2G_CAS - - --- The TASK_A2G_CAS class - -- @type TASK_A2G_CAS - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an Close Air Support task for a human player to be executed. - -- Friendly forces will be in the vicinity within 6km from the enemy. - -- - -- The TASK_A2G_CAS is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create CAS tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_CAS - TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" } - - --- Instantiates a new TASK_A2G_CAS. - -- @param #TASK_A2G_CAS self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_CAS self - function TASK_A2G_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS", TaskBriefing ) ) -- #TASK_A2G_CAS - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or ( "Execute a Close Air Support for a group of enemy targets. " .. "Beware of friendlies at the vicinity! " ) ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty ) - - return self - end - -end diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua deleted file mode 100644 index a98477d86..000000000 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ /dev/null @@ -1,828 +0,0 @@ ---- **Tasking** - Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. --- --- **Features:** --- --- * Dynamically assign tasks to human players based on detected targets. --- * Dynamically change the tasks as the tactical situation evolves during the mission. --- * Dynamically assign (CAS) Close Air Support tasks for human players. --- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players. --- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. --- * Define and use an EWR (Early Warning Radar) network. --- * Define different ranges to engage upon intruders. --- * Keep task achievements. --- * Score task achievements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2G_Dispatcher --- @image Task_A2G_Dispatcher.JPG - -do -- TASK_A2G_DISPATCHER - - --- TASK\_A2G\_DISPATCHER class. - -- @type TASK_A2G_DISPATCHER - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.Mission#MISSION Mission - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Functional.Detection} object. - -- - -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. - -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, - -- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**. - -- - -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Core.Set} of @{Wrapper.Group}s that will be manned by **Players**. - -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Wrapper.Client}s of the @{Wrapper.Group} @{Core.Set}. - -- - -- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation. - -- The detection object will group the detected targets by its grouping method, and integrates a @{Core.Set} of @{Wrapper.Group}s that are Recce vehicles or air units. - -- We call this the **RecceSet** of the A2G dispatcher. - -- - -- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet.. - -- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER: - -- - -- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area. - -- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- - -- # 0. Tactical Situations - -- - -- This chapters provides some insights in the tactical situations when certain Task Types are created. - -- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG) - -- - -- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test], - -- the tactical situation is a demonstration how the A2G detection works. - -- This example will be taken further in the explanation in the following chapters. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG) - -- - -- The red coalition are the players, the blue coalition is the enemy. - -- - -- Red reconnaissance vehicles and airborne units are detecting the targets. - -- We call this the RecceSet as explained above, which is a Set of Groups that - -- have a group name starting with `Recce` (configured in the mission script). - -- - -- Red attack units are responsible for executing the mission for the command center. - -- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script). - -- These units are setup in this demonstration mission to be ground vehicles and airplanes. - -- For demonstration purposes, the attack airplane is stationed on the ground to explain - -- the messages and the menus properly. - -- Further test missions demonstrate the A2G task dispatcher from within air. - -- - -- Depending upon the detection results, the A2G dispatcher will create different tasks. - -- - -- # 0.1. SEAD Task - -- - -- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG) - -- - -- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task! - -- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area! - -- - -- # 0.2. CAS Task - -- - -- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG) - -- - -- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task! - -- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support. - -- - -- # 0.3. BAI Task - -- - -- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG) - -- - -- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range! - -- - -- # 1. Player Experience - -- - -- The A2G dispatcher is residing under a @{Tasking.CommandCenter}, which is orchestrating a @{Tasking.Mission}. - -- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**. - -- - -- For example, if there are 2 Command Centers (CC). - -- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha (Primary)" - -- F2. Mission "Beta (Secondary)" - -- F3. Mission "Gamma (Tactical)" - -- F1. Command Center [Lima] - -- F1. Mission "Overlord (High)" - -- - -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma. - -- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority. - -- - -- ## 1.1. Mission Menu (Under the Command Center Menu) - -- - -- The Mission Menu controls the information of the mission, including the: - -- - -- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message. - -- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker. - -- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher. - -- - **Create Mission Reports**: A menu to create various reports on the current mission. - -- - -- For CC [Lima], Mission "Overlord", the menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Lima] - -- F1. Mission "Overlord" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG) - -- - -- ### 1.1.1. Mission Briefing Menu - -- - -- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations. - -- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation, - -- and explains the mission goals. - -- - -- - -- ### 1.1.2. Mark Task Locations Menu - -- - -- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center. - -- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant. - -- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked. - -- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness. - -- - -- ### 1.1.3. Task Reports Menu - -- - -- The Task Reports Menu is a sub menu, that allows to create various reports: - -- - -- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status. - -- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed. - -- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed. - -- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully. - -- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed. - -- - -- The information shown of the tasks will vary according the underlying task type, but are self explanatory. - -- - -- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F1. Tasks Summary - -- F2. Planned Tasks - -- F3. Assigned Tasks - -- F4. Successful Tasks - -- F5. Failed Tasks - -- F4. Mission Reports - -- - -- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu. - -- (See later). - -- - -- ### 1.1.4. Mission Reports Menu - -- - -- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission: - -- - -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion. - -- - **Report Players per Task**: Show which players are engaged on which Task within the Mission. - -- - -- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F1. Report Mission Progress - -- F2. Report Players per Task - -- - -- - -- ## 1.2. Task Management Menus - -- - -- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!** - -- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task. - -- Depending on whether a Task has been Planned or Assigned to a Player (Group), - -- **the Mission Menu will contain extra Menus to control specific Tasks.** - -- - -- #### 1.2.1. Join a Planned Task - -- - -- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a: - -- - -- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned). - -- - -- For CC |Gori|, Mission "Alpha", the menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- - -- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.** - -- - -- #### 1.2.2. Manage an Assigned Task - -- - -- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra: - -- - -- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task. - -- - -- In this example, the Group currently seated by the player is not assigned yet to a Task. - -- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha". - -- - -- This would be an example menu structure, - -- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Assigned Task CAS.001 - -- - -- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.** - -- - -- ## 1.3. Join Planned Task Menu - -- - -- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**. - -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. - -- - -- For example, for CC [Gori], Mission "Alpha", - -- if a Mission "ALpha" contains 5 Planned Tasks, which would be: - -- - -- - 2 CAS Tasks - -- - 1 BAI Task - -- - 2 SEAD Tasks - -- - -- the Join Planned Task Menu Hierarchy could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- F2. BAI - -- F1. BAI.001 - -- F1. CAS - -- F1. CAS.002 - -- F3. SEAD - -- F1. SEAD.003 - -- F2. SEAD.004 - -- F3. SEAD.005 - -- - -- An example from within a running simulation: - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG) - -- - -- Each Task Type Menu would have a list of the Task Menus underneath. - -- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**! - -- - -- ### 1.3.1. Planned Task Menu - -- - -- Each Planned Task Menu will allow for the following actions: - -- - -- - Report Task Details: Provides a detailed report on the Planned Task. - -- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant. - -- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission. - -- - -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center |Gori| - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- F1. CAS - -- F1. CAS.001 - -- F1. Report Task Details - -- F2. Mark Task Location on Map - -- F3. Join Task - -- - -- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.** - -- - -- - -- ## 1.4. Assigned Task Menu - -- - -- The Assigned Task Menu allows to control the **current assigned task** within the Mission. - -- - -- Depending on the Type of Task, the following menu options will be available: - -- - -- - **Report Task Details**: Provides a detailed report on the Planned Task. - -- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant. - -- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task. - -- - -- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be: - -- - -- F1. Command Center |Gori| - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Assigned Task - -- F1. Report Task Details - -- F2. Mark Task Location on Map - -- F3. Abort Task - -- - -- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**. - -- However, this will depend on the setup of each Mission. - -- - -- ## 1.5. Messages - -- - -- During game play, different messages are displayed. - -- These messages provide an update of the achievements made, and the state wherein the task is. - -- - -- The various reports can be used also to retrieve the current status of the mission and its tasks. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG) - -- - -- The @{Core.Settings} menu provides additional options to control the timing of the messages. - -- There are: - -- - -- - Status messages, which are quick status updates. The settings menu allows to switch off these messages. - -- - Information messages, which are shown a bit longer, as they contain important information. - -- - Summary reports, which are quick reports showing a high level summary. - -- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read. - -- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer. - -- - -- # 2. TASK\_A2G\_DISPATCHER constructor - -- - -- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance. - -- - -- # 3. Usage - -- - -- To use the TASK\_A2G\_DISPATCHER class, you need: - -- - -- - A @{Tasking.CommandCenter} object. The master communication channel. - -- - A @{Tasking.Mission} object. Each task belongs to a Mission. - -- - A @{Functional.Detection} object. There are several detection grouping methods to choose from. - -- - A @{Tasking.Task_A2G_Dispatcher} object. The master A2G task dispatcher. - -- - A @{Core.Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Functional.Detection} object. - -- - A @{Core.Set} of @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Tasking.Task_A2G_Dispatcher} object. - -- - -- Below an example mission declaration that is defines a Task A2G Dispatcher object. - -- - -- -- Declare the Command Center - -- local HQ = GROUP - -- :FindByName( "HQ", "Bravo HQ" ) - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ) - -- - -- -- Define the RecceSet that will detect the enemy. - -- local RecceSet = SET_GROUP - -- :New() - -- :FilterPrefixes( "FAC" ) - -- :FilterCoalitions("red") - -- :FilterStart() - -- - -- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius. - -- local DetectionAreas = DETECTION_AREAS - -- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies. - -- - -- -- Setup the AttackSet, which is a SET_GROUP. - -- -- The SET_GROUP is a dynamic collection of GROUP objects. - -- local AttackSet = SET_GROUP - -- :New() -- Create the SET_GROUP object. - -- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions. - -- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack. - -- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack. - -- - -- -- Now we have everything to setup the main A2G TaskDispatcher. - -- TaskDispatcher = TASK_A2G_DISPATCHER - -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. - -- - -- - -- # 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 #TASK_A2G_DISPATCHER - TASK_A2G_DISPATCHER = { - ClassName = "TASK_A2G_DISPATCHER", - Mission = nil, - Detection = nil, - Tasks = {} - } - - --- TASK_A2G_DISPATCHER constructor. - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. - -- @return #TASK_A2G_DISPATCHER self - function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - - self.Detection = Detection - self.Mission = Mission - self.FlashNewTask = true -- set to false to suppress flash messages - - self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign - -- @param #TASK_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#TASK_A2G Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:__Start( 5 ) - - return self - end - - --- Set flashing player messages on or off - -- @param #TASK_A2G_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function TASK_A2G_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff - end - - --- Creates a SEAD task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount > 0 then - - -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterHasSEAD() - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a CAS task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has ground units. - -- There should be ground unit friendlies nearby. Airborne units are valid friendlies types. - -- And there shouldn't be any radar. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) -- Are there friendlies nearby of type GROUND_UNIT? - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == true then - - -- Copy the Set - 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 - - --- Creates a BAI task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has ground units. - -- There shouldn't be any ground unit friendlies nearby. - -- And there shouldn't be any radar. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) -- Are there friendlies nearby of type GROUND_UNIT? - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == false then - - -- Copy the Set - 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 - - function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex ) - self.Mission:RemoveTask( self.Tasks[TaskIndex] ) - self.Tasks[TaskIndex] = nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param #boolean DetectedItemID - -- @param #boolean DetectedItemChange - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) - - if Task then - if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then - -- self:F( "Removing Tasking: " .. Task:GetTaskName() ) - self:RemoveTask( TaskIndex ) - end - end - - return Task - end - - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. - -- @param #TASK_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 TASK_A2G_DISPATCHER:ProcessDetected( Detection ) - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) - if not DetectedItem then - local TaskText = Task:GetName() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) - end - end - Task = self:RemoveTask( TaskIndex ) - end - end - end - - --- First we need to the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedZone = DetectedItem.Zone - -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - -- DetectedSet:Flush( self ) - - local DetectedItemID = DetectedItem.ID - local TaskIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } ) - - local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G - - if Task then - -- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets. - if Task:IsStateAssigned() then - if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set - local TargetsReport = REPORT:New() - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_SEAD ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - end - else - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_CAS ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_BAI ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - end - end - end - - -- Now we send to each group the changes, if any. - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TargetsText = TargetsReport:Text( ", " ) - if (Mission:IsGroupAssigned( TaskGroup )) and TargetsText ~= "" and self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup ) - end - end - end - end - end - - if Task then - if Task:IsStatePlanned() then - if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set - if Task:IsInstanceOf( TASK_A2G_SEAD ) then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - if Task:IsInstanceOf( TASK_A2G_CAS ) then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - if Task:IsInstanceOf( TASK_A2G_BAI ) then - local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - end - end - end - end - end - - -- Evaluate SEAD - if not Task then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - - -- Evaluate CAS - if not Task then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - - -- Evaluate BAI - if not Task then - local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - end - end - - if Task then - self.Tasks[TaskIndex] = Task - Task:SetTargetZone( DetectedZone ) - Task:SetDispatcher( self ) - Task:UpdateTaskInfo( DetectedItem ) - Mission:AddTask( Task ) - - function Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - TaskReport:Add( Task:GetName() ) - else - self:F( "This should not happen" ) - end - end - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text( ", " ) - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua deleted file mode 100644 index be894d805..000000000 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ /dev/null @@ -1,1410 +0,0 @@ ---- **Tasking** - Base class to model tasks for players to transport cargo. --- --- ## Features: --- --- * TASK_CARGO is the **base class** for: --- --- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} --- * @{Tasking.Task_Cargo_CSAR#TASK_CARGO_CSAR} --- --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Tasking/Task_Cargo_Dispatcher) --- --- === --- --- ## Tasking system. --- --- #### If you are not yet aware what the MOOSE tasking system is about, read FIRST the explanation on the @{Tasking.Task} module. --- --- === --- --- ## Context of cargo tasking. --- --- The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers. --- The CARGO_ classes, as part of the MOOSE core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units. --- --- The TASK_CARGO class is not meant to use within your missions as a mission designer. It is a base class, and other classes are derived from it. --- --- The following TASK_CARGO_ classes are important, as they implement the CONCRETE tasks: --- --- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT}: Defines a task for a human player to transport a set of cargo between various zones. --- * @{Tasking.Task_Cargo_CSAR#TASK_CARGO_CSAR}: Defines a task for a human player to Search and Rescue wounded pilots. --- --- However! The menu system and basic usage of the TASK_CARGO classes is explained in the @{#TASK_CARGO} class description. --- So please browse further below to understand how to use it from a player perspective! --- --- === --- --- ## Cargo tasking from a player perspective. --- --- A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J). --- The player needs to accept the task from the task overview list within the mission, using the menus. --- --- Once the task is assigned to the player and accepted by the player, the player will obtain --- an extra **Cargo (Radio) Menu** that contains the CARGO objects that need to be transported. --- --- Each @{Tasking.Task_CARGO#TASK_CARGO} object has a certain state: --- --- * **UnLoaded**: The cargo is located within the battlefield. It may still need to be transported. --- * **Loaded**: The cargo is loaded within a Carrier. This can be your air unit, or another air unit, or even a vehicle. --- * **Boarding**: The cargo is running or moving towards your Carrier for loading. --- * **UnBoarding**: The cargo is driving or jumping out of your Carrier and moves to a location in the Deployment Zone. --- --- Cargo must be transported towards different Deployment @{Core.Zone}s. --- --- The Cargo Menu system allows to execute **various actions** to transport the cargo. --- In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed. --- Depending on the location of your Carrier unit, the menu options will vary. --- --- ### Joining a Cargo Transport Task --- --- Once you've joined a task, using the **Join Planned Task Menu**, --- you can Pickup cargo from a pickup location and Deploy cargo in deployment zones, using the **Task Action Menu**. --- --- ### Task Action Menu. --- --- When a player has joined a **`CARGO`** task (type), for that player only, --- it's **Task Action Menu** will show an additional menu options. --- --- From within this menu, you will be able to route to a cargo location, deploy zone, and load/unload cargo. --- --- ### Pickup cargo by Boarding, Loading and Sling Loading. --- --- There are three different ways how cargo can be picked up: --- --- - **Boarding**: Moveable cargo (like infantry or vehicles), can be boarded, that means, the cargo will move towards your carrier to board. --- However, it can only execute the boarding actions if it is within the foreseen **Reporting Range**. --- Therefore, it is important that you steer your Carrier within the Reporting Range around the cargo, --- so that boarding actions can be executed on the cargo. The reporting range is set by the mission designer. --- Fortunately, the cargo is reporting to you when it is within reporting range. --- --- - **Loading**: Stationary cargo (like crates), which are heavy, can only be loaded or sling loaded, meaning, --- your carrier must be close enough to the cargo to be able to load the cargo within the carrier bays. --- Moose provides you with an additional menu system to load stationary cargo into your carrier bays using the menu. --- These menu options will become available, when the carrier is within loading range. --- The Moose cargo will report to the carrier when the range is close enough. The load range is set by the mission designer. --- --- - **Sling Loading**: Stationary cargo (like crates), which are heavy, can only be loaded or sling loaded, meaning, --- your carrier must be close enough to the cargo to be able to load the cargo within the carrier bays. --- Sling loading cargo is done using the default DCS menu system. However, Moose cargo will report to the carrier that --- it is within sling loading range. --- --- In order to be able to pickup cargo, you'll need to know where the cargo is located, right? --- --- Fortunately, if your Carrier is not within the reporting range of the cargo, --- **the HQ can help to route you to the locations of cargo**. --- --- ![Task_Types](../Tasking/Task_Cargo_Main_Menu.JPG) --- --- Use the task action menu to receive HQ help for this. --- --- ![Task_Types](../Tasking/Task_Cargo_Action_Menu.JPG) --- --- Depending on the location within the battlefield, the task action menu will contain **Route options** that can be selected --- to start the HQ sending you routing messages. --- The **route options will vary**, depending on the position of your carrier, and the location of the cargo and the deploy zones. --- Note that the route options will **only be created** for cargo that is **in scope of your cargo transportation task**, --- so there may be other cargo objects within the DCS simulation, but if those belong to other cargo transportations tasks, --- then no routing options will be shown for these cargo. --- This is done to ensure that **different teams** have a **defined scope** for defined cargo, and that **multiple teams** can join --- **multiple tasks**, transporting cargo **simultaneously** in a **cooperation**. --- --- In this example, there is a menu option to **Route to pickup cargo...**. --- Use this menu to route towards cargo locations for pickup into your carrier. --- --- ![Task_Types](../Tasking/Task_Cargo_Types_Menu.JPG) --- --- When you select this menu, you'll see a new menu listing the different cargo types that are out there in the dcs simulator. --- These cargo types are symbolic names that are assigned by the mission designer, like oil, liquid, engineers, food, workers etc. --- MOOSE has introduced this concept to allow mission designers to make different cargo types for different purposes. --- Only the creativity of the mission designer limits now the things that can be done with cargo ... --- Okay, let's continue ..., and let's select Oil ... --- --- When selected, the HQ will send you routing messages. --- --- ![Task_Types](../Tasking/Task_Cargo_Routing_BR.JPG) --- --- An example of routing in BR mode. --- --- Note that the coordinate display format in the message can be switched between LL DMS, LL DDM, MGRS and BR. --- --- ![Task_Types](../Tasking/Main_Settings.JPG) --- --- Use the @{Core.Settings} menu to change your display format preferences. --- --- ![Task_Types](../Tasking/Settings_A2G_Coordinate.JPG) --- --- There you can change the display format to another format that suits your need. --- Because cargo transportation is Air 2 Ground oriented, you need to select the A2G coordinate format display options. --- Note that the main settings menu contains much more --- options to control your display formats, like switch to metric and imperial, or change the duration of the display messages. --- --- ![Task_Types](../Tasking/Task_Cargo_Routing_LL.JPG) --- --- Here I changed the routing display format to LL DMS. --- --- One important thing to know, is that the routing messages will flash at regular time intervals. --- When using BR coordinate display format, the **distance and angle will change accordingly** from your carrier position and the location of the cargo. --- --- Another important note is the routing towards deploy zones. --- These routing options will only be shown, when your carrier bays have cargo loaded. --- So, only when there is something to be deployed from your carrier, the deploy options will be shown. --- --- #### Pickup Cargo. --- --- In order to pickup cargo, use the **task action menu** to **route to a specific cargo**. --- When a cargo route is selected, the HQ will send you routing messages indicating the location of the cargo. --- --- Upon arrival at the cargo, and when the cargo is within **reporting range**, the cargo will contact you and **further instructions will be given**. --- --- - When your Carrier is airborne, you will receive instructions to land your Carrier. --- The action will not be completed until you've landed your Carrier. --- --- - For ground carriers, you can just drive to the optimal cargo board or load position. --- --- It takes a bit of skill to land a helicopter near a cargo to be loaded, but that is part of the game, isn't it? --- Expecially when you are landing in a "hot" zone, so when cargo is under immediate threat of fire. --- --- #### Board Cargo (infantry). --- --- ![](../Tasking/Boarding_Ready.png) --- --- If your Carrier is within the **Reporting Range of the cargo**, and the cargo is **moveable**, the **cargo can be boarded**! --- This type of cargo will be most of the time be infantry. --- --- ![](../Tasking/Boarding_Menu.png) --- --- A **Board cargo...** sub menu has appeared, because your carrier is in boarding range of the cargo (infantry). --- Select the **Board cargo...** menu. --- --- ![](../Tasking/Boarding_Menu_Engineers.png) --- --- Any cargo that can be boarded (thus movable cargo), within boarding range of the carrier, will be listed here! --- In this example, the cargo **Engineers** can be boarded, by selecting the menu option. --- --- ![](../Tasking/Boarding_Started.png) --- --- After the menu option to board the cargo has been selected, the boarding process is started. --- A message from the cargo is communicated to the pilot, that boarding is started. --- --- ![](../Tasking/Boarding_Ongoing.png) --- --- **The pilot must wait at the exact position until all cargo has been boarded!** --- --- The moveable cargo will run in formation to your carrier, and will board one by one, depending on the near range set by the mission designer. --- The near range as added because carriers can be large or small, depending on the object size of the carrier. --- --- ![](../Tasking/Boarding_In_Progress.png) --- --- ![](../Tasking/Boarding_Almost_Done.png) --- --- Note that multiple units may need to board your Carrier, so it is required to await the full boarding process. --- --- ![](../Tasking/Boarding_Done.png) --- --- Once the cargo is fully boarded within your Carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne Carriers, it is required to land first before the Boarding process can be initiated. --- If during boarding the Carrier gets airborne, the boarding process will be cancelled. --- * The carrier must remain stationary when the boarding sequence has started until further notified. --- --- #### Load Cargo. --- --- Cargo can be loaded into vehicles or helicopters or airplanes, as long as the carrier is sufficiently near to the cargo object. --- --- ![](../Tasking/Loading_Ready.png) --- --- If your Carrier is within the **Loading Range of the cargo**, thus, sufficiently near to the cargo, and the cargo is **stationary**, the **cargo can be loaded**, but not boarded! --- --- ![](../Tasking/Loading_Menu.png) --- --- Select the task action menu and now a **Load cargo...** sub menu will be listed. --- Select the **Load cargo...** sub menu, and a further detailed menu will be shown. --- --- ![](../Tasking/Loading_Menu_Crate.png) --- --- For each non-moveable cargo object (crates etc), **within loading range of the carrier**, the cargo will be listed and can be loaded into the carrier! --- --- ![](../Tasking/Loading_Cargo_Loaded.png) --- --- Once the cargo is loaded within your Carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne Carriers, it is required to **land first right near the cargo**, before the loading process can be initiated. --- As stated, this requires some pilot skills :-) --- --- #### Sling Load Cargo (helicopters only). --- --- If your Carrier is within the **Loading Range of the cargo**, and the cargo is **stationary**, the **cargo can also be sling loaded**! --- Note that this is only possible for helicopters. --- --- To sling load cargo, there is no task action menu required. Just follow the normal sling loading procedure and the cargo will report. --- Use the normal DCS sling loading menu system to hook the cargo you the cable attached on your helicopter. --- --- Again note that you may land firstly right next to the cargo, before the loading process can be initiated. --- As stated, this requires some pilot skills :-) --- --- --- ### Deploy cargo by Unboarding, Unloading and Sling Deploying. --- --- #### **Deploying the relevant cargo within deploy zones, will make you achieve cargo transportation tasks!!!** --- --- There are two different ways how cargo can be deployed: --- --- - **Unboarding**: Moveable cargo (like infantry or vehicles), can be unboarded, that means, --- the cargo will step out of the carrier and will run to a group location. --- Moose provides you with an additional menu system to unload stationary cargo from the carrier bays, --- using the menu. These menu options will become available, when the carrier is within the deploy zone. --- --- - **Unloading**: Stationary cargo (like crates), which are heavy, can only be unloaded or sling loaded. --- Moose provides you with an additional menu system to unload stationary cargo from the carrier bays, --- using the menu. These menu options will become available, when the carrier is within the deploy zone. --- --- - **Sling Deploying**: Stationary cargo (like crates), which are heavy, can also be sling deployed. --- Once the cargo is within the deploy zone, the cargo can be deployed from the sling onto the ground. --- --- In order to be able to deploy cargo, you'll need to know where the deploy zone is located, right? --- Fortunately, the HQ can help to route you to the locations of deploy zone. --- Use the task action menu to receive HQ help for this. --- --- ![](../Tasking/Routing_Deploy_Zone_Menu.png) --- --- Depending on the location within the battlefield, the task action menu will contain **Route options** that can be selected --- to start the HQ sending you routing messages. Also, if the carrier cargo bays contain cargo, --- then beside **Route options** there will also be **Deploy options** listed. --- These **Deploy options** are meant to route you to the deploy zone locations. --- --- ![](../Tasking/Routing_Deploy_Zone_Menu_Workplace.png) --- --- Depending on the task that you have selected, the deploy zones will be listed. --- **There may be multiple deploy zones within the mission, but only the deploy zones relevant for your task will be available in the menu!** --- --- ![](../Tasking/Routing_Deploy_Zone_Message.png) --- --- When a routing option is selected, you are sent routing messages in a selected coordinate format. --- Possible routing coordinate formats are: Bearing Range (BR), Lattitude Longitude (LL) or Military Grid System (MGRS). --- Note that for LL, there are two sub formats. (See pickup). --- --- ![](../Tasking/Routing_Deploy_Zone_Arrived.png) --- --- When you are within the range of the deploy zone (can be also a polygon!), a message is communicated by HQ that you have arrived within the zone! --- --- The routing messages are formulated in the coordinate format that is currently active as configured in your settings profile. --- Use the **Settings Menu** to select the coordinate format that you would like to use for location determination. --- --- #### Unboard Cargo. --- --- If your carrier contains cargo, and the cargo is **moveable**, the **cargo can be unboarded**! --- You can only unload cargo if there is cargo within your cargo bays within the carrier. --- --- ![](../Tasking/Unboarding_Menu.png) --- --- Select the task action menu and now an **Unboard cargo...** sub menu will be listed! --- Again, this option will only be listed if there is a non moveable cargo within your cargo bays. --- --- ![](../Tasking/Unboarding_Menu_Engineers.png) --- --- Now you will see a menu option to unload the non-moveable cargo. --- In this example, you can unload the **Engineers** that was loaded within your carrier cargo bays. --- Depending on the cargo loaded within your cargo bays, you will see other options here! --- Select the relevant menu option from the cargo unload menu, and the cargo will unloaded from your carrier. --- --- ![](../Tasking/Unboarding_Started.png) --- --- **The cargo will step out of your carrier and will move towards a grouping point.** --- When the unboarding process has started, you will be notified by a message to your carrier. --- --- ![](../Tasking/Unboarding_In_Progress.png) --- --- The moveable cargo will unboard one by one, so note that multiple units may need to unboard your Carrier, --- so it is required to await the full completion of the unboarding process. --- --- ![](../Tasking/Unboarding_Done.png) --- --- Once the cargo is fully unboarded from your carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne carriers, it is required to land first before the unboarding process can be initiated. --- If during unboarding the Carrier gets airborne, the unboarding process will be cancelled. --- * Once the moveable cargo is unboarded, they will start moving towards a specified gathering point. --- * The moveable cargo will send a message to your carrier with unboarding status updates. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- #### Unload Cargo. --- --- If your carrier contains cargo, and the cargo is **stationary**, the **cargo can be unloaded**, but not unboarded! --- You can only unload cargo if there is cargo within your cargo bays within the carrier. --- --- ![](../Tasking/Unloading_Menu.png) --- --- Select the task action menu and now an **Unload cargo...** sub menu will be listed! --- Again, this option will only be listed if there is a non moveable cargo within your cargo bays. --- --- ![](../Tasking/Unloading_Menu_Crate.png) --- --- Now you will see a menu option to unload the non-moveable cargo. --- In this example, you can unload the **Crate** that was loaded within your carrier cargo bays. --- Depending on the cargo loaded within your cargo bays, you will see other options here! --- Select the relevant menu option from the cargo unload menu, and the cargo will unloaded from your carrier. --- --- ![](../Tasking/Unloading_Done.png) --- --- Once the cargo is unloaded fom your Carrier, you may be notified of this, when there is a truck near to the cargo. --- If there is no truck near to the unload area, no message will be sent to your carrier! --- --- **Remarks:** --- --- * For airborne Carriers, it is required to land first, before the unloading process can be initiated. --- * A truck must be near the unload area to get messages to your carrier of the unload event! --- * Unloading is only for non-moveable cargo. --- * The non-moveable cargo must be within your cargo bays, or no unload option will be available. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- --- #### Sling Deploy Cargo (helicopters only). --- --- If your Carrier is within the **deploy zone**, and the cargo is **stationary**, the **cargo can also be sling deploying**! --- Note that this is only possible for helicopters. --- --- To sling deploy cargo, there is no task action menu required. Just follow the normal sling deploying procedure. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- ## Cargo tasking from a mission designer perspective. --- --- Please consult the documentation how to implement the derived classes of SET_CARGO in: --- --- - @{Tasking.Task_CARGO#TASK_CARGO}: Documents the main methods how to handle the cargo tasking from a mission designer perspective. --- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT}: Documents the specific methods how to handle the cargo transportation tasking from a mission designer perspective. --- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR}: Documents the specific methods how to handle the cargo CSAR tasking from a mission designer perspective. --- --- --- # 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** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_CARGO --- @image MOOSE.JPG - -do -- TASK_CARGO - - -- @type TASK_CARGO - -- @extends Tasking.Task#TASK - - --- Model tasks for players to transport Cargo. - -- - -- This models the process of a flexible transporation tasking system of cargo. - -- - -- # 1) A flexible tasking system. - -- - -- The TASK_CARGO classes provide you with a flexible tasking sytem, - -- that allows you to transport cargo of various types between various locations - -- and various dedicated deployment zones. - -- - -- The cargo in scope of the TASK\_CARGO classes must be explicitly given, and is of type SET\_CARGO. - -- The SET_CARGO contains a collection of CARGO objects that must be handled by the players in the mission. - -- - -- # 2) Cargo Tasking from a mission designer perspective. - -- - -- A cargo task is governed by a @{Tasking.Mission} object. Tasks are of different types. - -- The @{#TASK} object is used or derived by more detailed tasking classes that will implement the task execution mechanisms - -- and goals. - -- - -- ## 2.1) Derived cargo task classes. - -- - -- The following TASK_CARGO classes are derived from @{#TASK}. - -- - -- TASK - -- TASK_CARGO - -- TASK_CARGO_TRANSPORT - -- TASK_CARGO_CSAR - -- - -- ### 2.1.1) Cargo Tasks - -- - -- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT} - Models the transportation of cargo to deployment zones. - -- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR} - Models the rescue of downed friendly pilots from behind enemy lines. - -- - -- ## 2.2) Handle TASK_CARGO Events ... - -- - -- The TASK_CARGO classes define Cargo transport tasks, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- - -- ### 2.2.1) Boarding events. - -- - -- Specific Cargo event can be captured, that allow to trigger specific actions! - -- - -- * **Boarded**: Triggered when the Cargo has been Boarded into your Carrier. - -- * **UnBoarded**: Triggered when the cargo has been Unboarded from your Carrier and has arrived at the Deployment Zone. - -- - -- ### 2.2.2) Loading events. - -- - -- Specific Cargo event can be captured, that allow to trigger specific actions! - -- - -- * **Loaded**: Triggered when the Cargo has been Loaded into your Carrier. - -- * **UnLoaded**: Triggered when the cargo has been Unloaded from your Carrier and has arrived at the Deployment Zone. - -- - -- ### 2.2.2) Standard TASK_CARGO Events - -- - -- The TASK_CARGO is implemented using a @{Core.Fsm#FSM_TASK}, and has the following standard statuses: - -- - -- * **None**: Start of the process. - -- * **Planned**: The cargo task is planned. - -- * **Assigned**: The cargo task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The cargo task is successfully completed. - -- * **Failed**: The cargo task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- - -- - -- === - -- - -- @field #TASK_CARGO - TASK_CARGO = { - ClassName = "TASK_CARGO", - } - - --- Instantiates a new TASK_CARGO. - -- @param #TASK_CARGO self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskType The type of Cargo task. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO self - function TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_CARGO - self:F( {Mission, SetGroup, TaskName, SetCargo, TaskType}) - - self.SetCargo = SetCargo - self.TaskType = TaskType - self.SmokeColor = SMOKECOLOR.Red - - self.CargoItemCount = {} -- Map of Carriers having a cargo item count to check the cargo loading limits. - self.CargoLimit = 10 - - self.DeployZones = {} -- setmetatable( {}, { __mode = "v" } ) -- weak table on value - - self:AddTransition( "*", "CargoDeployed", "*" ) - - --- CargoDeployed Handler OnBefore for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnBeforeCargoDeployed - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - -- @return #boolean - - --- CargoDeployed Handler OnAfter for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnAfterCargoDeployed - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - -- @usage - -- - -- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- Here we add the task. We name the task "Build a Workplace". - -- -- We provide the CargoSetWorkmaterials, and a briefing as the 2nd and 3rd parameter. - -- -- The :AddTransportTask() returns a Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object, which we keep as a reference for further actions. - -- -- The WorkplaceTask holds the created and returned Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object. - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - -- Helos = { SPAWN:New( "Helicopters 1" ), SPAWN:New( "Helicopters 2" ), SPAWN:New( "Helicopters 3" ), SPAWN:New( "Helicopters 4" ), SPAWN:New( "Helicopters 5" ) } - -- EnemyHelos = { SPAWN:New( "Enemy Helicopters 1" ), SPAWN:New( "Enemy Helicopters 2" ), SPAWN:New( "Enemy Helicopters 3" ) } - -- - -- -- This is our worker method! So when a cargo is deployed within a deployment zone, this method will be called. - -- -- By example we are spawning here a random friendly helicopter and a random enemy helicopter. - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - - self:AddTransition( "*", "CargoPickedUp", "*" ) - - --- CargoPickedUp Handler OnBefore for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnBeforeCargoPickedUp - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @return #boolean - - --- CargoPickedUp Handler OnAfter for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnAfterCargoPickedUp - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - - local Fsm = self:GetUnitProcess() - --- Fsm:SetStartState( "Planned" ) --- --- Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } ) - - Fsm:AddTransition( { "Planned", "Assigned", "Cancelled", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded", "Loaded", "UnLoaded", "Landed", "Boarding" }, "SelectAction", "*" ) - - Fsm:AddTransition( "*", "RouteToPickup", "RoutingToPickup" ) - Fsm:AddProcess ( "RoutingToPickup", "RouteToPickupPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtPickup", Cancelled = "CancelRouteToPickup" } ) - Fsm:AddTransition( "Arrived", "ArriveAtPickup", "ArrivedAtPickup" ) - Fsm:AddTransition( "Cancelled", "CancelRouteToPickup", "Cancelled" ) - - Fsm:AddTransition( "*", "RouteToDeploy", "RoutingToDeploy" ) - Fsm:AddProcess ( "RoutingToDeploy", "RouteToDeployZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtDeploy", Cancelled = "CancelRouteToDeploy" } ) - Fsm:AddTransition( "Arrived", "ArriveAtDeploy", "ArrivedAtDeploy" ) - Fsm:AddTransition( "Cancelled", "CancelRouteToDeploy", "Cancelled" ) - - Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy", "Landing" }, "Land", "Landing" ) - Fsm:AddTransition( "Landing", "Landed", "Landed" ) - - Fsm:AddTransition( "*", "PrepareBoarding", "AwaitBoarding" ) - Fsm:AddTransition( "AwaitBoarding", "Board", "Boarding" ) - Fsm:AddTransition( "Boarding", "Boarded", "Boarded" ) - - Fsm:AddTransition( "*", "Load", "Loaded" ) - - Fsm:AddTransition( "*", "PrepareUnBoarding", "AwaitUnBoarding" ) - Fsm:AddTransition( "AwaitUnBoarding", "UnBoard", "UnBoarding" ) - Fsm:AddTransition( "UnBoarding", "UnBoarded", "UnBoarded" ) - - Fsm:AddTransition( "*", "Unload", "Unloaded" ) - - Fsm:AddTransition( "*", "Planned", "Planned" ) - - - Fsm:AddTransition( "Deployed", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnAfterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:SelectAction() - end - - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:onafterSelectAction( TaskUnit, Task ) - - local TaskUnitName = TaskUnit:GetName() - local MenuTime = Task:InitTaskControlMenu( TaskUnit ) - local MenuControl = Task:GetTaskControlMenu( TaskUnit ) - - Task.SetCargo:ForEachCargo( - - -- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - - if Cargo:IsAlive() then - --- if Task:is( "RoutingToPickup" ) then --- MENU_GROUP_COMMAND:New( --- TaskUnit:GetGroup(), --- "Cancel Route " .. Cargo.Name, --- MenuControl, --- self.MenuRouteToPickupCancel, --- self, --- Cargo --- ):SetTime(MenuTime) --- end - - --self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } ) - - local TaskGroup = TaskUnit:GetGroup() - - if Cargo:IsUnLoaded() then - local CargoBayFreeWeight = TaskUnit:GetCargoBayFreeWeight() - local CargoWeight = Cargo:GetWeight() - - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - -- Only when there is space within the bay to load the next cargo item! - if CargoBayFreeWeight > CargoWeight then - if Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - local NotInDeployZones = true - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - NotInDeployZones = false - end - end - if NotInDeployZones then - if not TaskUnit:InAir() then - if Cargo:CanBoard() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for boarding.", "board", TaskUnit:GetGroup() ) - local BoardMenu = MENU_GROUP:New( TaskGroup, "Board cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, BoardMenu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Board at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() .. "." ), "reporting", TaskUnit:GetGroup() ) - end - else - if Cargo:CanLoad() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for loading.", "load", TaskUnit:GetGroup() ) - local LoadMenu = MENU_GROUP:New( TaskGroup, "Load cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, LoadMenu, self.MenuLoadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Load at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. " within " .. Cargo.NearRadius .. ".", "reporting", TaskUnit:GetGroup() ) - end - else - --local Cargo = Cargo -- Cargo.CargoSlingload#CARGO_SLINGLOAD - if Cargo:CanSlingload() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for sling loading.", "slingload", TaskUnit:GetGroup() ) - local SlingloadMenu = MENU_GROUP:New( TaskGroup, "Slingload cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, SlingloadMenu, self.MenuLoadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Slingload at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. ".", "reporting", TaskUnit:GetGroup() ) - end - end - end - end - else - Cargo:ReportResetAll( TaskUnit:GetGroup() ) - end - end - else - if not Cargo:IsDeployed() == true then - local RouteToPickupMenu = MENU_GROUP:New( TaskGroup, "Route to pickup cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - --MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, RouteToPickupMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - Cargo:ReportResetAll( TaskUnit:GetGroup() ) - if Cargo:CanBoard() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local BoardMenu = MENU_GROUP:New( TaskGroup, "Board cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, BoardMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - else - if Cargo:CanLoad() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local LoadMenu = MENU_GROUP:New( TaskGroup, "Load cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, LoadMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - else - --local Cargo = Cargo -- Cargo.CargoSlingload#CARGO_SLINGLOAD - if Cargo:CanSlingload() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local SlingloadMenu = MENU_GROUP:New( TaskGroup, "Slingload cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, SlingloadMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - end - end - end - end - - -- Cargo in deployzones are flagged as deployed. - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - Task:I( { CargoIsDeployed = Task.CargoDeployed and "true" or "false" } ) - if Cargo:IsDeployed() == false then - Cargo:SetDeployed( true ) - -- Now we call a callback method to handle the CargoDeployed event. - Task:I( { CargoIsAlive = Cargo:IsAlive() and "true" or "false" } ) - if Cargo:IsAlive() then - Task:CargoDeployed( TaskUnit, Cargo, DeployZone ) - end - end - end - end - - end - - if Cargo:IsLoaded() == true and Cargo:IsLoadedInCarrier( TaskUnit ) == true then - if not TaskUnit:InAir() then - if Cargo:CanUnboard() == true then - local UnboardMenu = MENU_GROUP:New( TaskGroup, "Unboard cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnboardMenu, self.MenuUnboardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - if Cargo:CanUnload() == true then - local UnloadMenu = MENU_GROUP:New( TaskGroup, "Unload cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnloadMenu, self.MenuUnloadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - end - - -- Deployzones are optional zones that can be selected to request routing information. - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if not Cargo:IsInZone( DeployZone ) then - local RouteToDeployMenu = MENU_GROUP:New( TaskGroup, "Route to deploy cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Zone " .. DeployZoneName, RouteToDeployMenu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - - end - ) - - Task:RefreshTaskControlMenu( TaskUnit, MenuTime, "Cargo" ) - - self:__SelectAction( -1 ) - - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - --local MenuControl = Task:GetTaskControlMenu( TaskUnit ) - - --MenuControl:Remove() - end - - function Fsm:MenuBoardCargo( Cargo ) - self:__PrepareBoarding( 1.0, Cargo ) - end - - function Fsm:MenuLoadCargo( Cargo ) - self:__Load( 1.0, Cargo ) - end - - function Fsm:MenuUnboardCargo( Cargo, DeployZone ) - self:__PrepareUnBoarding( 1.0, Cargo, DeployZone ) - end - - function Fsm:MenuUnloadCargo( Cargo, DeployZone ) - self:__Unload( 1.0, Cargo, DeployZone ) - end - - function Fsm:MenuRouteToPickup( Cargo ) - self:__RouteToPickup( 1.0, Cargo ) - end - - function Fsm:MenuRouteToDeploy( DeployZone ) - self:__RouteToDeploy( 1.0, DeployZone ) - end - - - - --- - --#TASK_CAROG_TRANSPORT self - --#Wrapper.Unit#UNIT - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo.Cargo#CARGO Cargo - function Fsm:onafterRouteToPickup( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Cargo:IsAlive() then - self.Cargo = Cargo -- Cargo.Cargo#CARGO - Task:SetCargoPickup( self.Cargo, TaskUnit ) - self:__RouteToPickupPoint( -0.1 ) - end - - end - - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterArriveAtPickup( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if self.Cargo:IsAlive() then - if TaskUnit:IsAir() then - Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() ) - self:__Land( -0.1, "Pickup" ) - else - self:__SelectAction( -0.1 ) - end - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterCancelRouteToPickup( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to Cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() ) - self:__SelectAction( -0.1 ) - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - function Fsm:onafterRouteToDeploy( TaskUnit, Task, From, Event, To, DeployZone ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:F( DeployZone ) - self.DeployZone = DeployZone - Task:SetDeployZone( self.DeployZone, TaskUnit ) - self:__RouteToDeployZone( -0.1 ) - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterArriveAtDeploy( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if TaskUnit:IsAir() then - Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() ) - self:__Land( -0.1, "Deploy" ) - else - self:__SelectAction( -0.1 ) - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterCancelRouteToDeploy( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() ) - self:__SelectAction( -0.1 ) - end - - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Action == "Pickup" then - if self.Cargo:IsAlive() then - if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at pickup location...", TaskUnit:GetGroup() ) - self:__Landed( -0.1, Action ) - end - else - self:__RouteToPickup( -0.1, self.Cargo ) - end - end - else - if TaskUnit:IsAlive() then - if TaskUnit:IsInZone( self.DeployZone ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() ) - self:__Landed( -0.1, Action ) - end - else - self:__RouteToDeploy( -0.1, self.Cargo ) - end - end - end - end - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Action == "Pickup" then - if self.Cargo:IsAlive() then - if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -0.1, Action ) - else - self:__SelectAction( -0.1 ) - end - else - self:__RouteToPickup( -0.1, self.Cargo ) - end - end - else - if TaskUnit:IsAlive() then - if TaskUnit:IsInZone( self.DeployZone ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - self:__SelectAction( -0.1 ) - end - else - self:__RouteToDeploy( -0.1, self.Cargo ) - end - end - end - end - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Cargo and Cargo:IsAlive() then - self:__Board( -0.1, Cargo ) - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterBoard( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - function Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess ) - self:F({From, Event, To, TaskUnit, TaskProcess }) - TaskProcess:__Boarded( 0.1, self ) - end - - if Cargo:IsAlive() then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - --- ABORT the boarding. Split group if any and go back to select action. - else - Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() ) - if not Cargo:IsBoarding() then - Cargo:Board( TaskUnit, nil, self ) - end - end - else - --self:__ArriveAtCargo( -0.1 ) - end - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterBoarded( TaskUnit, Task, From, Event, To, Cargo ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - Cargo:MessageToGroup( "Boarded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - - self:__Load( -0.1, Cargo ) - - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLoad( TaskUnit, Task, From, Event, To, Cargo ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - if not Cargo:IsLoaded() then - Cargo:Load( TaskUnit ) - end - - Cargo:MessageToGroup( "Loaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - TaskUnit:AddCargo( Cargo ) - - Task:CargoPickedUp( TaskUnit, Cargo ) - - self:SelectAction( -1 ) - - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo - -- @param Core.Zone#ZONE_BASE DeployZone - function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID(), From, Event, To, Cargo } ) - - self.Cargo = Cargo - self.DeployZone = nil - - -- Check if the Cargo is at a deployzone... If it is, provide it as a parameter! - if Cargo:IsAlive() then - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - self.DeployZone = DeployZone -- Core.Zone#ZONE_BASE - break - end - end - self:__UnBoard( -0.1, Cargo, self.DeployZone ) - end - end - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo - -- @param Core.Zone#ZONE_BASE DeployZone - function Fsm:onafterUnBoard( TaskUnit, Task, From, Event, To, Cargo, DeployZone ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID(), From, Event, To, Cargo, DeployZone } ) - - function self.Cargo:OnEnterUnLoaded( From, Event, To, DeployZone, TaskProcess ) - self:F({From, Event, To, DeployZone, TaskProcess }) - TaskProcess:__UnBoarded( -0.1 ) - end - - if self.Cargo:IsAlive() then - self.Cargo:MessageToGroup( "UnBoarding ...", TaskUnit:GetGroup() ) - if DeployZone then - self.Cargo:UnBoard( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - else - self.Cargo:UnBoard( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - end - end - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterUnBoarded( TaskUnit, Task ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - self.Cargo:MessageToGroup( "UnBoarded cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() ) - - self:Unload( self.Cargo ) - end - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterUnload( TaskUnit, Task, From, Event, To, Cargo, DeployZone ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - if not Cargo:IsUnLoaded() then - if DeployZone then - Cargo:UnLoad( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - else - Cargo:UnLoad( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - end - end - TaskUnit:RemoveCargo( Cargo ) - - Cargo:MessageToGroup( "Unloaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - - self:Planned() - self:__SelectAction( 1 ) - end - - return self - - end - - - --- Set a limit on the amount of cargo items that can be loaded into the Carriers. - -- @param #TASK_CARGO self - -- @param CargoLimit Specifies a number of cargo items that can be loaded in the helicopter. - -- @return #TASK_CARGO - function TASK_CARGO:SetCargoLimit( CargoLimit ) - self.CargoLimit = CargoLimit - return self - end - - - ---@param Color Might be SMOKECOLOR.Blue, SMOKECOLOR.Red SMOKECOLOR.Orange, SMOKECOLOR.White or SMOKECOLOR.Green - function TASK_CARGO:SetSmokeColor(SmokeColor) - -- Makes sure Coloe is set - if SmokeColor == nil then - self.SmokeColor = SMOKECOLOR.Red -- Make sure a default color is exist - - elseif type(SmokeColor) == "number" then - self:F2(SmokeColor) - if SmokeColor > 0 and SmokeColor <=5 then -- Make sure number is within ragne, assuming first enum is one - self.SmokeColor = SMOKECOLOR.SmokeColor - end - end - end - - --@return SmokeColor - function TASK_CARGO:GetSmokeColor() - return self.SmokeColor - end - - -- @param #TASK_CARGO self - function TASK_CARGO:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_CARGO self - -- @return Core.Set#SET_CARGO The Cargo Set. - function TASK_CARGO:GetCargoSet() - - return self.SetCargo - end - - -- @param #TASK_CARGO self - -- @return #list The Deployment Zones. - function TASK_CARGO:GetDeployZones() - - return self.DeployZones - end - - -- @param #TASK_CARGO self - -- @param AI.AI_Cargo#AI_CARGO Cargo The cargo. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetCargoPickup( Cargo, TaskUnit ) - - self:F({Cargo, TaskUnit}) - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local MenuTime = self:InitTaskControlMenu( TaskUnit ) - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - - local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToPickup", "RouteToPickupPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteCargo:Reset() - ActRouteCargo:SetCoordinate( Cargo:GetCoordinate() ) - ActRouteCargo:SetRange( Cargo:GetLoadRadius() ) - ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), MenuControl, MenuTime, "Cargo" ) - ActRouteCargo:Start() - - return self - end - - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetDeployZone( DeployZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local MenuTime = self:InitTaskControlMenu( TaskUnit ) - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - - local ActRouteDeployZone = ProcessUnit:GetProcess( "RoutingToDeploy", "RouteToDeployZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteDeployZone:Reset() - ActRouteDeployZone:SetZone( DeployZone ) - ActRouteDeployZone:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Deploy Zone" .. DeployZone:GetName(), MenuControl, MenuTime, "Cargo" ) - ActRouteDeployZone:Start() - - return self - end - - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:AddDeployZone( DeployZone, TaskUnit ) - - self.DeployZones[DeployZone:GetName()] = DeployZone - - return self - end - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:RemoveDeployZone( DeployZone, TaskUnit ) - - self.DeployZones[DeployZone:GetName()] = nil - - return self - end - - -- @param #TASK_CARGO self - -- @param #list DeployZones - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetDeployZones( DeployZones, TaskUnit ) - - for DeployZoneID, DeployZone in pairs( DeployZones or {} ) do - self.DeployZones[DeployZone:GetName()] = DeployZone - end - - return self - end - - - - -- @param #TASK_CARGO self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_CARGO:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - --- Set a score when progress is made. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when there is progress on the task goals. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnProgress( Text, Score, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "Account", Text, Score ) - - return self - end - - --- Set a score when success is achieved. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when the task goals have been achieved. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnSuccess( Text, Score, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", Text, Score ) - - return self - end - - --- Set a penalty when the task goals have failed.. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when the task goals has failed. - -- @param #number Penalty The penalty in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnFail( Text, Penalty, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", Text, Penalty ) - - return self - end - - function TASK_CARGO:SetGoalTotal() - - self.GoalTotal = self.SetCargo:Count() - end - - function TASK_CARGO:GetGoalTotal() - - return self.GoalTotal - end - - -- @param #TASK_CARGO self - function TASK_CARGO:UpdateTaskInfo() - - if self:IsStatePlanned() or self:IsStateAssigned() then - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCargoSet( self.SetCargo, 10, "SOD", true ) - local Coordinates = {} - for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if not Cargo:IsLoaded() then - Coordinates[#Coordinates+1] = Cargo:GetCoordinate() - end - end - self.TaskInfo:AddCoordinates( Coordinates, 1, "M" ) - end - end - - function TASK_CARGO:ReportOrder( ReportGroup ) - - return 0 - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_CARGO self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_CARGO:GetAutoAssignPriority( AutoAssignMethod, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - return 0 - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - - - -end - - diff --git a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua deleted file mode 100644 index c9c476efa..000000000 --- a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua +++ /dev/null @@ -1,402 +0,0 @@ ---- **Tasking** - Creates and manages player TASK_ZONE_CAPTURE tasks. --- --- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human --- players capture zones in a co-operation effort. --- --- The dispatcher will implement for you mechanisms to create capture zone tasks: --- --- * As setup by the mission designer. --- * Dynamically capture zone tasks. --- --- --- --- **Specific features:** --- --- * Creates a task to capture zones and achieve mission goals. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the location of the zones to capture on the map. --- * Provide details of the zones. --- * Route to the zones. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the zones. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Various other options. --- --- # 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** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Capture_Dispatcher --- @image MOOSE.JPG - -do -- TASK_CAPTURE_DISPATCHER - - --- TASK_CAPTURE_DISPATCHER class. - -- @type TASK_CAPTURE_DISPATCHER - -- @extends Tasking.Task_Manager#TASK_MANAGER - -- @field TASK_CAPTURE_DISPATCHER.ZONE ZONE - - -- @type TASK_CAPTURE_DISPATCHER.CSAR - -- @field Wrapper.Unit#UNIT PilotUnit - -- @field Tasking.Task#TASK Task - - - --- Implements the dynamic dispatching of capture zone tasks. - -- - -- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human - -- players capture zones in a co-operation effort. - -- - -- Let's explore **step by step** how to setup the task capture zone dispatcher. - -- - -- # 1. Setup a mission environment. - -- - -- It is easy, as it works just like any other task setup, so setup a command center and a mission. - -- - -- ## 1.1. Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- The command assumes that you´ve setup a group in the mission editor with the name HQ. - -- This group will act as the command center object. - -- It is a good practice to mark this group as invisible and invulnerable. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( GROUP:FindByName( "HQ" ), "HQ" ) -- Create the CommandCenter. - -- - -- ## 1.2. Create a mission. - -- - -- Tasks work in a **mission**, which groups these tasks to achieve a joint **mission goal**. A command center can **govern multiple missions**. - -- - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Capture the blue zones.", - -- coalition.side.RED - -- ) - -- - -- - -- # 2. Dispatch a **capture zone** task. - -- - -- So, now that we have a command center and a mission, we now create the capture zone task. - -- We create the capture zone task using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() constructor. - -- - -- ## 2.1. Create the capture zones. - -- - -- Because a capture zone task will not generate the capture zones, you'll need to create them first. - -- - -- - -- -- We define here a capture zone; of the type ZONE_CAPTURE_COALITION. - -- -- The zone to be captured has the name Alpha, and was defined in the mission editor as a trigger zone. - -- CaptureZone = ZONE:New( "Alpha" ) - -- CaptureZoneCoalitionApha = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED ) - -- - -- ## 2.2. Create a set of player groups. - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the player slots, which must be aircraft (airplanes or helicopters), that can be manned by players. - -- -- We use the method FilterPrefixes to filter those player groups that have client slots, as defined in the mission editor. - -- -- In this example, we filter the groups where the name starts with "Blue Player", which captures the blue player slots. - -- local PlayerGroupSet = SET_GROUP:New():FilterPrefixes( "Blue Player" ):FilterStart() - -- - -- ## 2.3. Setup the capture zone task. - -- - -- First, we need to create a TASK_CAPTURE_DISPATCHER object. - -- - -- TaskCaptureZoneDispatcher = TASK_CAPTURE_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- So, the variable `TaskCaptureZoneDispatcher` will contain the object of class TASK_CAPTURE_DISPATCHER, - -- which will allow you to dispatch capture zone tasks: - -- - -- * for mission `Mission`, as was defined in section 1.2. - -- * for the group set `PilotGroupSet`, as was defined in section 2.2. - -- - -- Now that we have `TaskDispatcher` object, we can now **create the TaskCaptureZone**, using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() method! - -- - -- local TaskCaptureZone = TaskCaptureZoneDispatcher:AddCaptureZoneTask( - -- "Capture zone Alpha", - -- CaptureZoneCoalitionAlpha, - -- "Fly to zone Alpha and eliminate all enemy forces to capture it." ) - -- - -- As a result of this code, the `TaskCaptureZone` (returned) variable will contain an object of @{#TASK_CAPTURE_ZONE}! - -- We pass to the method the title of the task, and the `CaptureZoneCoalitionAlpha`, which is the zone to be captured, as defined in section 2.1! - -- This returned `TaskCaptureZone` object can now be used to setup additional task configurations, or to control this specific task with special events. - -- - -- And you're done! As you can see, it is a small bit of work, but the reward is great. - -- And, because all this is done using program interfaces, you can easily build a mission to capture zones yourself! - -- Based on various events happening within your mission, you can use the above methods to create new capture zones, - -- and setup a new capture zone task and assign it to a group of players, while your mission is running! - -- - -- - -- - -- @field #TASK_CAPTURE_DISPATCHER - TASK_CAPTURE_DISPATCHER = { - ClassName = "TASK_CAPTURE_DISPATCHER", - Mission = nil, - Tasks = {}, - Zones = {}, - ZoneCount = 0, - } - - - - TASK_CAPTURE_DISPATCHER.AI_A2G_Dispatcher = nil -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - - --- TASK_CAPTURE_DISPATCHER constructor. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @return #TASK_CAPTURE_DISPATCHER self - function TASK_CAPTURE_DISPATCHER:New( Mission, SetGroup ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER - - self.Mission = Mission - self.FlashNewTask = false - - self:AddTransition( "Started", "Assign", "Started" ) - self:AddTransition( "Started", "ZoneCaptured", "Started" ) - - self:__StartTasks( 5 ) - - return self - end - - - --- Link a task capture dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other coalition! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param #TASK_CAPTURE_DISPATCHER DefenseTaskCaptureDispatcher - function TASK_CAPTURE_DISPATCHER:SetDefenseTaskCaptureDispatcher( DefenseTaskCaptureDispatcher ) - - self.DefenseTaskCaptureDispatcher = DefenseTaskCaptureDispatcher - end - - - --- Get the linked task capture dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other coalition! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return #TASK_CAPTURE_DISPATCHER - function TASK_CAPTURE_DISPATCHER:GetDefenseTaskCaptureDispatcher() - - return self.DefenseTaskCaptureDispatcher - end - - - --- Link an AI A2G dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other AI A2G dispatcher! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER DefenseAIA2GDispatcher - function TASK_CAPTURE_DISPATCHER:SetDefenseAIA2GDispatcher( DefenseAIA2GDispatcher ) - - self.DefenseAIA2GDispatcher = DefenseAIA2GDispatcher - end - - - --- Get the linked AI A2G dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the AI A2G dispatcher! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - function TASK_CAPTURE_DISPATCHER:GetDefenseAIA2GDispatcher() - - return self.DefenseAIA2GDispatcher - end - - - --- Add a capture zone task. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param #string TaskPrefix (optional) The prefix of the capture zone task. - -- If no TaskPrefix is given, then "Capture" will be used as the TaskPrefix. - -- The TaskPrefix will be appended with a . + a number of 3 digits, if the TaskPrefix already exists in the task collection. - -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION CaptureZone The zone of the coalition to be captured as the task goal. - -- @param #string Briefing The briefing of the task to be shown to the player. - -- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - -- @usage - -- - -- - function TASK_CAPTURE_DISPATCHER:AddCaptureZoneTask( TaskPrefix, CaptureZone, Briefing ) - - local TaskName = TaskPrefix or "Capture" - if self.Zones[TaskName] then - self.ZoneCount = self.ZoneCount + 1 - TaskName = string.format( "%s.%03d", TaskName, self.ZoneCount ) - end - - self.Zones[TaskName] = {} - self.Zones[TaskName].CaptureZone = CaptureZone - self.Zones[TaskName].Briefing = Briefing - self.Zones[TaskName].Task = nil - self.Zones[TaskName].TaskPrefix = TaskPrefix - - self:ManageTasks() - - return self.Zones[TaskName] and self.Zones[TaskName].Task - end - - - --- Link an AI_A2G_DISPATCHER to the TASK_CAPTURE_DISPATCHER. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER AI_A2G_Dispatcher The AI Dispatcher to be linked to the tasking. - -- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - function TASK_CAPTURE_DISPATCHER:Link_AI_A2G_Dispatcher( AI_A2G_Dispatcher ) - - self.AI_A2G_Dispatcher = AI_A2G_Dispatcher -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - AI_A2G_Dispatcher.Detection:LockDetectedItems() - - return self - end - - - --- Assigns tasks to the @{Core.Set#SET_GROUP}. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_CAPTURE_DISPATCHER:ManageTasks() - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - -- Here we need to check if the pilot is still existing. --- Task = self:RemoveTask( TaskIndex ) - end - - end - - -- Now that all obsolete tasks are removed, loop through the Zone tasks. - for TaskName, CaptureZone in pairs( self.Zones ) do - - if not CaptureZone.Task then - -- New Transport Task - CaptureZone.Task = TASK_CAPTURE_ZONE:New( Mission, self.SetGroup, TaskName, CaptureZone.CaptureZone, CaptureZone.Briefing ) - CaptureZone.Task.TaskPrefix = CaptureZone.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( CaptureZone.Task ) - TaskReport:Add( TaskName ) - - -- Link the Task Dispatcher to the capture zone task, because it is used on the UpdateTaskInfo. - CaptureZone.Task:SetDispatcher( self ) - CaptureZone.Task:UpdateTaskInfo() - - function CaptureZone.Task.OnEnterAssigned( Task, From, Event, To ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Unlock( Task.TaskZoneName ) -- This will unlock the zone to be defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = true - end - - function CaptureZone.Task.OnEnterSuccess( Task, From, Event, To ) - --self:Success( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function CaptureZone.Task.OnAfterCaptured( Task, From, Event, To, TaskUnit ) - self:Captured( Task, Task.TaskPrefix, TaskUnit ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - end - - end - - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not self.FlashNewTask ) then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua deleted file mode 100644 index 33b464a35..000000000 --- a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua +++ /dev/null @@ -1,334 +0,0 @@ ---- **Tasking** - The TASK_Protect models tasks for players to protect or capture specific zones. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: MillerTime --- --- === --- --- @module Tasking.Task_Capture_Zone --- @image MOOSE.JPG - -do -- TASK_ZONE_GOAL - - --- The TASK_ZONE_GOAL class - -- @type TASK_ZONE_GOAL - -- @field Functional.ZoneGoal#ZONE_GOAL ZoneGoal - -- @extends Tasking.Task#TASK - - --- # TASK_ZONE_GOAL class, extends @{Tasking.Task#TASK} - -- - -- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone. - -- The TASK_ZONE_GOAL is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2G task is planned. - -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2G task is successfully completed. - -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- ## Set the scoring of achievements in an A2G attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_ZONE_GOAL.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. - -- * @{#TASK_ZONE_GOAL.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- * @{#TASK_ZONE_GOAL.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- - -- # 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 #TASK_ZONE_GOAL - TASK_ZONE_GOAL = { - ClassName = "TASK_ZONE_GOAL", - } - - --- Instantiates a new TASK_ZONE_GOAL. - -- @param #TASK_ZONE_GOAL self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal - -- @return #TASK_ZONE_GOAL self - function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL - self:F() - - self.ZoneGoal = ZoneGoal - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - - Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" ) - Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} ) - Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} ) - - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - self:SetTargetZone( self.ZoneGoal:GetZone() ) - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK Task - function Fsm:OnAfterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:__StartMonitoring( 0.1 ) - self:__RouteToZone( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_ZONE_GOAL Task - function Fsm:onafterStartMonitoring( TaskUnit, Task ) - self:F( { self } ) - self:__Monitor( 0.1 ) - end - - --- Monitor Loop - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_ZONE_GOAL Task - function Fsm:onafterMonitor( TaskUnit, Task ) - self:F( { self } ) - self:__Monitor( 15 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_ZONE_GOAL Task - function Fsm:onafterRouteTo( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToZone( 0.1 ) - end - end - - return self - - end - - -- @param #TASK_ZONE_GOAL self - -- @param Functional.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine. - function TASK_ZONE_GOAL:SetProtect( ZoneGoal ) - - self.ZoneGoal = ZoneGoal -- Functional.ZoneGoal#ZONE_GOAL - end - - - - -- @param #TASK_ZONE_GOAL self - function TASK_ZONE_GOAL:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )" - end - - - -- @param #TASK_ZONE_GOAL self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteZone:SetZone( TargetZone ) - end - - - -- @param #TASK_ZONE_GOAL self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_ZONE_GOAL:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteZone:GetZone() - end - - function TASK_ZONE_GOAL:SetGoalTotal( GoalTotal ) - - self.GoalTotal = GoalTotal - end - - function TASK_ZONE_GOAL:GetGoalTotal() - - return self.GoalTotal - end - -end - - -do -- TASK_CAPTURE_ZONE - - --- The TASK_CAPTURE_ZONE class - -- @type TASK_CAPTURE_ZONE - -- @field Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal - -- @extends #TASK_ZONE_GOAL - - --- # TASK_CAPTURE_ZONE class, extends @{Tasking.Task_Capture_Zone#TASK_ZONE_GOAL} - -- - -- The TASK_CAPTURE_ZONE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed. - -- These tasks are important to be executed as they will help to achieve air superiority at the vicinity. - -- - -- The TASK_CAPTURE_ZONE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_CAPTURE_ZONE - TASK_CAPTURE_ZONE = { - ClassName = "TASK_CAPTURE_ZONE", - } - - - --- Instantiates a new TASK_CAPTURE_ZONE. - -- @param #TASK_CAPTURE_ZONE self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_CAPTURE_ZONE self - function TASK_CAPTURE_ZONE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing) - local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_CAPTURE_ZONE - self:F() - - Mission:AddTask( self ) - - self.TaskCoalition = ZoneGoalCoalition:GetCoalition() - self.TaskCoalitionName = ZoneGoalCoalition:GetCoalitionName() - self.TaskZoneName = ZoneGoalCoalition:GetZoneName() - - ZoneGoalCoalition:MonitorDestroyedUnits() - - self:SetBriefing( - TaskBriefing or - "Capture Zone " .. self.TaskZoneName - ) - - self:UpdateTaskInfo( true ) - - self:SetGoal( self.ZoneGoal.Goal ) - - return self - end - - - --- Instantiates a new TASK_CAPTURE_ZONE. - -- @param #TASK_CAPTURE_ZONE self - function TASK_CAPTURE_ZONE:UpdateTaskInfo( Persist ) - - Persist = Persist or false - - local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD", Persist ) - self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist ) --- self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist ) --- self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist ) - local SetUnit = self.ZoneGoal:GetScannedSetUnit() - local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G() - local ThreatCount = SetUnit:Count() - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 20, "MOD", Persist ) - self.TaskInfo:AddInfo( "Remaining Units", ThreatCount, 21, "MOD", Persist, true) - - if self.Dispatcher then - local DefenseTaskCaptureDispatcher = self.Dispatcher:GetDefenseTaskCaptureDispatcher() -- Tasking.Task_Capture_Dispatcher#TASK_CAPTURE_DISPATCHER - - if DefenseTaskCaptureDispatcher then - -- Loop through all zones of the player Defenses, and check which zone has an assigned task! - -- The Zones collection contains a Task. This Task is checked if it is assigned. - -- If Assigned, then this task will be the task that is the closest to the defense zone. - for TaskName, CaptureZone in pairs( DefenseTaskCaptureDispatcher.Zones or {} ) do - local Task = CaptureZone.Task -- Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - if Task and Task:IsStateAssigned() then -- We also check assigned. - -- Now we register the defense player zone information to the task report. - self.TaskInfo:AddInfo( "Defense Player Zone", Task.ZoneGoal:GetName(), 30, "MOD", Persist ) - self.TaskInfo:AddCoordinate( Task.ZoneGoal:GetZone():GetCoordinate(), 31, "MOD", Persist, false, "Defense Player Coordinate" ) - end - end - end - local DefenseAIA2GDispatcher = self.Dispatcher:GetDefenseAIA2GDispatcher() -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - - if DefenseAIA2GDispatcher then - -- Loop through all the tasks of the AI Defenses, and check which zone is involved in the defenses and is active! - for Defender, Task in pairs( DefenseAIA2GDispatcher:GetDefenderTasks() or {} ) do - local DetectedItem = DefenseAIA2GDispatcher:GetDefenderTaskTarget( Defender ) - if DetectedItem then - local DetectedZone = DefenseAIA2GDispatcher.Detection:GetDetectedItemZone( DetectedItem ) - if DetectedZone then - self.TaskInfo:AddInfo( "Defense AI Zone", DetectedZone:GetName(), 40, "MOD", Persist ) - self.TaskInfo:AddCoordinate( DetectedZone:GetCoordinate(), 41, "MOD", Persist, false, "Defense AI Coordinate" ) - end - end - end - end - end - - end - - - function TASK_CAPTURE_ZONE:ReportOrder( ReportGroup ) - - local Coordinate = self.TaskInfo:GetCoordinate() - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - - -- @param #TASK_CAPTURE_ZONE self - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_CAPTURE_ZONE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName ) - - self:F( { PlayerUnit = PlayerUnit, Achieved = self.ZoneGoal.Goal:IsAchieved() } ) - - if self.ZoneGoal then - if self.ZoneGoal.Goal:IsAchieved() then - local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions() - local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions() - self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } ) - for PlayerName, PlayerContribution in pairs( PlayerContributions ) do - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions ) - end - end - self:Success() - end - end - - self:__Goal( -10, PlayerUnit, PlayerName ) - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_CAPTURE_ZONE self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_CAPTURE_ZONE:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup, AutoAssignReference ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetCoordinate() - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua deleted file mode 100644 index e27a225c0..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua +++ /dev/null @@ -1,398 +0,0 @@ ---- **Tasking** - Orchestrates the task for players to execute CSAR for downed pilots. --- --- **Specific features:** --- --- * Creates a task to retrieve a pilot @{Cargo.Cargo} from behind enemy lines. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- === --- --- Please read through the @{Tasking.Task_CARGO} process to understand the mechanisms of tasking and cargo tasking and handling. --- --- The cargo will be a downed pilot, which is located somwhere on the battlefield. Use the menus system and facilities to --- join the CSAR task, and retrieve the pilot from behind enemy lines. The menu system is generic, there is nothing --- specific on a CSAR task that requires further explanation, than reading the generic TASK_CARGO explanations. --- --- Enjoy! --- FC --- --- # 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** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Cargo_CSAR --- @image Task_Cargo_CSAR.JPG - - -do -- TASK_CARGO_CSAR - - -- @type TASK_CARGO_CSAR - -- @extends Tasking.Task_Cargo#TASK_CARGO - - --- Orchestrates the task for players to execute CSAR for downed pilots. - -- - -- CSAR tasks are suited to govern the process of return downed pilots behind enemy lines back to safetly. - -- Typically, this task is executed by helicopter pilots, but it can also be executed by ground forces! - -- - -- === - -- - -- A CSAR task can be created manually, but actually, it is better to **GENERATE** these tasks using the - -- @{Tasking.Task_Cargo_Dispatcher} module. - -- - -- Using the dispatcher, CSAR tasks will be created **automatically** when a pilot ejects from a damaged AI aircraft. - -- When this happens, the pilot actually will survive, but needs to be retrieved from behind enemy lines. - -- - -- # 1) Create a CSAR task manually (code it). - -- - -- Although it is recommended to use the dispatcher, you can create a CSAR task yourself as a mission designer. - -- It is easy, as it works just like any other task setup. - -- - -- ## 1.1) Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2) Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Retrieve the downed pilots.", - -- coalition.side.RED - -- ) - -- - -- ## 1.3) Create the CSAR cargo task. - -- - -- So, now that we have a command center and a mission, we now create the CSAR task. - -- We create the CSAR task using the @{#TASK_CARGO_CSAR.New}() constructor. - -- - -- Because a CSAR task will not generate the cargo itself, you'll need to create it first. - -- The cargo in this case will be the downed pilot! - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically refreshed when new cargo is added/deleted. - -- local CargoSet = SET_CARGO:New():FilterTypes( "Pilots" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Pilot" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name this group Engineers. - -- -- Note that the name of the cargo is "Engineers". - -- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set. - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Pilots", "Downed Pilot", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- Now that we have a CargoSet and a GroupSet, we can now create the CSARTask manually. - -- - -- -- Declare the CSAR task. - -- local CSARTask = TASK_CARGO_CSAR - -- :New( Mission, - -- GroupSet, - -- "CSAR Pilot", - -- CargoSet, - -- "Fly behind enemy lines, and retrieve the downed pilot." - -- ) - -- - -- So you can see, setting up a CSAR task manually is a lot of work. - -- It is better you use the cargo dispatcher to generate CSAR tasks and it will work as it is intended. - -- By doing this, CSAR tasking will become a dynamic experience. - -- - -- # 2) Create a task using the @{Tasking.Task_Cargo_Dispatcher} module. - -- - -- Actually, it is better to **GENERATE** these tasks using the @{Tasking.Task_Cargo_Dispatcher} module. - -- Using the dispatcher module, transport tasks can be created much more easy. - -- - -- Find below an example how to use the TASK_CARGO_DISPATCHER class: - -- - -- - -- -- Find the HQ group. - -- HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- - -- -- Create the command center with the name "Lima". - -- CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Create the mission, for the command center, with the name "CSAR Mission", a "Tactical" mission, with the mission briefing "Rescue downed pilots.", for the RED coalition. - -- Mission = MISSION - -- :New( CommandCenter, "CSAR Mission", "Tactical", "Rescue downed pilots.", coalition.side.RED ) - -- - -- -- Create the SET of GROUPs containing clients (players) that will transport the cargo. - -- -- These are have a name that start with "Rescue" and are of the "red" coalition. - -- AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Rescue" ):FilterStart() - -- - -- - -- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the AttackGroups. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- - -- -- Here the task dispatcher will generate automatically CSAR tasks once a pilot ejects. - -- TaskDispatcher:StartCSARTasks( - -- "CSAR", - -- { ZONE_UNIT:New( "Hospital", STATIC:FindByName( "Hospital" ), 100 ) }, - -- "One of our pilots has ejected. Go out to Search and Rescue our pilot!\n" .. - -- "Use the radio menu to let the command center assist you with the CSAR tasking." - -- ) - -- - -- # 3) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the tasks. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the CLASS literal to the task 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! - -- - -- NOTE: CSAR tasks are actually automatically created by the TASK_CARGO_DISPATCHER. So the underlying is not really applicable for mission designers as they will use the dispatcher instead - -- of capturing these events from manually created CSAR tasks! - -- - -- ## 3.1) Handle the **CargoPickedUp** event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the CSARTask: - -- - -- function CSARTask:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo.", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for CLASS. - -- -- @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.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function CLASS:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 3.2) Handle the **CargoDeployed** event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the CSARTask: - -- - -- function CSARTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter for CLASS. - -- -- @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.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD 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 CLASS:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- === - -- - -- @field #TASK_CARGO_CSAR - TASK_CARGO_CSAR = { - ClassName = "TASK_CARGO_CSAR", - } - - --- Instantiates a new TASK_CARGO_CSAR. - -- @param #TASK_CARGO_CSAR self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO_CSAR self - function TASK_CARGO_CSAR:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing ) - local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "CSAR", TaskBriefing ) ) -- #TASK_CARGO_CSAR - self:F() - - Mission:AddTask( self ) - - - -- Events - - self:AddTransition( "*", "CargoPickedUp", "*" ) - self:AddTransition( "*", "CargoDeployed", "*" ) - - self:F( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } ) - - --- OnAfter Transition Handler for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoPickedUp - -- @param #TASK_CARGO_CSAR self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - --- OnAfter Transition Handler for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoDeployed - -- @param #TASK_CARGO_CSAR self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - - local Fsm = self:GetUnitProcess() - - local CargoReport = REPORT:New( "Rescue a downed pilot from the following position:") - - SetCargo:ForEachCargo( - --- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - local CargoType = Cargo:GetType() - local CargoName = Cargo:GetName() - local CargoCoordinate = Cargo:GetCoordinate() - CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) ) - end - ) - - self:SetBriefing( - TaskBriefing or - CargoReport:Text() - ) - - - return self - end - - - - function TASK_CARGO_CSAR:ReportOrder( ReportGroup ) - - return 0 - end - - - --- - -- @param #TASK_CARGO_CSAR self - -- @return #boolean - function TASK_CARGO_CSAR:IsAllCargoTransported() - - local CargoSet = self:GetCargoSet() - local Set = CargoSet:GetSet() - - local DeployZones = self:GetDeployZones() - - local CargoDeployed = true - - -- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ). - for CargoID, CargoData in pairs( Set ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - - self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } ) - - if Cargo:IsDeployed() then - --- -- Loop the DeployZones set for the TASK_CARGO_CSAR. --- for DeployZoneID, DeployZone in pairs( DeployZones ) do --- --- -- If all cargo is in one of the deploy zones, then all is good. --- self:T( { Cargo.CargoObject } ) --- if Cargo:IsInZone( DeployZone ) == false then --- CargoDeployed = false --- end --- end - else - CargoDeployed = false - end - end - - self:F( { CargoDeployed = CargoDeployed } ) - - return CargoDeployed - end - - --- @param #TASK_CARGO_CSAR self - function TASK_CARGO_CSAR:onafterGoal( TaskUnit, From, Event, To ) - local CargoSet = self.CargoSet - - if self:IsAllCargoTransported() then - self:Success() - end - - self:__Goal( -10 ) - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua deleted file mode 100644 index f78b2d5cd..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ /dev/null @@ -1,919 +0,0 @@ ---- **Tasking** - Creates and manages player TASK_CARGO tasks. --- --- The **TASK_CARGO_DISPATCHER** allows you to setup various tasks for let human --- players transport cargo as part of a task. --- --- The cargo dispatcher will implement for you mechanisms to create cargo transportation tasks: --- --- * As setup by the mission designer. --- * Dynamically create CSAR missions (when a pilot is downed as part of a downed plane). --- * Dynamically spawn new cargo and create cargo taskings! --- --- --- --- **Specific features:** --- --- * Creates a task to transport @{Cargo.Cargo} to and between deployment zones. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- # 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** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Cargo_Dispatcher --- @image Task_Cargo_Dispatcher.JPG - -do -- TASK_CARGO_DISPATCHER - - --- TASK_CARGO_DISPATCHER class. - -- @type TASK_CARGO_DISPATCHER - -- @extends Tasking.Task_Manager#TASK_MANAGER - -- @field TASK_CARGO_DISPATCHER.CSAR CSAR - -- @field Core.Set#SET_ZONE SetZonesCSAR - - -- @type TASK_CARGO_DISPATCHER.CSAR - -- @field Wrapper.Unit#UNIT PilotUnit - -- @field Tasking.Task#TASK Task - - - --- Implements the dynamic dispatching of cargo tasks. - -- - -- The **TASK_CARGO_DISPATCHER** allows you to setup various tasks for let human - -- players transport cargo as part of a task. - -- - -- There are currently **two types of tasks** that can be constructed: - -- - -- * A **normal cargo transport** task, which tasks humans to transport cargo from a location towards a deploy zone. - -- * A **CSAR** cargo transport task. CSAR tasks are **automatically generated** when a friendly (AI) plane is downed and the friendly pilot ejects... - -- You as a player (the helo pilot) can go out in the battlefield, fly behind enemy lines, and rescue the pilot (back to a deploy zone). - -- - -- Let's explore **step by step** how to setup the task cargo dispatcher. - -- - -- # 1. Setup a mission environment. - -- - -- It is easy, as it works just like any other task setup, so setup a command center and a mission. - -- - -- ## 1.1. Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2. Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Transport the cargo.", - -- coalition.side.RED - -- ) - -- - -- - -- # 2. Dispatch a **transport cargo** task. - -- - -- So, now that we have a command center and a mission, we now create the transport task. - -- We create the transport task using the @{#TASK_CARGO_DISPATCHER.AddTransportTask}() constructor. - -- - -- ## 2.1. Create the cargo in the mission. - -- - -- Because a transport task will not generate the cargo itself, you'll need to create it first. - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically updated when new cargo is added/deleted. - -- local WorkmaterialsCargoSet = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Engineers" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name the type of this group "Workmaterials", so that this cargo group will be included within the WorkmaterialsCargoSet. - -- -- Note that the name of the cargo is "Engineer Team 1". - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Workmaterials", "Engineer Team 1", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopters to retrieve the pilot, that can be manned by players. - -- -- The name of these helicopter groups containing one client begins with "Transport", as modelled within the mission editor. - -- local PilotGroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- ## 2.2. Setup the cargo transport task. - -- - -- First, we need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- So, the variable `TaskDispatcher` will contain the object of class TASK_CARGO_DISPATCHER, which will allow you to dispatch cargo transport tasks: - -- - -- * for mission `Mission`. - -- * for the group set `PilotGroupSet`. - -- - -- Now that we have `TaskDispatcher` object, we can now **create the TransportTask**, using the @{#TASK_CARGO_DISPATCHER.AddTransportTask}() method! - -- - -- local TransportTask = TaskDispatcher:AddTransportTask( - -- "Transport workmaterials", - -- WorkmaterialsCargoSet, - -- "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- As a result of this code, the `TransportTask` (returned) variable will contain an object of @{#TASK_CARGO_TRANSPORT}! - -- We pass to the method the title of the task, and the `WorkmaterialsCargoSet`, which is the set of cargo groups to be transported! - -- This object can also be used to setup additional things, or to control this specific task with special actions. - -- - -- And you're done! As you can see, it is a bit of work, but the reward is great. - -- And, because all this is done using program interfaces, you can build a mission with a **dynamic cargo transport task mechanism** yourself! - -- Based on events happening within your mission, you can use the above methods to create new cargo, and setup a new task for cargo transportation to a group of players! - -- - -- - -- # 3. Dispatch CSAR tasks. - -- - -- CSAR tasks can be dynamically created when a friendly pilot ejects, or can be created manually. - -- We'll explore both options. - -- - -- ## 3.1. CSAR task dynamic creation. - -- - -- Because there is an "event" in a running simulation that creates CSAR tasks, the method @{#TASK_CARGO_DISPATCHER.StartCSARTasks}() will create automatically: - -- - -- 1. a new downed pilot at the location where the plane was shot - -- 2. declare that pilot as cargo - -- 3. creates a CSAR task automatically to retrieve that pilot - -- 4. requires deploy zones to be specified where to transport the downed pilot to, in order to complete that task. - -- - -- You create a CSAR task dynamically in a very easy way: - -- - -- TaskDispatcher:StartCSARTasks( - -- "CSAR", - -- { ZONE_UNIT:New( "Hospital", STATIC:FindByName( "Hospital" ), 100 ) }, - -- "One of our pilots has ejected. Go out to Search and Rescue our pilot!\n" .. - -- "Use the radio menu to let the command center assist you with the CSAR tasking." - -- ) - -- - -- The method @{#TASK_CARGO_DISPATCHER.StopCSARTasks}() will automatically stop with the creation of CSAR tasks when friendly pilots eject. - -- - -- **Remarks:** - -- - -- * the ZONE_UNIT can also be a ZONE, or a ZONE_POLYGON object, or any other ZONE_ object! - -- * you can declare the array of zones in another variable, or course! - -- - -- - -- ## 3.2. CSAR task manual creation. - -- - -- We create the CSAR task using the @{#TASK_CARGO_DISPATCHER.AddCSARTask}() constructor. - -- - -- The method will create a new CSAR task, and will generate the pilots cargo itself, at the specified coordinate. - -- - -- What is first needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- We need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, GroupSet ) - -- - -- So, the variable `TaskDispatcher` will contain the object of class TASK_CARGO_DISPATCHER, which will allow you to dispatch cargo CSAR tasks: - -- - -- * for mission `Mission`. - -- * for the group of players (pilots) captured within the `GroupSet` (those groups with a name starting with `"Transport"`). - -- - -- Now that we have a PilotsCargoSet and a GroupSet, we can now create the CSAR task manually. - -- - -- -- Declare the CSAR task. - -- local CSARTask = TaskDispatcher:AddCSARTask( - -- "CSAR Task", - -- Coordinate, - -- 270, - -- "Bring the pilot back!" - -- ) - -- - -- As a result of this code, the `CSARTask` (returned) variable will contain an object of @{#TASK_CARGO_CSAR}! - -- We pass to the method the title of the task, and the `WorkmaterialsCargoSet`, which is the set of cargo groups to be transported! - -- This object can also be used to setup additional things, or to control this specific task with special actions. - -- Note that when you declare a CSAR task manually, you'll still need to specify a deployment zone! - -- - -- # 4. Setup the deploy zone(s). - -- - -- The task cargo dispatcher also foresees methods to setup the deployment zones to where the cargo needs to be transported! - -- - -- There are two levels on which deployment zones can be configured: - -- - -- * Default deploy zones: The TASK_CARGO_DISPATCHER object can have default deployment zones, which will apply over all tasks active in the task dispatcher. - -- * Task specific deploy zones: The TASK_CARGO_DISPATCHER object can have specific deployment zones which apply to a specific task only! - -- - -- Note that for Task specific deployment zones, there are separate deployment zone creation methods per task type! - -- - -- ## 4.1. Setup default deploy zones. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetDefaultDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetDefaultDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.2. Setup task specific deploy zones for a **transport task**. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetTransportDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetTransportDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.3. Setup task specific deploy zones for a **CSAR task**. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetCSARDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetCSARDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.4. **CSAR ejection zones**. - -- - -- Setup a set of zones where the pilots will only eject and a task is created for CSAR. When such a set of zones is given, any ejection outside those zones will not result in a pilot created for CSAR! - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetCSARZones}() to setup the set of zones. - -- - -- ## 4.5. **CSAR ejection maximum**. - -- - -- Setup how many pilots will eject the maximum. This to avoid an overload of CSAR tasks being created :-) The default is endless CSAR tasks. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetMaxCSAR}() to setup the maximum of pilots that will eject for CSAR. - -- - -- - -- # 5) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the dispatcher. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the CLASS literal to the task 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! - -- - -- First, we need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- Second, we create a new cargo transport task for the transportation of workmaterials. - -- - -- TaskDispatcher:AddTransportTask( - -- "Transport workmaterials", - -- WorkmaterialsCargoSet, - -- "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- Note that we don't really need to keep the resulting task, it is kept internally also in the dispatcher. - -- - -- Using the `TaskDispatcher` object, we can now cpature the CargoPickedUp and CargoDeployed events. - -- - -- ## 5.1) Handle the **CargoPickedUp** event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the `TaskDispatcher`: - -- - -- function TaskDispatcher:OnAfterCargoPickedUp( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo for task " .. Task:GetName() .. ".", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for CLASS. - -- -- @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 Tasking.Task_Cargo#TASK_CARGO Task The cargo task for which the cargo has been picked up. Note that this will be a derived TAKS_CARGO object! - -- -- @param #string TaskPrefix The prefix of the task that was provided when the task was created. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function CLASS:OnAfterCargoPickedUp( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 5.2) Handle the **CargoDeployed** event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the `TaskDispatcher`: - -- - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName() .. " for task " .. Task:GetName() .. ".", MESSAGE.Type.Information ):ToAll() - -- - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter for CLASS. - -- -- @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 Tasking.Task_Cargo#TASK_CARGO Task The cargo task for which the cargo has been deployed. Note that this will be a derived TAKS_CARGO object! - -- -- @param #string TaskPrefix The prefix of the task that was provided when the task was created. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD 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 CLASS:OnAfterCargoDeployed( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- - -- @field #TASK_CARGO_DISPATCHER - TASK_CARGO_DISPATCHER = { - ClassName = "TASK_CARGO_DISPATCHER", - Mission = nil, - Tasks = {}, - CSAR = {}, - CSARSpawned = 0, - - Transport = {}, - TransportCount = 0, - } - - - --- TASK_CARGO_DISPATCHER constructor. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @return #TASK_CARGO_DISPATCHER self - function TASK_CARGO_DISPATCHER:New( Mission, SetGroup ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CARGO_DISPATCHER - - self.Mission = Mission - - self:AddTransition( "Started", "Assign", "Started" ) - self:AddTransition( "Started", "CargoPickedUp", "Started" ) - self:AddTransition( "Started", "CargoDeployed", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_CARGO_DISPATCHER] OnAfterAssign - -- @param #TASK_CARGO_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#TASK_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:SetCSARRadius() - self:__StartTasks( 5 ) - - self.MaxCSAR = nil - self.CountCSAR = 0 - - -- For CSAR missions, we process the event when a pilot ejects. - - self:HandleEvent( EVENTS.Ejection ) - - return self - end - - - --- Sets the set of zones were pilots will only be spawned (eject) when the planes crash. - -- Note that because this is a set of zones, the MD can create the zones dynamically within his mission! - -- Just provide a set of zones, see usage, but find the tactical situation here: - -- - -- ![CSAR Zones](../Tasking/CSAR_Zones.JPG) - -- - -- @param #TASK_CARGO_DISPATCHER self - -- @param Core.Set#SET_ZONE SetZonesCSAR The set of zones where pilots will only be spawned for CSAR when they eject. - -- @usage - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- -- Use this call to pass the set of zones. - -- -- Note that you can create the set of zones inline, because the FilterOnce method (and other SET_ZONE methods return self). - -- -- So here the zones can be created as normal trigger zones (MOOSE creates a collection of ZONE objects when teh mission starts of all trigger zones). - -- -- Just name them as CSAR zones here. - -- TaskDispatcher:SetCSARZones( SET_ZONE:New():FilterPrefixes("CSAR"):FilterOnce() ) - -- - function TASK_CARGO_DISPATCHER:SetCSARZones( SetZonesCSAR ) - - self.SetZonesCSAR = SetZonesCSAR - - end - - - --- Sets the maximum of pilots that will be spawned (eject) when the planes crash. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #number MaxCSAR The maximum of pilots that will eject for CSAR. - -- @usage - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- -- Use this call to the maximum of CSAR to 10. - -- TaskDispatcher:SetMaxCSAR( 10 ) - -- - function TASK_CARGO_DISPATCHER:SetMaxCSAR( MaxCSAR ) - - self.MaxCSAR = MaxCSAR - - end - - - - --- Handle the event when a pilot ejects. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function TASK_CARGO_DISPATCHER:OnEventEjection( EventData ) - self:F( { EventData = EventData } ) - - if self.CSARTasks == true then - - local CSARCoordinate = EventData.IniUnit:GetCoordinate() - local CSARCoalition = EventData.IniUnit:GetCoalition() - local CSARCountry = EventData.IniUnit:GetCountry() - local CSARHeading = EventData.IniUnit:GetHeading() - - -- Only add a CSAR task if the coalition of the mission is equal to the coalition of the ejected unit. - if CSARCoalition == self.Mission:GetCommandCenter():GetCoalition() then - -- And only add if the eject is in one of the zones, if defined. - if not self.SetZonesCSAR or ( self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone( CSARCoordinate ) ) then - -- And only if the maximum of pilots is not reached that ejected! - if not self.MaxCSAR or ( self.MaxCSAR and self.CountCSAR < self.MaxCSAR ) then - local CSARTaskName = self:AddCSARTask( self.CSARTaskName, CSARCoordinate, CSARHeading, CSARCountry, self.CSARBriefing ) - self:SetCSARDeployZones( CSARTaskName, self.CSARDeployZones ) - self.CountCSAR = self.CountCSAR + 1 - end - end - end - end - - return self - end - - - --- Define one default deploy zone for all the cargo tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param DefaultDeployZone A default deploy zone. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:SetDefaultDeployZone( DefaultDeployZone ) - - self.DefaultDeployZones = { DefaultDeployZone } - - return self - end - - - --- Define the deploy zones for all the cargo tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param DefaultDeployZones A list of the deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetDefaultDeployZones( DefaultDeployZones ) - - self.DefaultDeployZones = DefaultDeployZones - - return self - end - - - --- Start the generation of CSAR tasks to retrieve a downed pilots. - -- You need to specify a task briefing, a task name, default deployment zone(s). - -- This method can only be used once! - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName The CSAR task name. - -- @param #string CSARDeployZones The zones to where the CSAR deployment should be directed. - -- @param #string CSARBriefing The briefing of the CSAR tasks. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:StartCSARTasks( CSARTaskName, CSARDeployZones, CSARBriefing) - - if not self.CSARTasks then - self.CSARTasks = true - self.CSARTaskName = CSARTaskName - self.CSARDeployZones = CSARDeployZones - self.CSARBriefing = CSARBriefing - else - error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks has already started." ) - end - - return self - end - - - --- Stop the generation of CSAR tasks to retrieve a downed pilots. - -- @param #TASK_CARGO_DISPATCHER self - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:StopCSARTasks() - - if self.CSARTasks then - self.CSARTasks = nil - self.CSARTaskName = nil - self.CSARDeployZones = nil - self.CSARBriefing = nil - else - error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks was not yet started." ) - end - - return self - end - - - --- Add a CSAR task to retrieve a downed pilot. - -- You need to specify a coordinate from where the pilot will be spawned to be rescued. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskPrefix (optional) The prefix of the CSAR task. - -- @param Core.Point#COORDINATE CSARCoordinate The coordinate where a downed pilot will be spawned. - -- @param #number CSARHeading The heading of the pilot in degrees. - -- @param #DCSCountry CSARCountry The country ID of the pilot that will be spawned. - -- @param #string CSARBriefing The briefing of the CSAR task. - -- @return #string The CSAR Task Name as a string. The Task Name is the main key and is shown in the task list of the Mission Tasking menu. - -- @usage - -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate. - -- local Coordinate = PlaneUnit:GetPointVec2() - -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate ) - -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). - -- local Coordinate = PlaneUnit:GetPointVec2() - -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate, 270, Country.RUSSIA ) - -- - function TASK_CARGO_DISPATCHER:AddCSARTask( CSARTaskPrefix, CSARCoordinate, CSARHeading, CSARCountry, CSARBriefing ) - - local CSARCoalition = self.Mission:GetCommandCenter():GetCoalition() - - CSARHeading = CSARHeading or 0 - CSARCountry = CSARCountry or self.Mission:GetCommandCenter():GetCountry() - - self.CSARSpawned = self.CSARSpawned + 1 - - local CSARTaskName = string.format( ( CSARTaskPrefix or "CSAR" ) .. ".%03d", self.CSARSpawned ) - - -- Create the CSAR Pilot SPAWN object. - -- Let us create the Template for the replacement Pilot :-) - local Template = { - ["visible"] = false, - ["hidden"] = false, - ["task"] = "Ground Nothing", - ["name"] = string.format( "CSAR Pilot#%03d", self.CSARSpawned ), - ["x"] = CSARCoordinate.x, - ["y"] = CSARCoordinate.z, - ["units"] = - { - [1] = - { - ["type"] = ( CSARCoalition == coalition.side.BLUE ) and "Soldier M4" or "Infantry AK", - ["name"] = string.format( "CSAR Pilot#%03d-01", self.CSARSpawned ), - ["skill"] = "Excellent", - ["playerCanDrive"] = false, - ["x"] = CSARCoordinate.x, - ["y"] = CSARCoordinate.z, - ["heading"] = CSARHeading, - }, -- end of [1] - }, -- end of ["units"] - } - - local CSARGroup = GROUP:NewTemplate( Template, CSARCoalition, Group.Category.GROUND, CSARCountry ) - - self.CSAR[CSARTaskName] = {} - self.CSAR[CSARTaskName].PilotGroup = CSARGroup - self.CSAR[CSARTaskName].Briefing = CSARBriefing - self.CSAR[CSARTaskName].Task = nil - self.CSAR[CSARTaskName].TaskPrefix = CSARTaskPrefix - - return CSARTaskName - end - - - --- Define the radius to when a CSAR task will be generated for any downed pilot within range of the nearest CSAR airbase. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #number CSARRadius (Optional, Default = 50000) The radius in meters to decide whether a CSAR needs to be created. - -- @return #TASK_CARGO_DISPATCHER - -- @usage - -- - -- -- Set 20km as the radius to CSAR any downed pilot within range of the nearest CSAR airbase. - -- TaskA2ADispatcher:SetEngageRadius( 20000 ) - -- - -- -- Set 50km as the radius to to CSAR any downed pilot within range of the nearest CSAR airbase. - -- TaskA2ADispatcher:SetEngageRadius() -- 50000 is the default value. - -- - function TASK_CARGO_DISPATCHER:SetCSARRadius( CSARRadius ) - - self.CSARRadius = CSARRadius or 50000 - - return self - end - - - --- Define one deploy zone for the CSAR tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName (optional) The name of the CSAR task. - -- @param CSARDeployZone A CSAR deploy zone. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:SetCSARDeployZone( CSARTaskName, CSARDeployZone ) - - if CSARTaskName then - self.CSAR[CSARTaskName].DeployZones = { CSARDeployZone } - end - - return self - end - - - --- Define the deploy zones for the CSAR tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName (optional) The name of the CSAR task. - -- @param CSARDeployZones A list of the CSAR deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetCSARDeployZones( CSARTaskName, CSARDeployZones ) - - if CSARTaskName and self.CSAR[CSARTaskName] then - self.CSAR[CSARTaskName].DeployZones = CSARDeployZones - end - - return self - end - - - --- Add a Transport task to transport cargo from fixed locations to a deployment zone. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string TaskPrefix (optional) The prefix of the transport task. - -- This prefix will be appended with a . + a number of 3 digits. - -- If no TaskPrefix is given, then "Transport" will be used as the prefix. - -- @param Core.Set#SET_CARGO SetCargo The SetCargo to be transported. - -- @param #string Briefing The briefing of the task transport to be shown to the player. - -- @param #boolean Silent If true don't send a message that a new task is available. - -- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT - -- @usage - -- - -- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- Here we add the task. We name the task "Build a Workplace". - -- -- We provide the CargoSetWorkmaterials, and a briefing as the 2nd and 3rd parameter. - -- -- The :AddTransportTask() returns a Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object, which we keep as a reference for further actions. - -- -- The WorkplaceTask holds the created and returned Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object. - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing, Silent ) - - self.TransportCount = self.TransportCount + 1 - - local verbose = Silent or false - - local TaskName = string.format( ( TaskPrefix or "Transport" ) .. ".%03d", self.TransportCount ) - - self.Transport[TaskName] = {} - self.Transport[TaskName].SetCargo = SetCargo - self.Transport[TaskName].Briefing = Briefing - self.Transport[TaskName].Task = nil - self.Transport[TaskName].TaskPrefix = TaskPrefix - - self:ManageTasks(verbose) - - return self.Transport[TaskName] and self.Transport[TaskName].Task - end - - - --- Define one deploy zone for the Transport tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT Task The name of the Transport task. - -- @param TransportDeployZone A Transport deploy zone. - -- @return #TASK_CARGO_DISPATCHER - -- @usage - -- - -- - function TASK_CARGO_DISPATCHER:SetTransportDeployZone( Task, TransportDeployZone ) - - if self.Transport[Task.TaskName] then - self.Transport[Task.TaskName].DeployZones = { TransportDeployZone } - else - error( "Task does not exist" ) - end - - self:ManageTasks() - - return self - end - - - --- Define the deploy zones for the Transport tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT Task The name of the Transport task. - -- @param TransportDeployZones A list of the Transport deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetTransportDeployZones( Task, TransportDeployZones ) - - if self.Transport[Task.TaskName] then - self.Transport[Task.TaskName].DeployZones = TransportDeployZones - else - error( "Task does not exist" ) - end - - self:ManageTasks() - - return self - end - - --- Evaluates of a CSAR task needs to be started. - -- @param #TASK_CARGO_DISPATCHER self - -- @return Core.Set#SET_CARGO The SetCargo to be rescued. - -- @return #nil If there is no CSAR task required. - function TASK_CARGO_DISPATCHER:EvaluateCSAR( CSARUnit ) - - local CSARCargo = CARGO_GROUP:New( CSARUnit, "Pilot", CSARUnit:GetName(), 80, 1500, 10 ) - - local SetCargo = SET_CARGO:New() - SetCargo:AddCargosByName( CSARUnit:GetName() ) - - SetCargo:Flush(self) - - return SetCargo - - end - - - - --- Assigns tasks to the @{Core.Set#SET_GROUP}. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #boolean Silent Announce new task (nil/false) or not (true). - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_CARGO_DISPATCHER:ManageTasks(Silent) - self:F() - local verbose = Silent and true - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - -- Here we need to check if the pilot is still existing. --- local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) --- if not DetectedItem then --- local TaskText = Task:GetName() --- for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do --- Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) --- end --- Task = self:RemoveTask( TaskIndex ) --- end - end - end - - -- Now that all obsolete tasks are removed, loop through the CSAR pilots. - for CSARName, CSAR in pairs( self.CSAR ) do - - if not CSAR.Task then - -- New CSAR Task - local SetCargo = self:EvaluateCSAR( CSAR.PilotGroup ) - CSAR.Task = TASK_CARGO_CSAR:New( Mission, self.SetGroup, CSARName, SetCargo, CSAR.Briefing ) - CSAR.Task.TaskPrefix = CSAR.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( CSAR.Task ) - TaskReport:Add( CSARName ) - if CSAR.DeployZones then - CSAR.Task:SetDeployZones( CSAR.DeployZones or {} ) - else - CSAR.Task:SetDeployZones( self.DefaultDeployZones or {} ) - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function CSAR.Task.OnAfterCargoPickedUp( Task, From, Event, To, TaskUnit, Cargo ) - self:CargoPickedUp( Task, Task.TaskPrefix, TaskUnit, Cargo ) - end - - -- Now broadcast the onafterCargoDeployed event to the Task Cargo Dispatcher. - function CSAR.Task.OnAfterCargoDeployed( Task, From, Event, To, TaskUnit, Cargo, DeployZone ) - self:CargoDeployed( Task, Task.TaskPrefix, TaskUnit, Cargo, DeployZone ) - end - - end - end - - - -- Now that all obsolete tasks are removed, loop through the Transport tasks. - for TransportName, Transport in pairs( self.Transport ) do - - if not Transport.Task then - -- New Transport Task - Transport.Task = TASK_CARGO_TRANSPORT:New( Mission, self.SetGroup, TransportName, Transport.SetCargo, Transport.Briefing ) - Transport.Task.TaskPrefix = Transport.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( Transport.Task ) - TaskReport:Add( TransportName ) - function Transport.Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Transport.Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Transport.Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Transport.Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function Transport.Task.OnAfterCargoPickedUp( Task, From, Event, To, TaskUnit, Cargo ) - self:CargoPickedUp( Task, Task.TaskPrefix, TaskUnit, Cargo ) - end - - -- Now broadcast the onafterCargoDeployed event to the Task Cargo Dispatcher. - function Transport.Task.OnAfterCargoDeployed( Task, From, Event, To, TaskUnit, Cargo, DeployZone ) - self:CargoDeployed( Task, Task.TaskPrefix, TaskUnit, Cargo, DeployZone ) - end - - end - - if Transport.DeployZones then - Transport.Task:SetDeployZones( Transport.DeployZones or {} ) - else - Transport.Task:SetDeployZones( self.DefaultDeployZones or {} ) - end - - end - - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and not verbose then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua deleted file mode 100644 index 6293f03fa..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua +++ /dev/null @@ -1,363 +0,0 @@ ---- **Tasking** - Models tasks for players to transport cargo. --- --- **Specific features:** --- --- * Creates a task to transport #Cargo.Cargo to and between deployment zones. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- # 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 --- --- === --- --- Please read through the #Tasking.Task_Cargo process to understand the mechanisms of tasking and cargo tasking and handling. --- --- Enjoy! --- FC --- --- === --- --- @module Tasking.Task_Cargo_Transport --- @image Task_Cargo_Transport.JPG - - -do -- TASK_CARGO_TRANSPORT - - -- @type TASK_CARGO_TRANSPORT - -- @extends Tasking.Task_CARGO#TASK_CARGO - - --- Orchestrates the task for players to transport cargo to or between deployment zones. - -- - -- Transport tasks are suited to govern the process of transporting cargo to specific deployment zones. - -- Typically, this task is executed by helicopter pilots, but it can also be executed by ground forces! - -- - -- === - -- - -- A transport task can be created manually. - -- - -- # 1) Create a transport task manually (code it). - -- - -- Although it is recommended to use the dispatcher, you can create a transport task yourself as a mission designer. - -- It is easy, as it works just like any other task setup. - -- - -- ## 1.1) Create a command center. - -- - -- First you need to create a command center using the Tasking.CommandCenter#COMMANDCENTER.New constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2) Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the Tasking.Mission#MISSION.New constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Transport the cargo to the deploy zones.", - -- coalition.side.RED - -- ) - -- - -- ## 1.3) Create the transport cargo task. - -- - -- So, now that we have a command center and a mission, we now create the transport task. - -- We create the transport task using the #TASK_CARGO_TRANSPORT.New constructor. - -- - -- Because a transport task will not generate the cargo itself, you'll need to create it first. - -- The cargo in this case will be the downed pilot! - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically refreshed when new cargo is added/deleted. - -- local CargoSet = SET_CARGO:New():FilterTypes( "Cargo" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Engineers" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name this group Engineers. - -- -- Note that the name of the cargo is "Engineers". - -- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set. - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Cargo", "Engineer Team 1", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- Now that we have a CargoSet and a GroupSet, we can now create the TransportTask manually. - -- - -- -- Declare the transport task. - -- local TransportTask = TASK_CARGO_TRANSPORT - -- :New( Mission, - -- GroupSet, - -- "Transport Engineers", - -- CargoSet, - -- "Fly behind enemy lines, and retrieve the downed pilot." - -- ) - -- - -- So you can see, setting up a transport task manually is a lot of work. - -- It is better you use the cargo dispatcher to create transport tasks and it will work as it is intended. - -- By doing this, cargo transport tasking will become a dynamic experience. - -- - -- - -- # 2) Create a task using the Tasking.Task_Cargo_Dispatcher module. - -- - -- Actually, it is better to **GENERATE** these tasks using the Tasking.Task_Cargo_Dispatcher module. - -- Using the dispatcher module, transport tasks can be created easier. - -- - -- Find below an example how to use the TASK_CARGO_DISPATCHER class: - -- - -- - -- -- Find the HQ group. - -- HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- - -- -- Create the command center with the name "Lima". - -- CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition. - -- Mission = MISSION - -- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE ) - -- - -- -- Create the SET of GROUPs containing clients (players) that will transport the cargo. - -- -- These are have a name that start with "Transport" and are of the "blue" coalition. - -- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart() - -- - -- - -- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- - -- -- Here we declare the SET of CARGOs called "Workmaterials". - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- - -- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set. - -- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on. - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - -- # 3) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the tasks. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the "myclass" literal to the task 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! - -- - -- - -- ## 3.1) Handle the CargoPickedUp event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the WorkplaceTask: - -- - -- function WorkplaceTask:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo.", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for "myclass". - -- -- @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.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function myclass:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 3.2) Handle the CargoDeployed event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the WorkplaceTask: - -- - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() - -- - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter foR "myclass". - -- -- @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.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD 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 myclass:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- - -- === - -- - -- @field #TASK_CARGO_TRANSPORT - TASK_CARGO_TRANSPORT = { - ClassName = "TASK_CARGO_TRANSPORT", - } - - --- Instantiates a new TASK_CARGO_TRANSPORT. - -- @param #TASK_CARGO_TRANSPORT self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO_TRANSPORT self - function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing ) - local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport", TaskBriefing ) ) -- #TASK_CARGO_TRANSPORT - self:F() - - Mission:AddTask( self ) - - local Fsm = self:GetUnitProcess() - - local CargoReport = REPORT:New( "Transport Cargo. The following cargo needs to be transported including initial positions:") - - SetCargo:ForEachCargo( - -- @param Core.Cargo#CARGO Cargo - function( Cargo ) - local CargoType = Cargo:GetType() - local CargoName = Cargo:GetName() - local CargoCoordinate = Cargo:GetCoordinate() - CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) ) - end - ) - - self:SetBriefing( - TaskBriefing or - CargoReport:Text() - ) - - - return self - end - - function TASK_CARGO_TRANSPORT:ReportOrder( ReportGroup ) - - return 0 - end - - - --- - -- @param #TASK_CARGO_TRANSPORT self - -- @return #boolean - function TASK_CARGO_TRANSPORT:IsAllCargoTransported() - - local CargoSet = self:GetCargoSet() - local Set = CargoSet:GetSet() - - local DeployZones = self:GetDeployZones() - - local CargoDeployed = true - - -- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ). - for CargoID, CargoData in pairs( Set ) do - local Cargo = CargoData -- Core.Cargo#CARGO - - self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } ) - - if Cargo:IsDeployed() then - --- -- Loop the DeployZones set for the TASK_CARGO_TRANSPORT. --- for DeployZoneID, DeployZone in pairs( DeployZones ) do --- --- -- If all cargo is in one of the deploy zones, then all is good. --- self:T( { Cargo.CargoObject } ) --- if Cargo:IsInZone( DeployZone ) == false then --- CargoDeployed = false --- end --- end - else - CargoDeployed = false - end - end - - self:F( { CargoDeployed = CargoDeployed } ) - - return CargoDeployed - end - - -- @param #TASK_CARGO_TRANSPORT self - function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To ) - local CargoSet = self.CargoSet - - if self:IsAllCargoTransported() then - self:Success() - end - - self:__Goal( -10 ) - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Manager.lua b/Moose Development/Moose/Tasking/Task_Manager.lua deleted file mode 100644 index 127b455ad..000000000 --- a/Moose Development/Moose/Tasking/Task_Manager.lua +++ /dev/null @@ -1,192 +0,0 @@ ---- **Tasking** - This module contains the TASK_MANAGER class and derived classes. --- --- === --- --- 1) @{Tasking.Task_Manager#TASK_MANAGER} class, extends @{Core.Fsm#FSM} --- === --- The @{Tasking.Task_Manager#TASK_MANAGER} class defines the core functions to report tasks to groups. --- Reportings can be done in several manners, and it is up to the derived classes if TASK_MANAGER to model the reporting behaviour. --- --- 1.1) TASK_MANAGER constructor: --- ----------------------------------- --- * @{Tasking.Task_Manager#TASK_MANAGER.New}(): Create a new TASK_MANAGER instance. --- --- 1.2) TASK_MANAGER reporting: --- --------------------------------- --- Derived TASK_MANAGER classes will manage tasks using the method @{Tasking.Task_Manager#TASK_MANAGER.ManageTasks}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the task management can be changed using the methods @{Tasking.Task_Manager#TASK_MANAGER.SetRefreshTimeInterval}(). --- To control how long a reporting message is displayed, use @{Tasking.Task_Manager#TASK_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{Tasking.Task_Manager#TASK_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Task management can be started and stopped using the methods @{Tasking.Task_Manager#TASK_MANAGER.StartTasks}() and @{Tasking.Task_Manager#TASK_MANAGER.StopTasks}() respectively. --- If an ad-hoc report is requested, use the method @{Tasking.Task_Manager#TASK_MANAGER#ManageTasks}(). --- --- The default task management interval is every 60 seconds. --- --- # 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 --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module Tasking.Task_Manager --- @image MOOSE.JPG - -do -- TASK_MANAGER - - --- TASK_MANAGER class. - -- @type TASK_MANAGER - -- @field Core.Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed. - -- @extends Core.Fsm#FSM - TASK_MANAGER = { - ClassName = "TASK_MANAGER", - SetGroup = nil, - } - - --- TASK\_MANAGER constructor. - -- @param #TASK_MANAGER self - -- @param Core.Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed. - -- @return #TASK_MANAGER self - function TASK_MANAGER:New( SetGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #TASK_MANAGER - - self.SetGroup = SetGroup - - self:SetStartState( "Stopped" ) - self:AddTransition( "Stopped", "StartTasks", "Started" ) - - --- StartTasks Handler OnBefore for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnBeforeStartTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- StartTasks Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterStartTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- StartTasks Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] StartTasks - -- @param #TASK_MANAGER self - - --- StartTasks Asynchronous Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] __StartTasks - -- @param #TASK_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "StopTasks", "Stopped" ) - - --- StopTasks Handler OnBefore for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnBeforeStopTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- StopTasks Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterStopTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- StopTasks Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] StopTasks - -- @param #TASK_MANAGER self - - --- StopTasks Asynchronous Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] __StopTasks - -- @param #TASK_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "Manage", "Started" ) - - self:AddTransition( "Started", "Success", "Started" ) - - --- Success Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterSuccess - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Failed", "Started" ) - - --- Failed Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterFailed - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Aborted", "Started" ) - - --- Aborted Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterAborted - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Cancelled", "Started" ) - - --- Cancelled Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterCancelled - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:SetRefreshTimeInterval( 30 ) - - return self - end - - function TASK_MANAGER:onafterStartTasks( From, Event, To ) - self:Manage() - end - - function TASK_MANAGER:onafterManage( From, Event, To ) - - self:__Manage( -self._RefreshTimeInterval ) - - self:ManageTasks() - end - - --- Set the refresh time interval in seconds when a new task management action needs to be done. - -- @param #TASK_MANAGER self - -- @param #number RefreshTimeInterval The refresh time interval in seconds when a new task management action needs to be done. - -- @return #TASK_MANAGER self - function TASK_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval ) - self:F2() - - self._RefreshTimeInterval = RefreshTimeInterval - end - - - --- Manages the tasks for the @{Core.Set#SET_GROUP}. - -- @param #TASK_MANAGER self - -- @return #TASK_MANAGER self - function TASK_MANAGER:ManageTasks() - - end - -end - diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua deleted file mode 100644 index 1a27696f1..000000000 --- a/Moose Development/Moose/Utilities/STTS.lua +++ /dev/null @@ -1,259 +0,0 @@ ---- **Utilities** - DCS Simple Text-To-Speech (STTS). --- --- --- @module Utilities.STTS --- @image MOOSE.JPG - ---- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) --- @type STTS --- @field #string DIRECTORY Path of the SRS directory. - ---- Simple Text-To-Speech --- --- Version 0.4 - Compatible with SRS version 1.9.6.0+ --- --- # DCS Modification Required --- --- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization. --- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" --- Do this without DCS running to allow mission scripts to use os functions. --- --- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* --- --- # USAGE: --- --- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it --- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. --- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. --- --- Example calls: --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) --- --- Arguments in order are: --- --- * Message to say, make sure not to use a newline (\n) ! --- * Frequency in MHz --- * Modulation - AM/FM --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed --- * OPTIONAL - Speed -10 to +10 --- * OPTIONAL - Gender male, female or neuter --- * OPTIONAL - Culture - en-US, en-GB etc --- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line --- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly --- --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") --- --- Arguments in order are: --- --- * FULL path to the MP3 OR OGG to play --- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations --- * Modulation - AM/FM - to use multiple --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- --- ## Example --- --- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only --- --- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) --- --- @field #STTS -STTS = { - ClassName = "STTS", - DIRECTORY = "", - SRS_PORT = 5002, - GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE = "DCS-SR-ExternalAudio.exe" -} - ---- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER -STTS.DIRECTORY = "D:/DCS/_SRS" - ---- LOCAL SRS PORT - DEFAULT IS 5002 -STTS.SRS_PORT = 5002 - ---- Google credentials file -STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" - ---- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING -STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" - ---- Function for UUID. -function STTS.uuid() - local random = math.random - local template = 'yxxx-xxxxxxxxxxxx' - return string.gsub( template, '[xy]', function( c ) - local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb ) - return string.format( '%x', v ) - end ) -end - ---- Round a number. --- @param #number x Number. --- @param #number n Precision. -function STTS.round( x, n ) - n = math.pow( 10, n or 0 ) - x = x * n - if x >= 0 then - x = math.floor( x + 0.5 ) - else - x = math.ceil( x - 0.5 ) - end - return x / n -end - ---- Function returns estimated speech time in seconds. --- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so --- --- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- --- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: --- --- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min --- --- @param #number length can also be passed as #string --- @param #number speed Defaults to 1.0 --- @param #boolean isGoogle We're using Google TTS -function STTS.getSpeechTime(length,speed,isGoogle) - - local maxRateRatio = 3 - - speed = speed or 1.0 - isGoogle = isGoogle or false - - local speedFactor = 1.0 - if isGoogle then - speedFactor = speed - else - if speed ~= 0 then - speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 - end - if speed < 0 then - speedFactor = 1 / speedFactor - end - end - - local wpm = math.ceil( 100 * speedFactor ) - local cps = math.floor( (wpm * 5) / 60 ) - - if type( length ) == "string" then - length = string.len( length ) - end - - return length/cps --math.ceil(length/cps) -end - ---- Text to speech function. -function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS ) - if os == nil or io == nil then - env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " ) - return - end - - speed = speed or 1 - gender = gender or "female" - culture = culture or "" - voice = voice or "" - coalition = coalition or "0" - name = name or "ROBOT" - volume = 1 - speed = 1 - - message = message:gsub( "\"", "\\\"" ) - - local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name ) - - if voice ~= "" then - cmd = cmd .. string.format( " -V \"%s\"", voice ) - else - - if culture ~= "" then - cmd = cmd .. string.format( " -l %s", culture ) - end - - if gender ~= "" then - cmd = cmd .. string.format( " -g %s", gender ) - end - end - - if googleTTS == true then - cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS ) - end - - if speed ~= 1 then - cmd = cmd .. string.format( " -s %s", speed ) - end - - if volume ~= 1.0 then - cmd = cmd .. string.format( " -v %s", volume ) - end - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - cmd = cmd .. string.format( " -t \"%s\"", message ) - - if string.len( cmd ) > 255 then - local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" - local script = io.open( filename, "w+" ) - script:write( cmd .. " && exit" ) - script:close() - cmd = string.format( "\"%s\"", filename ) - timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 ) - end - - if string.len( cmd ) > 255 then - env.info( "[DCS-STTS] - cmd string too long" ) - env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" ) - end - os.execute( cmd ) - - return STTS.getSpeechTime( message, speed, googleTTS ) -end - ---- Play mp3 function. --- @param #string pathToMP3 Path to the sound file. --- @param #string freqs Frequencies, e.g. "305, 256". --- @param #string modulations Modulations, e.g. "AM, FM". --- @param #string volume Volume, e.g. "0.5". -function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point ) - - local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" ) - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" ) - os.execute( cmd ) - -end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index ab22084a2..5b8cd0b3a 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -2,7 +2,6 @@ Utilities/Enums.lua Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua -Utilities/STTS.lua Utilities/FiFo.lua Utilities/Socket.lua