diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 8f77f698d..7fcd96a10 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.9.1.{build} +version: 2.4.a.{build} shallow_clone: true skip_branch_with_pr: false skip_commits: @@ -17,6 +17,7 @@ environment: platform: - x64 + init: - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` @@ -48,7 +49,7 @@ install: build_script: - ps: | - if( $env:appveyor_repo_branch -eq 'master' ) + if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { $apiUrl = 'https://ci.appveyor.com/api' $token = 'qts80b5kpq0ooj4x6vvw' @@ -56,12 +57,12 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = 'master'; environmentVariables = @{} } | ConvertTo-Json - # get project with last build details + $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json + # Generate the new version ... $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } - ps: | - if( $env:appveyor_repo_branch -eq 'master' ) + if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { $apiUrl = 'https://ci.appveyor.com/api' $token = 'qts80b5kpq0ooj4x6vvw' @@ -69,13 +70,12 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = 'master'; environmentVariables = @{} } | ConvertTo-Json + $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json # get project with last build details $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } - test: off # test_script: # - cmd: luacheck "Moose Development\Moose\moose.lua" "Moose Mission Setup\moose.lua" diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua index 1309b52bc..4fbe0f37d 100644 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ b/Moose Development/Moose/AI/AI_A2A.lua @@ -1,14 +1,13 @@ --- **AI** -- (R2.2) - Models the process of air operations for airplanes. -- --- This is a class used in the @{AI_A2A_Dispatcher}. --- -- === -- -- ### Author: **FlightControl** -- -- === -- --- @module AI_A2A +-- @module AI.AI_A2A +-- @image AI_Air_To_Air_Dispatching.JPG --BASE:TraceClass("AI_A2A") @@ -16,9 +15,7 @@ --- @type AI_A2A -- @extends Core.Fsm#FSM_CONTROLLABLE ---- # AI_A2A class, extends @{Fsm#FSM_CONTROLLABLE} --- --- The AI_A2A class implements the core functions to operate an AI @{Group} A2A tasking. +--- The AI_A2A class implements the core functions to operate an AI @{Wrapper.Group} A2A tasking. -- -- -- ## AI_A2A constructor @@ -295,8 +292,8 @@ end --- Sets (modifies) the minimum and maximum speed of the patrol. -- @param #AI_A2A self --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @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_A2A self function AI_A2A:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) @@ -308,8 +305,8 @@ end --- Sets the floor and ceiling altitude of the patrol. -- @param #AI_A2A self --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @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 self function AI_A2A:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 9b90b489e..480bd39ec 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -1,29 +1,24 @@ --- **AI** -- (R2.2) - Models the process of Combat Air Patrol (CAP) for airplanes. -- --- This is a class used in the @{AI_A2A_Dispatcher}. --- -- === -- -- ### Author: **FlightControl** -- -- === -- --- @module AI_A2A_Cap - ---BASE:TraceClass("AI_A2A_CAP") +-- @module AI.AI_A2A_Cap +-- @image AI_Combat_Air_Patrol.JPG --- @type AI_A2A_CAP -- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL ---- # AI_A2A_CAP class, extends @{AI_CAP#AI_PATROL_ZONE} --- --- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group} +--- The AI_A2A_CAP class implements the core functions to patrol a @{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 @{Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event. +-- 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) -- @@ -66,15 +61,15 @@ -- -- ### 2.2 AI_A2A_CAP Events -- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. +-- * **@{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_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Unit}. --- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task. +-- * **@{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 tresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement @@ -85,7 +80,7 @@ -- 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#AI_A2A_CAP.SetEngageRange}() to define that range. +-- Use the method @{AI.AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range. -- -- ## 4. Set the Zone of Engagement -- @@ -93,7 +88,7 @@ -- -- An optional @{Zone} can be set, -- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone. +-- Use the method @{AI.AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone. -- -- === -- @@ -106,13 +101,13 @@ AI_A2A_CAP = { -- @param #AI_A2A_CAP self -- @param Wrapper.Group#GROUP AICap -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Group} in km/h when engaging a target. --- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Group} in km/h when engaging a target. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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 ) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 96fc731d1..7a017f854 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1,11 +1,24 @@ --- **AI** - (R2.2) - Manages the process of an automatic A2A defense system based on an EWR network targets and coordinating CAP and GCI. -- --- === -- --- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia1.JPG) +-- 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}. +-- -- --- === --- -- # QUICK START GUIDE -- -- There are basically two classes available to model an A2A defense system. @@ -155,7 +168,8 @@ -- ### 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_A2A_Dispatcher +-- @module AI.AI_A2A_Dispatcher +-- @image AI_Air_To_Air_Dispatching.JPG @@ -165,11 +179,7 @@ do -- AI_A2A_DISPATCHER -- @type AI_A2A_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER - --- # AI\_A2A\_DISPATCHER class, extends @{Tasking#DETECTION_MANAGER} - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia1.JPG) - -- - -- The @{#AI_A2A_DISPATCHER} class is designed to create an automatic air defence system for a coalition. + --- Create an automatic air defence system for a coalition. -- -- === -- @@ -229,7 +239,7 @@ do -- AI_A2A_DISPATCHER -- 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_BASE} object that is given as the input parameter of the AI\_A2A\_DISPATCHER class. + -- 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. -- @@ -346,7 +356,7 @@ do -- AI_A2A_DISPATCHER -- -- ![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 @{zone} object derived from @{Zone#ZONE_BASE}. + -- If it’s a cold war then the **borders of red and blue territory** need to be defined using a @{zone} object derived from @{Core.Zone#ZONE_BASE}. -- 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. @@ -544,7 +554,7 @@ do -- AI_A2A_DISPATCHER -- * 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 @{Zone#ZONE_BASE} class. Zones can be @{Zone#ZONE}, @{Zone#ZONE_POLYGON}, @{Zone#ZONE_UNIT}, @{Zone#ZONE_GROUP}, etc. + -- * 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. @@ -740,7 +750,7 @@ do -- AI_A2A_DISPATCHER -- -- 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.SetDefaultFuelTreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -830,7 +840,7 @@ do -- AI_A2A_DISPATCHER --- AI_A2A_DISPATCHER constructor. -- This is defining the A2A DISPATCHER for one coaliton. - -- The Dispatcher works with a @{Functional#Detection} object that is taking of the detection of targets using the EWR units. + -- 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 choosen, 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. @@ -1142,7 +1152,7 @@ do -- AI_A2A_DISPATCHER --- 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 @{zone} object derived from @{Zone#ZONE_BASE}. This method needs to be used for this. + -- If it’s a cold war then the **borders of red and blue territory** need to be defined using a @{zone} object derived from @{Core.Zone#ZONE_BASE}. This method needs to be used for this. -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 -- @param #AI_A2A_DISPATCHER self -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. @@ -1276,7 +1286,7 @@ do -- AI_A2A_DISPATCHER --- Calculates which AI friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self -- @param DetectedItem - -- @return #number, Core.CommandCenter#REPORT + -- @return #table A list of the friendlies nearby. function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) local FriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem ) @@ -1423,11 +1433,11 @@ do -- AI_A2A_DISPATCHER -- 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 @{Airbase#AIRBASE} class contains enumerations of the airbases of each map. + -- 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: @{Airbase#AIRBASE.Caucaus} - -- * Nevada or NTTR: @{Airbase#AIRBASE.Nevada} - -- * Normandy: @{Airbase#AIRBASE.Normandy} + -- * 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"`. @@ -1512,7 +1522,7 @@ do -- AI_A2A_DISPATCHER --- Set a CAP for a Squadron. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. -- @param #number FloorAltitude The minimum altitude at which the cap can be executed. -- @param #number CeilingAltitude the maximum altitude at which the cap can be executed. -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. @@ -2391,7 +2401,7 @@ do -- AI_A2A_DISPATCHER --- Set the default tanker where defenders will Refuel in the air. -- @param #AI_A2A_DISPATCHER self - -- @param #strig TankerName A string defining the group name of the Tanker as defined within the Mission Editor. + -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. -- @return #AI_A2A_DISPATCHER -- @usage -- @@ -2414,7 +2424,7 @@ do -- AI_A2A_DISPATCHER --- 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 #strig TankerName A string defining the group name of the Tanker as defined within the Mission Editor. + -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. -- @return #AI_A2A_DISPATCHER -- @usage -- @@ -2470,7 +2480,7 @@ do -- AI_A2A_DISPATCHER --- Creates an SWEEP task when there are targets for it. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function AI_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -2890,8 +2900,8 @@ do -- AI_A2A_DISPATCHER --- 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 - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. + -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -2917,8 +2927,8 @@ do -- AI_A2A_DISPATCHER --- Creates an GCI task when there are targets for it. -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. + -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function AI_A2A_DISPATCHER:EvaluateGCI( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -2944,7 +2954,7 @@ do -- AI_A2A_DISPATCHER --- 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 @{Detection#DETECTION_BASE} derived object. + -- @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 ) @@ -3069,10 +3079,10 @@ end do - --- Calculates which HUMAN friendlies are nearby the area + --- Calculates which HUMAN friendlies are nearby the area. -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Core.CommandCenter#REPORT + -- @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_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) local DetectedSet = DetectedItem.Set @@ -3115,14 +3125,14 @@ do return PlayersCount, PlayerTypesReport end - --- Calculates which friendlies are nearby the area + --- Calculates which friendlies are nearby the area. -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Core.CommandCenter#REPORT - function AI_A2A_DISPATCHER:GetFriendliesNearBy( Target ) + -- @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_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - local DetectedSet = Target.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( Target ) + local DetectedSet = DetectedItem.Set + local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) local FriendlyTypes = {} local FriendliesCount = 0 @@ -3159,8 +3169,8 @@ do return FriendliesCount, FriendlyTypesReport end - --- - -- @param AI_A2A_DISPATCHER + --- 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 ) @@ -3173,12 +3183,8 @@ do --- @type AI_A2A_GCICAP -- @extends #AI_A2A_DISPATCHER - --- # AI\_A2A\_GCICAP class, extends @{AI_A2A_Dispatcher#AI_A2A_DISPATCHER} - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia1.JPG) - -- - -- The AI_A2A_GCICAP class is designed to create an automatic air defence system for a coalition setting up GCI and CAP air defenses. - -- The class derives from @{AI#AI_A2A_DISPATCHER} and thus, all the methods that are defined in the @{AI#AI_A2A_DISPATCHER} class, can be used also in AI\_A2A\_GCICAP. + --- 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. -- -- === -- @@ -3281,7 +3287,7 @@ do -- -- **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#AI_A2A_DISPATCHER}: + -- ## 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. -- diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index e714fcfe6..cc24341c3 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module AI_A2A_GCI +-- @module AI.AI_A2A_GCI +-- @image AI_Ground_Control_Intercept.JPG @@ -16,13 +17,11 @@ -- @extends AI.AI_A2A#AI_A2A ---- # AI_A2A_GCI class, extends @{AI_A2A#AI_A2A} --- --- The AI_A2A_GCI class implements the core functions to intercept intruders. The Engage function will intercept intruders. +--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- -- ![Process](..\Presentations\AI_GCI\Dia3.JPG) -- --- The AI_A2A_GCI is assigned a @{Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event. +-- 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. -- -- ![Process](..\Presentations\AI_GCI\Dia4.JPG) -- @@ -65,15 +64,15 @@ -- -- ### 2.2 AI_A2A_GCI Events -- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. +-- * **@{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_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Unit}. --- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task. +-- * **@{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 tresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement @@ -84,7 +83,7 @@ -- 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_GCI#AI_A2A_GCI.SetEngageRange}() to define that range. +-- Use the method @{AI.AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range. -- -- ## 4. Set the Zone of Engagement -- @@ -92,7 +91,7 @@ -- -- An optional @{Zone} can be set, -- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone. +-- Use the method @{AI.AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone. -- -- === -- diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 927c5fd32..3ff3cfa69 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -1,26 +1,23 @@ --- **AI** -- (R2.2) - Models the process of air patrol of airplanes. -- --- This is a class used in the @{AI_A2A_Dispatcher}. --- -- === -- -- ### Author: **FlightControl** -- -- === -- --- @module AI_A2A_Patrol +-- @module AI.AI_A2A_Patrol +-- @image AI_Air_Patrolling.JPG --- @type AI_A2A_PATROL -- @extends AI.AI_A2A#AI_A2A ---- # AI_A2A_PATROL class, extends @{Fsm#FSM_CONTROLLABLE} --- --- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group}. +--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}. -- -- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) -- --- The AI_A2A_PATROL is assigned a @{Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event. +-- 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) -- @@ -93,7 +90,7 @@ -- * @{#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 @{Unit}s detected by the AI. +-- 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. @@ -126,11 +123,11 @@ AI_A2A_PATROL = { -- @param #AI_A2A_PATROL self -- @param Wrapper.Group#GROUP AIPatrol -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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_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. @@ -236,8 +233,8 @@ end --- Sets (modifies) the minimum and maximum speed of the patrol. -- @param #AI_A2A_PATROL self --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. +-- @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 } ) @@ -250,8 +247,8 @@ end --- Sets the floor and ceiling altitude of the patrol. -- @param #AI_A2A_PATROL self --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @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 } ) @@ -358,7 +355,7 @@ function AI_A2A_PATROL.Resume( AIPatrol ) AIPatrol:F( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } ) if AIPatrol:IsAlive() then - local _AI_A2A = AIPatrol:GetState( AIPatrol, "AI_A2A" ) -- #AI_A2A + local _AI_A2A = AIPatrol:GetState( AIPatrol, "AI_A2A" ) -- AI.AI_A2A#AI_A2A _AI_A2A:__Reset( 1 ) _AI_A2A:__Route( 5 ) end diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index ccb0603bc..42bccbf06 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -1,9 +1,14 @@ ---- **AI** -- (R2.1) - Manages the independent process of Battlefield Air Interdiction (bombing) for airplanes. +--- **AI** -- Peform Battlefield Area Interdiction (BAI) within an engagement zone. -- --- === --- --- ![Banner Image](..\Presentations\AI_BAI\Dia1.JPG) +-- **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/BAI%20-%20Battlefield%20Air%20Interdiction) @@ -21,25 +26,23 @@ -- -- === -- --- @module AI_Bai +-- @module AI.AI_Bai +-- @image AI_Battlefield_Air_Interdiction.JPG --- AI_BAI_ZONE class -- @type AI_BAI_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. -- @extends AI.AI_Patrol#AI_PATROL_ZONE ---- # AI_BAI_ZONE class, extends @{AI_Patrol#AI_PATROL_ZONE} +--- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. -- --- AI_BAI_ZONE derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour. --- --- The AI_BAI_ZONE class implements the core functions to provide BattleGround Air Interdiction in an Engage @{Zone} by an AIR @{Controllable} or @{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 @{Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event. +-- 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) -- @@ -105,15 +108,15 @@ -- -- ### 2.2. AI_BAI_ZONE Events -- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. +-- * **@{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_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Unit}. --- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the BOMB task. +-- * **@{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 tresholds have been reached, the AI will RTB. -- -- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** @@ -140,12 +143,12 @@ AI_BAI_ZONE = { --- Creates a new AI_BAI_ZONE object -- @param #AI_BAI_ZONE self -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} 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#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.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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 ) @@ -182,24 +185,24 @@ function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. + -- @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 @{DCSTypes#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. + -- 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.DCSTypes#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. + -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. + -- @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 @{DCSTypes#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. + -- 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.DCSTypes#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. + -- @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 @@ -486,10 +489,10 @@ end -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param Dcs.DCSTypes#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 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.DCSTypes#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. +-- @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, diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index 18bb5fa65..1b18bf79d 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -1,8 +1,11 @@ ---- **AI** -- (2.1) - Balance player slots with AI to create an engaging simulation environment, independent of the amount of players. +--- **AI** -- Balance player slots with AI to create an engaging simulation environment, independent of the amount of players. -- --- === +-- **Features:** -- --- ![Banner Image](..\Presentations\AI_Balancer\Dia1.JPG) +-- * 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. -- -- === -- @@ -21,7 +24,8 @@ -- -- === -- --- @module AI_Balancer +-- @module AI.AI_Balancer +-- @image AI_Balancing.JPG --- @type AI_BALANCER -- @field Core.Set#SET_CLIENT SetClient @@ -30,13 +34,11 @@ -- @extends Core.Fsm#FSM_SET ---- # AI_BALANCER class, extends @{Fsm#FSM_SET} --- --- The AI_BALANCER class monitors and manages as many replacement AI groups as there are --- CLIENTS in a SET_CLIENT collection, which are not occupied by human players. +--- 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 @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM). +-- 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 @{FSM} module documentation. -- @@ -78,8 +80,8 @@ -- 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** @{Airbase#AIRBASE}. --- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}. +-- * @{#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. @@ -141,10 +143,10 @@ function AI_BALANCER:InitSpawnInterval( Earliest, Latest ) return self end ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. +--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. -- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. +-- @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 @@ -152,9 +154,9 @@ function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbas self.ReturnAirbaseSet = ReturnAirbaseSet end ---- Returns the AI to the home @{Airbase#AIRBASE}. +--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. -- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. +-- @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 diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 8dc70fd40..1ffdbc23b 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -1,9 +1,13 @@ ---- **AI** -- (R2.1) - Manages the independent process of Combat Air Patrol (CAP) for airplanes. +--- **AI** -- Perform Combat Air Patrolling (CAP) for airplanes. -- --- === --- --- ![Banner Image](..\Presentations\AI_CAP\Dia1.JPG) +-- **Features:** -- +-- * Patrol AI airplanes within a given zone. +-- * Trigger detected events when enemy airplanes are detected. +-- * Manage a fuel treshold to RTB on time. +-- * Engage the enemy when detected. +-- +-- -- === -- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol) @@ -25,23 +29,22 @@ -- -- === -- --- @module AI_Cap +-- @module AI.AI_Cap +-- @image AI_Combat_Air_Patrol.JPG --- @type AI_CAP_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. -- @extends AI.AI_Patrol#AI_PATROL_ZONE ---- # AI_CAP_ZONE class, extends @{AI_CAP#AI_PATROL_ZONE} --- --- The AI_CAP_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group} +--- Implements the core functions to patrol a @{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 @{Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. +-- 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) -- @@ -84,15 +87,15 @@ -- -- ### 2.2 AI_CAP_ZONE Events -- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. +-- * **@{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_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Unit}. --- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task. +-- * **@{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 tresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement @@ -103,7 +106,7 @@ -- 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#AI_CAP_ZONE.SetEngageRange}() to define that range. +-- Use the method @{AI.AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range. -- -- ## 4. Set the Zone of Engagement -- @@ -111,7 +114,7 @@ -- -- An optional @{Zone} can be set, -- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone. +-- Use the method @{AI.AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone. -- -- === -- @@ -125,11 +128,11 @@ AI_CAP_ZONE = { --- Creates a new AI_CAP_ZONE object -- @param #AI_CAP_ZONE self -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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 ) diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index 2450d02ff..33e07849d 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -1,9 +1,14 @@ ---- **AI** -- (R2.1) - Manages the independent process of Close Air Support for airplanes. +--- **AI** -- Perform Close Air Support (CAS) near friendlies. -- --- === --- --- ![Banner Image](..\Presentations\AI_CAS\Dia1.JPG) +-- **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-release/CAS%20-%20Close%20Air%20Support) @@ -23,25 +28,21 @@ -- -- === -- --- @module AI_Cas - +-- @module AI.AI_Cas +-- @image AI_Close_Air_Support.JPG --- AI_CAS_ZONE class -- @type AI_CAS_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. -- @extends AI.AI_Patrol#AI_PATROL_ZONE ---- # AI_CAS_ZONE class, extends @{AI_Patrol#AI_PATROL_ZONE} --- --- AI_CAS_ZONE derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour. --- --- The AI_CAS_ZONE class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}. +--- Implements the core functions to provide Close Air Support in an Engage @{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 @{Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event. +-- 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) -- @@ -107,15 +108,15 @@ -- -- ### 2.2. AI_CAS_ZONE Events -- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. +-- * **@{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_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Unit}. --- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the CAS task. +-- * **@{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 tresholds have been reached, the AI will RTB. -- -- === @@ -130,12 +131,12 @@ AI_CAS_ZONE = { --- Creates a new AI_CAS_ZONE object -- @param #AI_CAS_ZONE self -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} 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#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.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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 ) @@ -171,24 +172,24 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. + -- @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 @{DCSTypes#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. + -- 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.DCSTypes#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. + -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. + -- @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 @{DCSTypes#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. + -- 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.DCSTypes#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. + -- @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 @@ -430,10 +431,10 @@ end -- @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.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param Dcs.DCSTypes#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 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.DCSTypes#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. +-- @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, diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua new file mode 100644 index 000000000..a2d086683 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -0,0 +1,673 @@ +--- **AI** -- (R2.3) - Models the intelligent transportation of infantry and other cargo. +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Cargo_APC +-- @image AI_Cargo_Dispatching_For_APC.JPG + +--- @type AI_CARGO_APC +-- @extends Core.Fsm#FSM_CONTROLLABLE + + +--- Brings a dynamic cargo handling capability for AI groups. +-- +-- 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 module uses the @{Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\APC object recognize the cargo. +-- Please consult the @{Cargo} module for more information. +-- +-- ## Cargo loading. +-- +-- The module will load automatically cargo when the APCs are within boarding or loading range. +-- The boarding or loading range is specified when the cargo is created in the simulation, and therefore, this range depends on the type of cargo +-- and the specified boarding range. +-- +-- ## Enemies nearby. +-- +-- 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 range needs to be specified in meters at the @{#AI_CARGO_APC.New}() method. +-- This combat range will trigger the unboarding of troops when enemies are within the combat range around the APCs. +-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit range (effectiveness) versus +-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered. +-- A combat range of 350 meters to 500 meters has been proven to be the most effective and efficient. +-- +-- ## 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 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. +-- +-- +-- +-- @field #AI_CARGO_APC +AI_CARGO_APC = { + ClassName = "AI_CARGO_APC", + Coordinate = nil, -- Core.Point#COORDINATE, + APC_Cargo = {}, +} + +--- Creates a new AI_CARGO_APC object. +-- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param Core.Set#SET_CARGO CargoSet +-- @param #number CombatRadius +-- @return #AI_CARGO_APC +function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_APC + + self.CargoSet = CargoSet -- Core.Set#SET_CARGO + self.CombatRadius = CombatRadius + + self:SetStartState( "Unloaded" ) + + self:AddTransition( "Unloaded", "Pickup", "*" ) + self:AddTransition( "Loaded", "Deploy", "*" ) + + self:AddTransition( "*", "Load", "Boarding" ) + self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Loaded", "Unload", "Unboarding" ) + self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) + self:AddTransition( { "Unboarding", "Unloaded" }, "Unloaded", "Unloaded" ) + + self:AddTransition( "*", "Monitor", "*" ) + self:AddTransition( "*", "Follow", "Following" ) + self:AddTransition( "*", "Guard", "Unloaded" ) + self:AddTransition( "*", "Home", "*" ) + + self:AddTransition( "*", "Destroyed", "Destroyed" ) + + + --- Pickup Handler OnBefore for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnBeforePickup + -- @param #AI_CARGO_APC self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @return #boolean + + --- Pickup Handler OnAfter for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnAfterPickup + -- @param #AI_CARGO_APC self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + + --- Pickup Trigger for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] Pickup + -- @param #AI_CARGO_APC self + -- @param Core.Point#COORDINATE Coordinate + + --- Pickup Asynchronous Trigger for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] __Pickup + -- @param #AI_CARGO_APC self + -- @param #number Delay + -- @param Core.Point#COORDINATE Coordinate + + --- Deploy Handler OnBefore for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnBeforeDeploy + -- @param #AI_CARGO_APC self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @return #boolean + + --- Deploy Handler OnAfter for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnAfterDeploy + -- @param #AI_CARGO_APC self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + + --- Deploy Trigger for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] Deploy + -- @param #AI_CARGO_APC self + -- @param Core.Point#COORDINATE Coordinate + + --- Deploy Asynchronous Trigger for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] __Deploy + -- @param #AI_CARGO_APC self + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Delay + + + --- Loaded Handler OnAfter for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnAfterLoaded + -- @param #AI_CARGO_APC self + -- @param Wrapper.Group#GROUP APC + -- @param #string From + -- @param #string Event + -- @param #string To + + --- Unloaded Handler OnAfter for AI_CARGO_APC + -- @function [parent=#AI_CARGO_APC] OnAfterUnloaded + -- @param #AI_CARGO_APC self + -- @param Wrapper.Group#GROUP APC + -- @param #string From + -- @param #string Event + -- @param #string To + + + self:__Monitor( 1 ) + + + self:SetCarrier( APC ) + self.Transporting = false + self.Relocating = false + + 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 ) + CargoCarrier:HandleEvent( EVENTS.Hit ) + + 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 range. Unload the CargoCarrier. + AICargoTroops:Destroyed() + end + end + end + + 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 range. 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 + + +function AI_CARGO_APC:IsTransporting() + + return self.Transporting == true +end + +function AI_CARGO_APC:IsRelocating() + + return self.Relocating == true +end + +--- Find a free Carrier within a range. +-- @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 + + + +--- 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 + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) + self:F( { APC, From, Event, To } ) + + if APC and APC:IsAlive() then + if self.CarrierCoordinate then + if self:IsRelocating() == true then + local Coordinate = APC:GetCoordinate() + self.Zone:Scan( { Object.Category.UNIT } ) + if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then + if self:Is( "Unloaded" ) or self:Is( "Following" ) then + -- There are no enemies within combat range. Load the CargoCarrier. + self:Load() + end + else + if self:Is( "Loaded" ) then + -- There are enemies within combat range. Unload the CargoCarrier. + self:__Unload( 1, false ) + else + if self:Is( "Unloaded" ) then + self:Follow() + end + if self:Is( "Following" ) then + for APCUnit, Cargo in pairs( self.APC_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + 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 + self.CarrierCoordinate = APC:GetCoordinate() + end + + self:__Monitor( -5 ) + +end + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To ) + self:F( { APC, From, Event, To } ) + + local Boarding = false + self.BoardingCount = 0 + + if APC and APC:IsAlive() then + self.APC_Cargo = {} + for _, APCUnit in pairs( APC:GetUnits() ) do + local APCUnit = APCUnit -- Wrapper.Unit#UNIT + for _, Cargo in pairs( self.CargoSet:GetSet() ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), APC:GetName() } ) + if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then + if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then + self:F( { "In radius", APCUnit:GetName() } ) + APC:RouteStop() + --Cargo:Ungroup() + Cargo:Board( APCUnit, 25 ) + self:__Board( 1, Cargo ) + Boarding = true + + -- So now this APCUnit has Cargo that is being loaded. + -- This will be used further in the logic to follow and to check cargo status. + self.APC_Cargo[APCUnit] = Cargo + break + end + end + end + end + end + + return Boarding + +end + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo ) + self:F( { APC, From, Event, To, Cargo } ) + + if APC and APC:IsAlive() then + self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), APC:GetName() } ) + if not Cargo:IsLoaded() then + self:__Board( 10, Cargo ) + else + self:__Loaded( 1 ) + end + end + +end + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To ) + self:F( { APC, From, Event, To } ) + + local Loaded = true + + if APC and APC:IsAlive() then + for APCUnit, Cargo in pairs( self.APC_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), APC:GetName() } ) + if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then + Loaded = false + end + end + + end + + if Loaded == true then + APC:RouteResume() + end + + return Loaded + +end + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onafterUnload( APC, From, Event, To, Deployed ) + self:F( { APC, From, Event, To, Deployed } ) + + if APC and APC:IsAlive() then + for _, APCUnit in pairs( APC:GetUnits() ) do + local APCUnit = APCUnit -- Wrapper.Unit#UNIT + APC:RouteStop() + for _, Cargo in pairs( APCUnit:GetCargo() ) do + Cargo:UnBoard() + self:__Unboard( 10, Cargo, Deployed ) + end + end + end + +end + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo, Deployed ) + self:F( { APC, From, Event, To, Cargo:GetName() } ) + + if APC and APC:IsAlive() then + if not Cargo:IsUnLoaded() then + self:__Unboard( 10, Cargo, Deployed ) + else + self:__Unloaded( 1, Cargo, Deployed ) + end + end + +end + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed ) + self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } ) + + local AllUnloaded = true + + --Cargo:Regroup() + + if APC and APC:IsAlive() then + for _, APCUnit in pairs( APC:GetUnits() ) do + local APCUnit = APCUnit -- Wrapper.Unit#UNIT + local CargoCheck = self.APC_Cargo[APCUnit] + if CargoCheck then + self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } ) + if CargoCheck:IsUnLoaded() == false then + AllUnloaded = false + break + end + end + end + + if AllUnloaded == true then + if Deployed == true then + for APCUnit, Cargo in pairs( self.APC_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + Cargo:SetDeployed( true ) + end + self.APC_Cargo = {} + end + self:Guard() + self.CargoCarrier = APC + end + end + + self:F( { AllUnloaded = AllUnloaded } ) + return AllUnloaded + +end + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +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 APCUnit, Cargo in pairs( self.APC_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + if Cargo:IsUnLoaded() then + self:FollowToCarrier( self, APCUnit, Cargo ) + APCUnit:RouteResume() + end + end + end + +end + + +--- @param #AI_CARGO_APC +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC._Pickup( APC, self ) + + APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) + + if APC:IsAlive() then + self:Load() + self.Relocating = true + end +end + + +--- @param #AI_CARGO_APC +-- @param Wrapper.Group#GROUP APC +function AI_CARGO_APC._Deploy( APC, self ) + + APC:F( { "AI_CARGO_APC._Deploy:", APC } ) + + if APC:IsAlive() then + self:Unload( true ) + self.Transporting = false + self.Relocating = false + end +end + + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param From +-- @param Event +-- @param To +-- @param Core.Point#COORDINATE Coordinate +function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate ) + + if APC and APC:IsAlive() then + + if Coordinate then + self.RoutePickup = true + + local Waypoints = APC:TaskGroundOnRoad( Coordinate, APC:GetSpeedMax()*0.5, "Line abreast" ) + + local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self ) + + self:F({Waypoints = Waypoints}) + 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 ) + end + + self.Transporting = true + end + +end + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param From +-- @param Event +-- @param To +-- @param Core.Point#COORDINATE Coordinate +function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate ) + + if APC and APC:IsAlive() then + + self.RouteDeploy = true + + local Waypoints = APC:TaskGroundOnRoad( Coordinate, APC:GetSpeedMax()*0.5, "Line abreast" ) + + local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self ) + + self:F({Waypoints = Waypoints}) + 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. + end + +end + + +--- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param From +-- @param Event +-- @param To +-- @param Core.Point#COORDINATE Coordinate +function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate ) + + if APC and APC:IsAlive() ~= nil then + + self.RouteHome = true + + local Waypoints = APC:TaskGroundOnRoad( Coordinate, APC:GetSpeedMax()*0.5, "Line abreast" ) + + 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 new file mode 100644 index 000000000..343823d2f --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -0,0 +1,447 @@ +--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo). +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Cargo_Airplane +-- @image AI_Cargo_Dispatching_For_Airplanes.JPG + +--- @type AI_CARGO_AIRPLANE +-- @extends Core.Fsm#FSM_CONTROLLABLE + + +--- Implements the transportation of cargo by airplanes. +-- +-- @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 +-- @param Core.Set#SET_CARGO CargoSet +-- @param #number CombatRadius +-- @return #AI_CARGO_AIRPLANE +function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_AIRPLANE + + self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP + + self:SetStartState( "Unloaded" ) + + self:AddTransition( "Unloaded", "Pickup", "*" ) + self:AddTransition( "Loaded", "Deploy", "*" ) + + self:AddTransition( "Unloaded", "Load", "Boarding" ) + self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Loaded", "Unload", "Unboarding" ) + self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) + self:AddTransition( "Unboarding", "Unloaded", "Unloaded" ) + + self:AddTransition( "*", "Landed", "*" ) + + self:AddTransition( "*", "Destroyed", "Destroyed" ) + + --- Pickup Handler OnBefore for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup + -- @param #AI_CARGO_AIRPLANE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Airbase#AIRBASE Airbase + -- @return #boolean + + --- Pickup Handler OnAfter for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup + -- @param #AI_CARGO_AIRPLANE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Airbase#AIRBASE Airbase + + --- Pickup Trigger for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] Pickup + -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Airbase#AIRBASE Airbase + + --- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] __Pickup + -- @param #AI_CARGO_AIRPLANE self + -- @param #number Delay + -- @param Wrapper.Airbase#AIRBASE Airbase + + --- Deploy Handler OnBefore for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy + -- @param #AI_CARGO_AIRPLANE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Airbase#AIRBASE Airbase + -- @return #boolean + + --- Deploy Handler OnAfter for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy + -- @param #AI_CARGO_AIRPLANE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Airbase#AIRBASE Airbase + + --- Deploy Trigger for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] Deploy + -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Airbase#AIRBASE Airbase + + --- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE + -- @function [parent=#AI_CARGO_AIRPLANE] __Deploy + -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param #number Delay + + + self:SetCarrier( Airplane ) + + return self +end + + +--- Set the Carrier. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +-- @return #AI_CARGO_AIRPLANE +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:Landed() + 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 + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +-- @param From +-- @param Event +-- @param To + -- @param Wrapper.Airbase#AIRBASE Airbase +-- @param #number Speed +function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + + if self.RoutePickup == true then + self:Load( Airplane:GetPointVec2() ) + self.RoutePickup = false + end + + if self.RouteDeploy == true then + self:Unload() + self.RouteDeploy = false + end + + end + +end + + + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +-- @param From +-- @param Event +-- @param To +-- @param Wrapper.Airbase#AIRBASE Airbase +-- @param #number Speed +function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Speed ) + + if Airplane and Airplane:IsAlive() then + self:Route( Airplane, Airbase, Speed ) + self.RoutePickup = true + self.Airbase = Airbase + end + +end + + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +-- @param From +-- @param Event +-- @param To +-- @param Wrapper.Airbase#AIRBASE Airbase +-- @param #number Speed +function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Airbase, Speed ) + + if Airplane and Airplane:IsAlive() then + self:Route( Airplane, Airbase, Speed ) + self.RouteDeploy = true + self.Airbase = Airbase + end + +end + + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterLoad( Airplane, From, Event, To, Coordinate ) + + if Airplane and Airplane:IsAlive() then + + for _, Cargo in pairs( self.CargoSet:GetSet() ) do + if Cargo:IsInLoadRadius( Coordinate ) then + self:__Board( 5 ) + Cargo:Board( Airplane, 25 ) + self.Cargo = Cargo + break + end + end + end + +end + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + self:F({ IsLoaded = self.Cargo:IsLoaded() } ) + if not self.Cargo:IsLoaded() then + self:__Board( 10 ) + else + self:__Loaded( 1 ) + end + end + +end + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterLoaded( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + end + +end + + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + self.Cargo:UnBoard() + self:__Unboard( 10 ) + end + +end + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterUnboard( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + if not self.Cargo:IsUnLoaded() then + self:__Unboard( 10 ) + else + self:__Unloaded( 1 ) + end + end + +end + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +function AI_CARGO_AIRPLANE:onafterUnloaded( Airplane, From, Event, To ) + + if Airplane and Airplane:IsAlive() then + self.Airplane = Airplane + end + +end + + +--- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane +-- @param Wrapper.Airbase#AIRBASE Airbase +-- @param #number Speed +function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed ) + + if Airplane and Airplane:IsAlive() then + + local PointVec3 = Airplane:GetPointVec3() + + local Takeoff = SPAWN.Takeoff.Hot + + local Template = Airplane:GetTemplate() + + if Template then + + local Points = {} + + if self.Airbase then + + local FromWaypoint = Template.route.points[1] + + -- These are only for ships. + FromWaypoint.linkUnit = nil + FromWaypoint.helipadId = nil + FromWaypoint.airdromeId = nil + + local AirbaseID = self.Airbase:GetID() + local AirbaseCategory = self.Airbase:GetDesc().category + + FromWaypoint.airdromeId = AirbaseID + + FromWaypoint.alt = 0 + + FromWaypoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + FromWaypoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + + + -- Translate the position of the Group Template to the Vec3. + for UnitID = 1, #Template.units do + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. Template.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. Template.units[UnitID].y ) + + -- These cause a lot of confusion. + local UnitTemplate = Template.units[UnitID] + + UnitTemplate.parking = 15 + UnitTemplate.parking_id = "1" + UnitTemplate.alt = 0 + + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = FromWaypoint.x + local BY = FromWaypoint.y + local TX = PointVec3.x + ( SX - BX ) + local TY = PointVec3.z + ( SY - BY ) + + UnitTemplate.x = TX + UnitTemplate.y = TY + + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + end + + FromWaypoint.x = PointVec3.x + FromWaypoint.y = PointVec3.z + + Points[#Points+1] = FromWaypoint + else + + local GroupPoint = Airplane:GetVec2() + local GroupVelocity = Airplane:GetUnit(1):GetDesc().speedMax + + local FromWaypoint = {} + FromWaypoint.x = GroupPoint.x + FromWaypoint.y = GroupPoint.y + FromWaypoint.type = "Turning Point" + FromWaypoint.action = "Turning Point" + FromWaypoint.speed = GroupVelocity + + Points[#Points+1] = FromWaypoint + end + + local AirbasePointVec2 = Airbase:GetPointVec2() + local ToWaypoint = AirbasePointVec2:WaypointAir( + POINT_VEC3.RoutePointAltType.BARO, + "Land", + "Landing", + Speed or Airplane:GetUnit(1):GetDesc().speedMax + ) + + ToWaypoint["airdromeId"] = Airbase:GetID() + ToWaypoint["speed_locked"] = true, + + self:F( ToWaypoint ) + + Points[#Points+1] = ToWaypoint + + Template.x = PointVec3.x + Template.y = PointVec3.z + + self:T3( Points ) + Template.route.points = Points + + --self:Respawn( Template ) + + local GroupSpawned = Airplane:Respawn( Template ) + + return GroupSpawned + end + + end + +end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua new file mode 100644 index 000000000..6bce0816e --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -0,0 +1,477 @@ +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Cargo_Dispatcher +-- @image AI_Cargo_Dispatching_For_Helicopters.JPG + +--- @type AI_CARGO_DISPATCHER +-- @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} capabilities within the MOOSE framework, to enable Carrier GROUP objects +-- to transport @{Cargo} towards several deploy zones. +-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER object recognize the cargo. +-- Please consult the @{Cargo} module for more information. +-- +-- ## 1. AI\_CARGO\_DISPATCHER constructor +-- +-- * @{#AI_CARGO_DISPATCHER.New}(): Creates a new AI\_CARGO\_DISPATCHER object. +-- +-- ## 2. AI\_CARGO\_DISPATCHER is a FSM +-- +-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) +-- +-- ### 2.1. AI\_CARGO\_DISPATCHER States +-- +-- * **Monitoring**: The process is dispatching. +-- * **Idle**: The process is idle. +-- +-- ### 2.2. AI\_CARGO\_DISPATCHER Events +-- +-- * **Monitor**: Monitor and take action. +-- * **Start**: Start the transport process. +-- * **Stop**: Stop the transport process. +-- * **Pickup**: Pickup cargo. +-- * **Load**: Load the cargo. +-- * **Loaded**: Flag that the cargo is loaded. +-- * **Deploy**: Deploy cargo to a location. +-- * **Unload**: Unload the cargo. +-- * **Unloaded**: Flag that the cargo is unloaded. +-- * **Home**: A Carrier is going home. +-- +-- ## 3. 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. +-- +-- ## 4. 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. +-- +-- ## 5. 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", + SetCarrier = nil, + SetDeployZones = nil, + AI_Cargo = {}, + PickupCargo = {} +} + +--- @field #AI_CARGO_DISPATCHER.AI_Cargo +AI_CARGO_DISPATCHER.AI_Cargo = {} + +--- @field #AI_CARGO_DISPATCHER.PickupCargo +AI_CARGO_DISPATCHER.PickupCargo = {} + + +--- Creates a new AI_CARGO_DISPATCHER object. +-- @param #AI_CARGO_DISPATCHER self +-- @param Core.Set#SET_GROUP SetCarrier +-- @param Core.Set#SET_CARGO SetCargo +-- @param Core.Set#SET_ZONE SetDeployZone +-- @return #AI_CARGO_DISPATCHER +-- @usage +-- +-- -- Create a new cargo dispatcher +-- SetCarrier = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones ) + + local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER + + self.SetCarrier = SetCarrier -- Core.Set#SET_GROUP + self.SetCargo = SetCargo -- Core.Set#SET_CARGO + self.SetDeployZones = SetDeployZones -- Core.Set#SET_ZONE + + self:SetStartState( "Idle" ) + + self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) + + self:AddTransition( "Idle", "Start", "Monitoring" ) + self:AddTransition( "Monitoring", "Stop", "Idle" ) + + + self:AddTransition( "*", "Pickup", "*" ) + self:AddTransition( "*", "Loading", "*" ) + self:AddTransition( "*", "Loaded", "*" ) + + self:AddTransition( "*", "Deploy", "*" ) + self:AddTransition( "*", "Unloading", "*" ) + self:AddTransition( "*", "Unloaded", "*" ) + + self:AddTransition( "*", "Home", "*" ) + + self.MonitorTimeInterval = 30 + self.DeployRadiusInner = 200 + self.DeployRadiusOuter = 500 + + 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 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 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 +-- @return #AI_CARGO_DISPATCHER +-- @usage +-- +-- -- Create a new cargo dispatcher +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +-- -- Set the home coordinate +-- local HomeZone = ZONE:New( "Home" ) +-- AICargoDispatcher: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 +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! +-- AICargoDispatcher: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 +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +-- -- Set the minimum pickup speed to be 100 km/h and the maximum speed to be 200 km/h. +-- AICargoDispatcher: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 +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! +-- AICargoDispatcher: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 +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +-- -- Set the minimum deploy speed to be 100 km/h and the maximum speed to be 200 km/h. +-- AICargoDispatcher: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 + + + +--- The Start trigger event, which actually takes action at the specified time interval. +-- @param #AI_CARGO_DISPATCHER self +function AI_CARGO_DISPATCHER:onafterMonitor() + + for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do + local Carrier = Carrier -- Wrapper.Group#GROUP + 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] + + function AI_Cargo.OnAfterPickup( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Pickup( Carrier, Cargo ) + end + + function AI_Cargo.OnAfterLoad( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Loading( Carrier ) + end + + function AI_Cargo.OnAfterLoaded( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Loaded( Carrier, Cargo ) + end + + function AI_Cargo.OnAfterDeploy( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Deploy( Carrier, Cargo ) + end + + function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Unloading( Carrier, Cargo ) + end + + function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo ) + self:Unloaded( Carrier, Cargo ) + end + end + + -- The Pickup sequence ... + -- Check if this Carrier need to go and Pickup something... + self:I( { IsTransporting = AI_Cargo:IsTransporting() } ) + if AI_Cargo:IsTransporting() == false then + -- ok, so there is a free Carrier + -- now find the first cargo that is Unloaded + + local PickupCargo = nil + + for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) 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() and not Cargo:IsDeployed() then + local CargoCoordinate = Cargo:GetCoordinate() + local CoordinateFree = true + for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do + if CarrierPickup:IsAlive() == true then + if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then + CoordinateFree = false + break + end + else + self.PickupCargo[CarrierPickup] = nil + end + end + if CoordinateFree == true then + self.PickupCargo[Carrier] = CargoCoordinate + PickupCargo = Cargo + break + 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 ) ) + break + else + if self.HomeZone then + if not self.CarrierHome[Carrier] then + self.CarrierHome[Carrier] = true + AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2() ) + end + end + end + end + end + + self:__Monitor( self.MonitorTimeInterval ) +end + +--- Start Handler OnBefore for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStart +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #boolean + +--- Start Handler OnAfter for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStart +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To + +--- 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 Handler OnBefore for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStop +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #boolean + +--- Stop Handler OnAfter for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStop +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To + +--- 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 + + + +--- Loaded Handler OnAfter for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Group#GROUP Carrier +-- @param Cargo.Cargo#CARGO Cargo + +--- Unloaded Handler OnAfter for AI_CARGO_DISPATCHER +-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded +-- @param #AI_CARGO_DISPATCHER self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Group#GROUP Carrier +-- @param Cargo.Cargo#CARGO Cargo + + + + + + +--- 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:OnAfterLoaded( From, Event, To, Carrier, Cargo ) + + local DeployZone = self.SetDeployZones:GetRandomZone() + + local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) + self.AI_Cargo[Carrier]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) ) + + 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 new file mode 100644 index 000000000..561125786 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -0,0 +1,122 @@ +--- **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. +-- +-- === +-- +-- ### 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 uses the @{Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_APC object recognize the cargo. +-- Please consult the @{Cargo} module for more information. +-- +-- ## 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 FSM +-- +-- ![Process](..\Presentations\AI_CARGO_DISPATCHER_APC\Dia3.JPG) +-- +-- ### 2.1. AI\_CARGO\_DISPATCHER\_APC States +-- +-- * **Monitoring**: The process is dispatching. +-- * **Idle**: The process is idle. +-- +-- ### 2.2. AI\_CARGO\_DISPATCHER\_APC Events +-- +-- * **Monitor**: Monitor and take action. +-- * **Start**: Start the transport process. +-- * **Stop**: Stop the transport process. +-- * **Pickup**: Pickup cargo. +-- * **Load**: Load the cargo. +-- * **Loaded**: Flag that the cargo is loaded. +-- * **Deploy**: Deploy cargo to a location. +-- * **Unload**: Unload the cargo. +-- * **Unloaded**: Flag that the cargo is unloaded. +-- * **Home**: A APC is going home. +-- +-- ## 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. +-- +-- === +-- +-- @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 SetAPC The collection of APC @{Wrapper.Group}s. +-- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects. +-- @param Core.Set#SET_ZONE SetDeployZone The collection of deploy @{Zone}s, 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 +-- +-- -- Create a new cargo dispatcher for the set of APCs, with a combatradius of 500. +-- SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, 500 ) +-- +function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, CombatRadius ) + + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone ) ) -- #AI_CARGO_DISPATCHER_APC + + self.CombatRadius = CombatRadius or 500 + + self:SetDeploySpeed( 70, 120 ) + self:SetPickupSpeed( 70, 120 ) + self:SetPickupRadius( 0, 0 ) + self:SetDeployRadius( 0, 0 ) + + return self +end + + +function AI_CARGO_DISPATCHER_APC:AICargo( APC, SetCargo ) + + return AI_CARGO_APC:New( APC, SetCargo, self.CombatRadius ) +end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua new file mode 100644 index 000000000..c208bab9a --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua @@ -0,0 +1,51 @@ +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Planes. +-- +-- === +-- +-- ### 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 uses the @{Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_AIRPLANE object recognize the cargo. +-- Please consult the @{Cargo} module for more information. +-- +-- +-- +-- @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 SetAirplane +-- @param Core.Set#SET_CARGO SetCargo +-- @param Core.Set#SET_ZONE SetDeployZone +-- @return #AI_CARGO_DISPATCHER_AIRPLANE +-- @usage +-- +-- -- Create a new cargo dispatcher +-- SetAirplane = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() +-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo ) +-- +function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo, SetDeployZones ) + + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAirplane, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE + + return self +end + + diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua new file mode 100644 index 000000000..eabc91dc4 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua @@ -0,0 +1,119 @@ +--- **AI** -- Models the intelligent transportation of infantry and other cargo using Helicopters. +-- +-- The @{#AI_CARGO_DISPATCHER_HELICOPTER} classes implements the dynamic dispatching of cargo transportation tasks for helicopters. +-- +-- === +-- +-- ### 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 uses the @{Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_HELICOPTER object recognize the cargo. +-- Please consult the @{Cargo} module for more information. +-- +-- --- +-- +-- ## 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 FSM +-- +-- ![Process](..\Presentations\AI_CARGO_DISPATCHER_HELICOPTER\Dia3.JPG) +-- +-- ### 2.1. AI\_CARGO\_DISPATCHER\_HELICOPTER States +-- +-- * **Monitoring**: The process is dispatching. +-- * **Idle**: The process is idle. +-- +-- ### 2.2. AI\_CARGO\_DISPATCHER\_HELICOPTER Events +-- +-- * **Monitor**: Monitor and take action. +-- * **Start**: Start the transport process. +-- * **Stop**: Stop the transport process. +-- * **Pickup**: Pickup cargo. +-- * **Load**: Load the cargo. +-- * **Loaded**: Flag that the cargo is loaded. +-- * **Deploy**: Deploy cargo to a location. +-- * **Unload**: Unload the cargo. +-- * **Unloaded**: Flag that the cargo is unloaded. +-- * **Home**: A Helicopter is going home. +-- +-- --- +-- +-- ## 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. +-- +-- --- +-- +-- ## 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. +-- +-- --- +-- +-- ## 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. +-- +-- === +-- +-- @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 SetHelicopter The collection of Helicopter @{Wrapper.Group}s. +-- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects. +-- @param Core.Set#SET_ZONE SetDeployZone The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the Helicopters. +-- @return #AI_CARGO_DISPATCHER_HELICOPTER +-- @usage +-- +-- -- Create a new cargo dispatcher +-- SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() +-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo ) +-- +function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZones ) + + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER + + self:SetDeploySpeed( 200, 150 ) + self:SetPickupSpeed( 200, 150 ) + self:SetPickupRadius( 0, 0 ) + self:SetDeployRadius( 0, 0 ) + + return self +end + +function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, SetCargo ) + + return AI_CARGO_HELICOPTER:New( Helicopter, SetCargo ) +end + diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua new file mode 100644 index 000000000..e8b7d24ce --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -0,0 +1,717 @@ +--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo). +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Cargo_Helicopter +-- @image AI_Cargo_Dispatching_For_Helicopters.JPG + +--- @type AI_CARGO_HELICOPTER +-- @extends Core.Fsm#FSM_CONTROLLABLE + + +--- # AI\_CARGO\_TROOPS class, extends @{Core.Fsm#FSM_CONTROLLABLE} +-- +-- === +-- +-- @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 +-- @param #number CombatRadius +-- @return #AI_CARGO_HELICOPTER +function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_HELICOPTER + + self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP + + self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) + + self:SetStartState( "Unloaded" ) + + self:AddTransition( "Unloaded", "Pickup", "*" ) + self:AddTransition( "Loaded", "Deploy", "*" ) + + self:AddTransition( "Unloaded", "Load", "Boarding" ) + self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Loaded", "Unload", "Unboarding" ) + self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) + self:AddTransition( "Unboarding", "Unloaded", "Unloaded" ) + + self:AddTransition( "*", "Landed", "*" ) + self:AddTransition( "*", "Queue", "*" ) + self:AddTransition( "*", "Orbit" , "*" ) + self:AddTransition( "*", "Home" , "*" ) + + self:AddTransition( "*", "Destroyed", "Destroyed" ) + + --- 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 + + --- Pickup Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] Pickup + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate + + --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup + -- @param #AI_CARGO_HELICOPTER self + -- @param #number Delay + -- @param Core.Point#COORDINATE Coordinate + + --- 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 + -- @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 + + --- Deploy Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] Deploy + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate + + --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Delay + + + -- 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 ) + + return self +end + +function AI_CARGO_HELICOPTER:IsTransporting() + + return self.Transporting == true +end + +function AI_CARGO_HELICOPTER:IsRelocating() + + return self.Relocating == true +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 + + +--- @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:onafterLanded( Helicopter, 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< 5. + -- Only then the infantry can unload (and load too, for consistency)! + + self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) + + if self.RoutePickup == true then + if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + self:Load( Helicopter:GetPointVec2() ) + self.RoutePickup = false + self.Relocating = true + end + end + + if self.RouteDeploy == true then + if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + self:Unload( true ) + self.RouteDeploy = false + self.Transporting = false + self.Relocating = 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 ) + + local HelicopterInZone = false + + if Helicopter and Helicopter:IsAlive() == true then + + local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() ) + + if Distance > 2000 then + self:__Queue( -10, Coordinate ) + 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 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 ) + else + self:__Queue( -10, Coordinate ) + 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 ) + + if Helicopter and Helicopter:IsAlive() then + + if not self:IsTransporting() then + 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 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 +end + + + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To, Coordinate ) + + local Boarding = false + + if Helicopter and Helicopter:IsAlive() then + + self.BoardingCount = 0 + + if Helicopter and Helicopter:IsAlive() then + self.Helicopter_Cargo = {} + for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do + local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT + for _, Cargo in pairs( self.CargoSet:GetSet() ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsUnLoaded = Cargo:IsUnLoaded() } ) + if Cargo:IsUnLoaded() then + if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then + self:F( { "In radius", HelicopterUnit:GetName() } ) + --Cargo:Ungroup() + Cargo:Board( HelicopterUnit, 25 ) + self:__Board( 1, Cargo ) + Boarding = true + + -- So now this APCUnit has Cargo that is being loaded. + -- This will be used further in the logic to follow and to check cargo status. + self.Helicopter_Cargo[HelicopterUnit] = Cargo + break + end + end + end + end + end + end + + return Boarding + +end + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To, Cargo ) + self:F( { APC, From, Event, To, Cargo } ) + + if Helicopter and Helicopter:IsAlive() then + self:F({ IsLoaded = Cargo:IsLoaded() } ) + if not Cargo:IsLoaded() then + self:__Board( 10, Cargo ) + else + self:__Loaded( 1, Cargo ) + end + end + +end + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To, Cargo ) + self:F( { APC, From, Event, To } ) + + local Loaded = true + + if Helicopter and Helicopter:IsAlive() then + for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed() } ) + if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then + Loaded = false + end + end + + end + + return Loaded + +end + + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To, Deployed ) + + if Helicopter and Helicopter:IsAlive() then + for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do + local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT + for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do + Cargo:UnBoard() + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo, Deployed ) + end + end + end + + +end + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To, Cargo, Deployed ) + + if Helicopter and Helicopter:IsAlive() then + if not Cargo:IsUnLoaded() then + self:__Unboard( 10, Cargo, Deployed ) + else + self:__Unloaded( 1, Cargo, Deployed ) + end + end + +end + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To, Cargo, Deployed ) + self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } ) + + local AllUnloaded = true + + --Cargo:Regroup() + + if Helicopter and Helicopter:IsAlive() then + for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do + local CargoCheck = self.Helicopter_Cargo[HelicopterUnit] -- Cargo.Cargo#CARGO + if CargoCheck then + self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } ) + if CargoCheck:IsUnLoaded() == false then + AllUnloaded = false + break + end + end + end + + if AllUnloaded == true then + if Deployed == true then + for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + end + self.Helicopter_Cargo = {} + end + self.Helicopter = Helicopter + end + end + + self:F( { AllUnloaded = AllUnloaded } ) + return AllUnloaded + +end + +--- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To, Cargo, Deployed ) + + 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 + ) + +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:onafterPickup( Helicopter, From, Event, To, Coordinate ) + + if Helicopter and Helicopter:IsAlive() ~= nil then + + Helicopter:Activate() + + self.RoutePickup = true + Coordinate.y = math.random( 50, 200 ) + + local Route = {} + + --- Calculate the target route point. + local CoordinateFrom = Helicopter:GetCoordinate() + local CoordinateTo = Coordinate + + --- Create a route point of type air. + local WaypointFrom = CoordinateFrom:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + true + ) + + --- Create a route point of type air. + local WaypointTo = CoordinateTo:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + 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.Transporting = true + end + +end + + +function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate ) + AICargoHelicopter:__Queue( -10, Coordinate, 100 ) +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:onafterDeploy( Helicopter, From, Event, To, Coordinate ) + + if Helicopter and Helicopter:IsAlive() ~= nil then + + self.RouteDeploy = true + + + local Route = {} + + --- Calculate the target route point. + + Coordinate.y = math.random( 50, 200 ) + + --- Create a route point of type air. + local CoordinateFrom = Helicopter:GetCoordinate() + local WaypointFrom = CoordinateFrom:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + true + ) + Route[#Route+1] = WaypointFrom + Route[#Route+1] = WaypointFrom + + --- Create a route point of type air. + local CoordinateTo = Coordinate + local WaypointTo = CoordinateTo:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + 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 = {} + + Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate ) + Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), 150, 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 ) + + 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:onafterHome( Helicopter, From, Event, To, Coordinate ) + + if Helicopter and Helicopter:IsAlive() ~= nil then + + self.RouteHome = true + + local Route = {} + + --- Calculate the target route point. + + Coordinate.y = math.random( 50, 200 ) + + --- Create a route point of type air. + local CoordinateFrom = Helicopter:GetCoordinate() + local WaypointFrom = CoordinateFrom:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + true + ) + Route[#Route+1] = WaypointFrom + + --- Create a route point of type air. + local CoordinateTo = Coordinate + local WaypointTo = CoordinateTo:WaypointAir( + "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + 150, + 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_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 7e5b2828e..af5805bb4 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -1,35 +1,11 @@ ---- **AI** -- (R2.2) - Build large airborne formations of aircraft. +--- **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. -- --- ![Banner Image](..\Presentations\AI_FORMATION\Dia1.JPG) --- --- === --- --- AI_FORMATION makes AI @{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. --- * Make humans guide a large formation, when the planes are wide from each other. --- --- There are the following types of classes defined: --- --- * @{#AI_FORMATION}: Create a formation from several @{GROUP}s. --- -- === -- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20Formation) @@ -45,13 +21,14 @@ -- -- === -- --- @module AI_Formation +-- @module AI.AI_Formation +-- @image AI_Large_Formations.JPG --- AI_FORMATION class -- @type AI_FORMATION --- @extends Fsm#FSM_SET --- @field Unit#UNIT FollowUnit --- @field Set#SET_GROUP FollowGroupSet +-- @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 Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. @@ -61,9 +38,7 @@ -- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. ---- # AI_FORMATION class, extends @{Fsm#FSM_SET} --- --- The #AI_FORMATION class allows you to build large formations, make AI follow a @{Client#CLIENT} (player) leader or a @{Unit#UNIT} (AI) leader. +--- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. -- -- AI_FORMATION makes AI @{GROUP}s fly in formation of various compositions. -- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!! @@ -89,25 +64,25 @@ -- -- Create a new SPAWN object with the @{#AI_FORMATION.New} method: -- --- * @{Follow#AI_FORMATION.New}(): Creates a new AI_FORMATION object from a @{Group#GROUP} for a @{Client#CLIENT} or a @{Unit#UNIT}, with an optional briefing text. +-- * @{#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#AI_FORMATION.FormationLine}(): Form a line formation (core formation function). --- * @{AI_Formation#AI_FORMATION.FormationTrail}(): Form a trail formation. --- * @{AI_Formation#AI_FORMATION.FormationLeftLine}(): Form a left line formation. --- * @{AI_Formation#AI_FORMATION.FormationRightLine}(): Form a right line formation. --- * @{AI_Formation#AI_FORMATION.FormationRightWing}(): Form a right wing formation. --- * @{AI_Formation#AI_FORMATION.FormationLeftWing}(): Form a left wing formation. --- * @{AI_Formation#AI_FORMATION.FormationCenterWing}(): Form a center wing formation. --- * @{AI_Formation#AI_FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing. --- * @{AI_Formation#AI_FORMATION.FormationCenterBoxed}(): Form a center boxed 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_Formation#AI_FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters. +-- 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() @@ -147,7 +122,7 @@ AI_FORMATION = { --- AI_FORMATION class constructor for an AI group -- @param #AI_FORMATION self --- @param Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. +-- @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. -- @return #AI_FORMATION self @@ -155,8 +130,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) self:F( { FollowUnit, FollowGroupSet, FollowName } ) - self.FollowUnit = FollowUnit -- Unit#UNIT - self.FollowGroupSet = FollowGroupSet -- Set#SET_GROUP + self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT + self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP self:SetFlightRandomization( 2 ) @@ -906,7 +881,7 @@ function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XS end ---- Use the method @{AI_Formation#AI_FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation. +--- 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 diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 4cc6e7d6e..1f70d0015 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -1,8 +1,10 @@ ---- **AI** -- (R2.1) - Manages the independent process of Air Patrol for airplanes. +--- **AI** -- Perform Air Patrolling for airplanes. -- --- === +-- **Features:** -- --- ![Banner Image](..\Presentations\AI_PATROL\Dia1.JPG) +-- * Patrol AI airplanes within a given zone. +-- * Trigger detected events when enemy airplanes are detected. +-- * Manage a fuel treshold to RTB on time. -- -- === -- @@ -30,27 +32,25 @@ -- -- === -- --- @module AI_Patrol - +-- @module AI.AI_Patrol +-- @image AI_Air_Patrolling.JPG --- AI_PATROL_ZONE class -- @type AI_PATROL_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @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 ---- # AI_PATROL_ZONE class, extends @{Fsm#FSM_CONTROLLABLE} --- --- The AI_PATROL_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}. +--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}. -- -- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) -- --- The AI_PATROL_ZONE is assigned a @{Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. +-- 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) -- @@ -123,7 +123,7 @@ -- * @{#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 @{Unit}s detected by the AI. +-- 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. @@ -155,11 +155,11 @@ AI_PATROL_ZONE = { --- Creates a new AI_PATROL_ZONE object -- @param #AI_PATROL_ZONE self -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @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. @@ -454,8 +454,8 @@ end --- Sets (modifies) the minimum and maximum speed of the patrol. -- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @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 } ) @@ -468,8 +468,8 @@ end --- Sets the floor and ceiling altitude of the patrol. -- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @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 } ) @@ -562,18 +562,18 @@ function AI_PATROL_ZONE:SetDetectionZone( DetectionZone ) end end ---- Gets a list of @{Unit#UNIT}s that were detected by the AI. +--- 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 @{Unit} class and methods to filter the targets. +-- 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 @{Unit#UNIT}s +-- @return #table The list of @{Wrapper.Unit#UNIT}s function AI_PATROL_ZONE:GetDetectedUnits() self:F2() return self.DetectedUnits end ---- Clears the list of @{Unit#UNIT}s that were detected by the AI. +--- 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() diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua index 809a28690..b0d26c9ea 100644 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ b/Moose Development/Moose/Actions/Act_Account.lua @@ -1,15 +1,15 @@ ---- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Unit}s. +--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Wrapper.Unit}s. -- -- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG) -- -- === -- --- @module Account - +-- @module Actions.Account +-- @image MOOSE.JPG do -- ACT_ACCOUNT - --- # @{#ACT_ACCOUNT} FSM class, extends @{Fsm#FSM_PROCESS} + --- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} -- -- ## ACT_ACCOUNT state machine: -- @@ -55,7 +55,7 @@ do -- ACT_ACCOUNT -- These state transition methods need to provide a return value, which is specified at the function description. -- -- @type ACT_ACCOUNT - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Core.Fsm#FSM_PROCESS ACT_ACCOUNT = { ClassName = "ACT_ACCOUNT", @@ -138,7 +138,7 @@ end -- ACT_ACCOUNT do -- ACT_ACCOUNT_DEADS - --- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} + --- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Core.Fsm.Account#ACT_ACCOUNT} -- -- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. -- The process is given a @{Set} of units that will be tracked upon successful destruction. @@ -151,7 +151,7 @@ do -- ACT_ACCOUNT_DEADS -- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. -- -- @type ACT_ACCOUNT_DEADS - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends #ACT_ACCOUNT ACT_ACCOUNT_DEADS = { ClassName = "ACT_ACCOUNT_DEADS", @@ -160,7 +160,7 @@ do -- ACT_ACCOUNT_DEADS --- Creates a new DESTROY process. -- @param #ACT_ACCOUNT_DEADS self - -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskName function ACT_ACCOUNT_DEADS:New() -- Inherits from BASE @@ -285,7 +285,7 @@ do -- ACT_ACCOUNT_DEADS end --- @param #ACT_ACCOUNT_DEADS self - -- @param Event#EVENTDATA EventData + -- @param Core.Event#EVENTDATA EventData function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) self:T( { "EventDead", EventData } ) @@ -297,7 +297,7 @@ do -- ACT_ACCOUNT_DEADS --- DCS Events --- @param #ACT_ACCOUNT_DEADS self - -- @param Event#EVENTDATA EventData + -- @param Core.Event#EVENTDATA EventData function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData ) self:T( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua index 930810ee2..969009ab1 100644 --- a/Moose Development/Moose/Actions/Act_Assign.lua +++ b/Moose Development/Moose/Actions/Act_Assign.lua @@ -2,7 +2,7 @@ -- -- === -- --- # @{#ACT_ASSIGN} FSM template class, extends @{Fsm#FSM_PROCESS} +-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} -- -- ## ACT_ASSIGN state machine: -- @@ -54,7 +54,7 @@ -- -- === -- --- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN} -- -- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. -- @@ -64,7 +64,7 @@ -- -- === -- --- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm.Assign#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. @@ -77,7 +77,8 @@ -- -- === -- --- @module Assign +-- @module Actions.Assign +-- @image MOOSE.JPG do -- ACT_ASSIGN @@ -155,8 +156,7 @@ do -- ACT_ASSIGN_ACCEPT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit, From, Event, To } ) + function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) self:__Assign( 1 ) end @@ -167,11 +167,8 @@ do -- ACT_ASSIGN_ACCEPT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit, From, Event, To } ) + function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To ) - local ProcessGroup = ProcessUnit:GetGroup() - self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() ) end @@ -192,36 +189,26 @@ do -- ACT_ASSIGN_MENU_ACCEPT --- Init. -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskName -- @param #string TaskBriefing -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) + function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing ) -- Inherits from BASE local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT - self.TaskName = TaskName self.TaskBriefing = TaskBriefing return self end - function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) - - self.TaskName = FsmAssign.TaskName - self.TaskBriefing = FsmAssign.TaskBriefing - 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 TaskName -- @param #string TaskBriefing -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) + function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing ) self.TaskBriefing = TaskBriefing - self.TaskName = TaskName return self end @@ -232,30 +219,31 @@ do -- ACT_ASSIGN_MENU_ACCEPT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit, From, Event, To } ) + function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) - self:GetCommandCenter():MessageTypeToGroup( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", ProcessUnit:GetGroup(), MESSAGE.Type.Information ) + 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 ProcessGroup = ProcessUnit:GetGroup() + 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.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) + self:__Reject( 120, TaskGroup ) end --- Menu function. -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() + function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup ) - self:__Assign( 1 ) + self:__Assign( -1, TaskGroup ) end --- Menu function. -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuReject() + function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup ) - self:__Reject( 1 ) + self:__Reject( -1, TaskGroup ) end --- StateMachine callback function @@ -264,8 +252,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit.UnitNameFrom, Event, To } ) + function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup ) self.Menu:Remove() end @@ -276,13 +263,25 @@ do -- ACT_ASSIGN_MENU_ACCEPT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit.UnitName, From, Event, 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 - ProcessUnit:Destroy() + 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 index 7d4e55d85..f9cd5fc3a 100644 --- a/Moose Development/Moose/Actions/Act_Assist.lua +++ b/Moose Development/Moose/Actions/Act_Assist.lua @@ -1,9 +1,5 @@ --- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. -- --- === --- --- # @{#ACT_ASSIST} FSM class, extends @{Fsm#FSM_PROCESS} --- -- ## ACT_ASSIST state machine: -- -- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. @@ -52,7 +48,7 @@ -- -- === -- --- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} +-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Core.Fsm.Route#ACT_ASSIST} -- -- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. -- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. @@ -64,7 +60,9 @@ -- -- === -- --- @module Smoke +-- @module Actions.Assist +-- @image MOOSE.JPG + do -- ACT_ASSIST @@ -142,7 +140,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE --- ACT_ASSIST_SMOKE_TARGETS_ZONE class -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @field Core.Zone#ZONE_BASE TargetZone -- @extends #ACT_ASSIST ACT_ASSIST_SMOKE_TARGETS_ZONE = { @@ -158,7 +156,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE --- 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 Set#SET_UNIT TargetSetUnit + -- @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 @@ -177,7 +175,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE --- 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 Set#SET_UNIT TargetSetUnit + -- @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 ) diff --git a/Moose Development/Moose/Actions/Act_JTAC.lua b/Moose Development/Moose/Actions/Act_JTAC.lua deleted file mode 100644 index 86f965eb0..000000000 --- a/Moose Development/Moose/Actions/Act_JTAC.lua +++ /dev/null @@ -1,198 +0,0 @@ ---- @module Process_JTAC - ---- PROCESS_JTAC class --- @type PROCESS_JTAC --- @field Wrapper.Unit#UNIT ProcessUnit --- @field Core.Set#SET_UNIT TargetSetUnit --- @extends Core.Fsm#FSM_PROCESS -PROCESS_JTAC = { - ClassName = "PROCESS_JTAC", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_JTAC self --- @param Tasking.Task#TASK Task --- @param Wrapper.Unit#UNIT ProcessUnit --- @param Core.Set#SET_UNIT TargetSetUnit --- @param Wrapper.Unit#UNIT FACUnit --- @return #PROCESS_JTAC self -function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "JTAC", Task, ProcessUnit ) ) -- #PROCESS_JTAC - - self.TargetSetUnit = TargetSetUnit - self.FACUnit = FACUnit - - self.DisplayInterval = 60 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - - self.Fsm = FSM_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'CreatedMenu' }, - { name = 'JTACMenuUpdate', from = 'CreatedMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuAwait', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuSpot', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuCancel', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACStatus', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'Fail', from = 'AwaitingMenu', to = 'Failed' }, - { name = 'Fail', from = 'CreatedMenu', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onJTACMenuUpdate = self.OnJTACMenuUpdate, - onJTACMenuAwait = self.OnJTACMenuAwait, - onJTACMenuSpot = self.OnJTACMenuSpot, - onJTACMenuCancel = self.OnJTACMenuCancel, - }, - endstates = { 'Failed' } - } ) - - self:HandleEvent( EVENTS.Dead, self.EventDead ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnStart( Fsm, From, Event, To ) - - self:NextEvent( Fsm.JTACMenuUpdate ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuUpdate( Fsm, From, Event, To ) - - local function JTACMenuSpot( MenuParam ) - self:F( MenuParam.TargetUnit.UnitName ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuSpot, TargetUnit ) - end - - local function JTACMenuCancel( MenuParam ) - self:F( MenuParam ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuCancel, TargetUnit ) - end - - - -- Loop each unit in the target set, and determine the threat levels map table. - local UnitThreatLevels = self.TargetSetUnit:GetUnitThreatLevels() - - self:F( {"UnitThreadLevels", UnitThreatLevels } ) - - local JTACMenu = self.ProcessGroup:GetState( self.ProcessGroup, "JTACMenu" ) - - if not JTACMenu then - JTACMenu = MENU_GROUP:New( self.ProcessGroup, "JTAC", self.MissionMenu ) - for ThreatLevel, ThreatLevelTable in pairs( UnitThreatLevels ) do - local JTACMenuThreatLevel = MENU_GROUP:New( self.ProcessGroup, ThreatLevelTable.UnitThreatLevelText, JTACMenu ) - for ThreatUnitName, ThreatUnit in pairs( ThreatLevelTable.Units ) do - local JTACMenuUnit = MENU_GROUP:New( self.ProcessGroup, ThreatUnit:GetTypeName(), JTACMenuThreatLevel ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Lase Target", JTACMenuUnit, JTACMenuSpot, { self = self, TargetUnit = ThreatUnit } ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Cancel Target", JTACMenuUnit, JTACMenuCancel, { self = self, TargetUnit = ThreatUnit } ) - end - end - end - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuAwait( Fsm, From, Event, To ) - - if self.DisplayCount >= self.DisplayInterval then - - local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC - TaskJTAC.Spots = TaskJTAC.Spots or {} - for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do - local TargetUnit = UNIT:FindByName( TargetUnitName ) - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuSpot( Fsm, From, Event, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} - - local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetVec3() - - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetVec3(), math.random( 1000, 9999 ) ) - - local SpotData = TaskJTAC.Spots[TargetUnitName] - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuCancel( Fsm, From, Event, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - if TaskJTAC.Spots[TargetUnitName] then - TaskJTAC.Spots[TargetUnitName]:destroy() -- destroys the spot - TaskJTAC.Spots[TargetUnitName] = nil - end - - self.FACUnit:MessageToGroup( "Stopped lasing " .. TargetUnit:GetTypeName(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - - diff --git a/Moose Development/Moose/Actions/Act_Pickup.lua b/Moose Development/Moose/Actions/Act_Pickup.lua deleted file mode 100644 index 3ef76e0eb..000000000 --- a/Moose Development/Moose/Actions/Act_Pickup.lua +++ /dev/null @@ -1,173 +0,0 @@ ---- @module Process_Pickup - ---- PROCESS_PICKUP class --- @type PROCESS_PICKUP --- @field Wrapper.Unit#UNIT ProcessUnit --- @field Core.Set#SET_UNIT TargetSetUnit --- @extends Core.Fsm#FSM_PROCESS -PROCESS_PICKUP = { - ClassName = "PROCESS_PICKUP", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_PICKUP self --- @param Tasking.Task#TASK Task --- @param Wrapper.Unit#UNIT ProcessUnit --- @param Core.Set#SET_UNIT TargetSetUnit --- @return #PROCESS_PICKUP self -function PROCESS_PICKUP:New( Task, ProcessName, ProcessUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_PICKUP - - 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 - - self.Fsm = FSM_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'Navigating' }, - { name = 'Start', from = 'Navigating', to = 'Navigating' }, - { name = 'Nearby', from = 'Navigating', to = 'Preparing' }, - { name = 'Pickup', from = 'Preparing', to = 'Loading' }, - { name = 'Load', from = 'Loading', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Navigating', to = 'Failed' }, - { name = 'Fail', from = 'Preparing', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onNearby = self.OnNearby, - onPickup = self.OnPickup, - onLoad = self.OnLoad, - }, - endstates = { 'Success', 'Failed' } - } ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_PICKUP:OnStart( Fsm, From, Event, To ) - - self:NextEvent( Fsm.Start ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_PICKUP:OnNavigating( Fsm, From, Event, To ) - - local TaskGroup = self.ProcessUnit:GetGroup() - if self.DisplayCount >= self.DisplayInterval then - MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - -end - - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Event#EVENTDATA Event -function PROCESS_PICKUP:OnHitTarget( Fsm, From, Event, To, Event ) - - - self.TargetSetUnit:Flush( self ) - - if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then - self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) - local TaskGroup = self.ProcessUnit:GetGroup() - MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) - end - - - if self.TargetSetUnit:Count() > 0 then - self:NextEvent( Fsm.MoreTargets ) - else - self:NextEvent( Fsm.Destroyed ) - end -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_PICKUP:OnMoreTargets( Fsm, From, Event, To ) - - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Event#EVENTDATA DCSEvent -function PROCESS_PICKUP:OnKilled( Fsm, From, Event, To ) - - self:NextEvent( Fsm.Restart ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_PICKUP:OnRestart( Fsm, From, Event, To ) - - self:NextEvent( Fsm.Menu ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_PICKUP self --- @param Core.Fsm#FSM_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_PICKUP:OnDestroyed( Fsm, From, Event, To ) - -end - ---- DCS Events - ---- @param #PROCESS_PICKUP self --- @param Core.Event#EVENTDATA Event -function PROCESS_PICKUP:EventDead( Event ) - - if Event.IniDCSUnit then - self:NextEvent( self.Fsm.HitTarget, Event ) - end -end - - diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua index e50c3ffcc..9f858879f 100644 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -2,7 +2,7 @@ -- -- === -- --- # @{#ACT_ROUTE} FSM class, extends @{Fsm#FSM_PROCESS} +-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} -- -- ## ACT_ROUTE state machine: -- @@ -60,9 +60,9 @@ -- -- === -- --- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} +-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Core.Fsm.Route#ACT_ROUTE} -- --- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. +-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{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. -- @@ -72,7 +72,8 @@ -- -- === -- --- @module Route +-- @module Actions.Route +-- @image MOOSE.JPG do -- ACT_ROUTE @@ -123,16 +124,20 @@ do -- ACT_ROUTE --- Set a Cancel Menu item. -- @param #ACT_ROUTE self -- @return #ACT_ROUTE - function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime ) + function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag ) - MENU_GROUP_COMMAND:New( + self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, self.MenuCancel, self - ):SetTime(MenuTime) + ):SetTime( MenuTime ):SetTag( MenuTag ) + + ParentMenu:SetTime( MenuTime ) + ParentMenu:Remove( MenuTime, MenuTag ) + return self end @@ -206,7 +211,9 @@ do -- ACT_ROUTE function ACT_ROUTE:MenuCancel() - self:Cancel() + self:F("Cancelled") + self.CancelMenuGroupCommand:Remove() + self:__Cancel( 1 ) end --- Task Events @@ -238,10 +245,8 @@ do -- ACT_ROUTE -- @param #string From -- @param #string To function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To ) - self:F( { "BeforeRoute 1", self.DisplayCount, self.DisplayInterval } ) if ProcessUnit:IsAlive() then - self:F( "BeforeRoute 2" ) local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic if self.DisplayCount >= self.DisplayInterval then self:T( { HasArrived = HasArrived } ) @@ -253,8 +258,6 @@ do -- ACT_ROUTE self.DisplayCount = self.DisplayCount + 1 end - self:T( { DisplayCount = self.DisplayCount } ) - if HasArrived then self:__Arrive( 1 ) else @@ -337,7 +340,7 @@ do -- ACT_ROUTE_POINT -- @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( { self.Range } ) + self:F2( { Range } ) self.Range = Range or 10000 end @@ -345,6 +348,7 @@ do -- ACT_ROUTE_POINT -- @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 diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua new file mode 100644 index 000000000..8add3c374 --- /dev/null +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -0,0 +1,1144 @@ +--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers. +-- +-- === +-- +-- ### 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_Plane#AI_CARGO_PLANE} 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:F( { 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( false ) + 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:F() + + 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:F( { 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 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:F( { 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 Cargo to be Loaded. + -- @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:F( { PointVec2 = PointVec2, NearRadius = NearRadius } ) + + if self.CargoObject:IsAlive() then + --local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) + --self:F( { CargoObjectName = self.CargoObject:GetName() } ) + --self:F( { CargoObjectVec2 = self.CargoObject:GetVec2() } ) + --self:F( { PointVec2 = PointVec2:GetVec2() } ) + local Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) + --self:F( Distance ) + + if Distance <= NearRadius then + --self:F( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = true } ) + return true + end + end + + --self:F( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = false } ) + return false + end + + + + --- Check if Cargo is the given @{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:F( { Zone } ) + + if self:IsLoaded() then + return Zone:IsPointVec2InZone( self.CargoCarrier:GetPointVec2() ) + else + --self:F( { 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 + + --- 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 + + --- 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 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 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 #string Type + -- @param #string Name + -- @param #number Weight + -- @param #number LoadRadius (optional) + -- @param #number NearRadius (optional) + -- @return #CARGO_REPRESENTABLE + function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, LoadRadius, NearRadius ) + local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE + self:F( { Type, Name, Weight, LoadRadius, NearRadius } ) + + 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:F( { 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:F({NearUnit=NearUnit}) + local NearUnitCoalition = NearUnit:GetCoalition() + local CargoCoalition = self:GetCoalition() + if NearUnitCoalition == CargoCoalition then + local Attributes = NearUnit:GetDesc() + self:F({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:F( { 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:F( { 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:F() + + 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:F() + + 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 +function CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + 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 #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:F() + + 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 +function CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) + self:F() + + 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:F() + + 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 #number Distance +-- @param #number Angle +function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) + self:F() + + 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 new file mode 100644 index 000000000..399c29ffb --- /dev/null +++ b/Moose Development/Moose/Cargo/CargoCrate.lua @@ -0,0 +1,311 @@ +--- **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. + -- + -- === + -- + -- @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:F( { 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.PlayerLeaveUnit, self.OnEventCargoDead ) + + self:SetEventPriority( 4 ) + + 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:F( { 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:F( { 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() + --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 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:F( { 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:F( { 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:F() + + 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:F( {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:F( {NearRadius = NearRadius } ) + + return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) + end + + --- Respawn the CargoGroup. + -- @param #CARGO_CRATE self + function CARGO_CRATE:Respawn() + + self:F( { "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:F( { "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 new file mode 100644 index 000000000..de8b08e6d --- /dev/null +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -0,0 +1,772 @@ +--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object. +-- +-- === +-- +-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG) +-- +-- === +-- +-- ### [Demo Missions]() +-- +-- ### [YouTube Playlist]() +-- +-- === +-- +-- ### Author: **FlightControl** +-- ### Contributions: +-- +-- === +-- +-- @module Cargo.CargoGroup +-- @image Cargo_Groups.JPG + + +do -- CARGO_GROUP + + --- @type CARGO_GROUP + -- @extends Cargo.Cargo#CARGO_REPORTABLE + -- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects. + -- @field #string GroupName The name of the CargoGroup. + + --- 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 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_Plane#AI_CARGO_PLANE} 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 + -- + -- @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 @{Set} of individual Unit objects. + -- @param #CARGO_GROUP self + -- @param Wrapper.Group#GROUP CargoGroup + -- @param #string Type + -- @param #string Name + -- @param #number LoadRadius (optional) + -- @param #number NearRadius (optional) + -- @return #CARGO_GROUP + function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius ) + local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius ) ) -- #CARGO_GROUP + self:F( { Type, Name, LoadRadius } ) + + self.CargoSet = SET_CARGO:New() + self.CargoGroup = CargoGroup + self.Grouped = true + self.CargoUnitTemplate = {} + + self:SetDeployed( false ) + + local WeightGroup = 0 + + self.CargoGroup:Destroy() + + local GroupName = CargoGroup:GetName() + self.CargoName = Name + self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) ) + + local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) + GroupTemplate.name = self.CargoName .. "#CARGO" + GroupTemplate.groupId = nil + + GroupTemplate.units = {} + + for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do + UnitTemplate.name = UnitTemplate.name .. "#CARGO" + local CargoUnitName = UnitTemplate.name + self.CargoUnitTemplate[CargoUnitName] = UnitTemplate + + GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] + GroupTemplate.units[#GroupTemplate.units].unitId = nil + + -- And we register the spawned unit as part of the CargoSet. + local Unit = UNIT:Register( CargoUnitName ) + --local WeightUnit = Unit:GetDesc().massEmpty + --WeightGroup = WeightGroup + WeightUnit + local CargoUnit = CARGO_UNIT:New( Unit, Type, CargoUnitName, 10 ) + self.CargoSet:Add( CargoUnitName, CargoUnit ) + end + + -- Then we register the new group in the database + self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID) + + -- Now we spawn the new group based on the template created. + self.CargoObject = _DATABASE:Spawn( GroupTemplate ) + + self:SetWeight( WeightGroup ) + self.CargoLimit = 10 + + 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.PlayerLeaveUnit, self.OnEventCargoDead ) + + self:SetEventPriority( 4 ) + + return self + 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:F("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:F( { 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:F( { "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:I( 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:E( { "Cargo group destroyed" } ) + end + + end + + --- Enter Boarding State. + -- @param #CARGO_GROUP self + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + --self:F( { CargoCarrier.UnitName, From, Event, To } ) + + local NearRadius = NearRadius or 25 + + if From == "UnLoaded" then + + -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo, ... ) + Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) + end, ... + ) + + self:__Boarding( 1, CargoCarrier, NearRadius, ... ) + end + + 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:onenterLoaded( From, Event, To, CargoCarrier, ... ) + --self:F( { 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 + Cargo:Load( CargoCarrier ) + end + end + + --self.CargoObject:Destroy() + self.CargoCarrier = CargoCarrier + self.CargoCarrier:AddCargo( self ) + + end + + --- Leave Boarding State. + -- @param #CARGO_GROUP self + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + --self:F( { CargoCarrier.UnitName, From, Event, To } ) + + local NearRadius = NearRadius or 100 + + 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( 1, CargoCarrier, NearRadius, ... ) + else + self:F("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 Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + --self:F( {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 + Cargo:__UnBoard( Timer, ToPointVec2, NearRadius ) + Timer = Timer + 3 + end + end, { NearRadius } + ) + + + self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) + end + + end + + --- Leave UnBoarding State. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + --self:F( { 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 + return true + else + self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) + end + + return false + end + + end + + --- UnBoard Event. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + --self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + --local NearRadius = NearRadius or 25 + + self:__UnLoad( 1, ToPointVec2, ... ) + end + + + + --- Enter UnLoaded State. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) + --self:F( { 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=ToPointVec2:GetRandomPointVec2InRadius(10) + Cargo:UnLoad( 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() + self:F() + + 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 amount of cargo units in the 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:F( {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. + -- @return #nil The Cargo is not near to the Carrier. + function CARGO_GROUP:IsNear( CargoCarrier, NearRadius ) + self:F( {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:F( "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:F( { Coordinate } ) + + local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO + + if Cargo then + local Distance = 0 + if Cargo:IsLoaded() then + Distance = Coordinate:Get2DDistance( Cargo.CargoCarrier:GetCoordinate() ) + else + Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() ) + end + + self:F( { 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:F( { Coordinate } ) + + local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO + + if Cargo then + self:F( { 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 + + --- Respawn the CargoGroup. + -- @param #CARGO_GROUP self + function CARGO_GROUP:Respawn() + + self:F( { "Respawning" } ) + + for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do + local Cargo = CargoData -- Cargo.Cargo#CARGO + Cargo:Destroy() + Cargo:SetStartState( "UnLoaded" ) + end + + + -- We iterate through the group template and for each unit in the template, we create a new group with one unit. + for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do + + 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 = GroupName .. "#CARGO#" .. UnitID + GroupTemplate.groupId = nil + GroupTemplate.units = {} + GroupTemplate.units[1] = UnitTemplate + local UnitName = UnitTemplate.name .. "#CARGO" + GroupTemplate.units[1].name = UnitTemplate.name .. "#CARGO" + + + -- 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 ) + + -- And we register the spawned unit as part of the CargoSet. + local Unit = UNIT:FindByName( UnitName ) + --local WeightUnit = Unit:GetDesc().massEmpty + --WeightGroup = WeightGroup + WeightUnit + local CargoUnit = CARGO_UNIT:New( Unit, Type, UnitName, 10 ) + self.CargoSet:Add( UnitName, CargoUnit ) + end + + self:SetDeployed( false ) + self:SetStartState( "UnLoaded" ) + + 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 @{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:F( { 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 new file mode 100644 index 000000000..ff948d4e6 --- /dev/null +++ b/Moose Development/Moose/Cargo/CargoSlingload.lua @@ -0,0 +1,262 @@ +--- **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. + -- + -- === + -- + -- @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:F( { 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.PlayerLeaveUnit, self.OnEventCargoDead ) + + self:SetEventPriority( 4 ) + + 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:F( { 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:F( { 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:F() + + 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:F( {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:F( {NearRadius = NearRadius } ) + + return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) + end + + + --- Respawn the CargoGroup. + -- @param #CARGO_SLINGLOAD self + function CARGO_SLINGLOAD:Respawn() + + --self:F( { "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:F( { "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 new file mode 100644 index 000000000..2a5f009ac --- /dev/null +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -0,0 +1,393 @@ +--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.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. + -- + -- === + -- + -- @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, Weight, NearRadius ) + local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT + self:I( { Type, Name, Weight, NearRadius } ) + + self:T( CargoUnit ) + self.CargoObject = CargoUnit + + self:T( self.ClassName ) + + 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 + function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 25 + + 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 + self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) + self:F( { "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 + function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 100 + + 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 + function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 100 + + 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:F( { 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 + function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { From, Event, To, CargoCarrier, NearRadius } ) + + local NearRadius = NearRadius or 25 + + self.CargoInAir = self.CargoObject:InAir() + + local Desc = self.CargoObject:GetDesc() + local MaxSpeed = Desc.speedMaxOffRoad + local TypeName = Desc.typeName + + self:T( self.CargoInAir ) + + -- 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 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 = 5 + + NearRadius = NearRadius or 25 + + 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( -1, 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 + function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + --self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) + + + if CargoCarrier and CargoCarrier:IsAlive() and self.CargoObject and self.CargoObject:IsAlive() then + if CargoCarrier:InAir() == false then + if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then + self:__Load( 1, CargoCarrier, ... ) + else + self:__Boarding( -1, CargoCarrier, NearRadius, ... ) + self.RunCount = self.RunCount + 1 + if self.RunCount >= 40 then + self.RunCount = 0 + local Speed = 90 + local Angle = 180 + local Distance = 5 + + NearRadius = NearRadius or 25 + + 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:E("Something is wrong") + end + + end + + + --- Enter Boarding State. + -- @param #CARGO_UNIT self + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + --self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) + + local Speed = 90 + local Angle = 180 + local Distance = 5 + + local NearRadius = NearRadius or 25 + + if From == "UnLoaded" or From == "Boarding" then + + 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:F( { 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.CargoObject:Destroy() + --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/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index f642d79b1..83d9cd5a7 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1,15 +1,12 @@ --- **Core** -- BASE forms **the basis of the MOOSE framework**. Each class within the MOOSE framework derives from BASE. -- --- ![Banner Image](..\Presentations\BASE\Dia1.JPG) --- --- === --- -- ### Author: **FlightControl** -- ### Contributions: -- -- === -- --- @module Base +-- @module Core.Base +-- @image Core_Base.JPG @@ -26,9 +23,7 @@ local _ClassID = 0 -- @field ClassID The ID number of the class. -- @field ClassNameAndID The name of the class concatenated with the ID number of the class. ---- # 1) #BASE class --- --- All classes within the MOOSE framework are derived from the BASE class. +--- All classes within the MOOSE framework are derived from the BASE class. -- -- BASE provides facilities for : -- @@ -42,8 +37,8 @@ local _ClassID = 0 -- -- ## 1.1) BASE constructor -- --- Any class derived from BASE, will use the @{Base#BASE.New} constructor embedded in the @{Base#BASE.Inherit} method. --- See an example at the @{Base#BASE.New} method how this is done. +-- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method. +-- See an example at the @{Core.Base#BASE.New} method how this is done. -- -- ## 1.2) Trace information for debugging -- @@ -127,7 +122,7 @@ local _ClassID = 0 -- ### 1.3.2 Event Handling of DCS Events -- -- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information +-- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information -- about the event that occurred. -- -- Find below an example of the prototype how to write an event handling function for two units: @@ -609,8 +604,8 @@ end --- Creation of a Birth Event. -- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. -- @param #string IniUnitName The initiating unit name. -- @param place -- @param subplace @@ -631,8 +626,8 @@ end --- Creation of a Crash Event. -- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. function BASE:CreateEventCrash( EventTime, Initiator ) self:F( { EventTime, Initiator } ) @@ -645,10 +640,26 @@ function BASE:CreateEventCrash( EventTime, Initiator ) world.onEvent( Event ) end +--- Creation of a Dead Event. +-- @param #BASE self +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. +function BASE:CreateEventDead( EventTime, Initiator ) + self:F( { EventTime, Initiator } ) + + local Event = { + id = world.event.S_EVENT_DEAD, + time = EventTime, + initiator = Initiator, + } + + world.onEvent( Event ) +end + --- Creation of a Takeoff Event. -- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. function BASE:CreateEventTakeoff( EventTime, Initiator ) self:F( { EventTime, Initiator } ) @@ -661,10 +672,10 @@ function BASE:CreateEventTakeoff( EventTime, Initiator ) world.onEvent( Event ) end --- TODO: Complete Dcs.DCSTypes#Event structure. +-- TODO: Complete DCS#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self --- @param Dcs.DCSTypes#Event event +-- @param DCS#Event event function BASE:onEvent(event) --self:F( { BaseEventCodes[event.id], event } ) diff --git a/Moose Development/Moose/Core/Cargo.lua b/Moose Development/Moose/Core/Cargo.lua deleted file mode 100644 index 991669931..000000000 --- a/Moose Development/Moose/Core/Cargo.lua +++ /dev/null @@ -1,1677 +0,0 @@ ---- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers. --- --- ![Banner Image](..\Presentations\CARGO\Dia1.JPG) --- --- === --- --- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): --- --- * CARGO_UNIT, represented by a @{Unit} in a singleton @{Group}: Cargo can be represented by a Unit in a Group. a CARGO_UNIT is representable... --- * CARGO_GROUP, represented by a @{Group}. A CARGO_GROUP is reportable... --- --- This module is still under construction, but is described above works already, and will keep working ... --- --- === --- --- # Demo Missions --- --- ### [CARGO Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CGO%20-%20Cargo) --- --- ### [CARGO Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CGO%20-%20Cargo) --- --- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- === --- --- # YouTube Channel --- --- ### [CARGO YouTube Channel](https://www.youtube.com/watch?v=tM00lTlkpYs&list=PL7ZUrU4zZUl2zUTuKrLW5RsO9zLMqUtbf) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo - --- 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) @{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) @{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) @{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) @{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.Controllable#CONTROLLABLE 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. - - --- # (R2.1) CARGO class, extends @{Fsm#FSM_PROCESS} - -- - -- The CARGO class 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. - -- - -- 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.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. - -- - -- ## CARGO States: - -- - -- * **UnLoaded**: The cargo is unloaded from a carrier. - -- * **Boarding**: The cargo is currently boarding (= running) into a carrier. - -- * **Loaded**: The cargo is loaded into a carrier. - -- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. - -- * **Dead**: The cargo is dead ... - -- * **End**: The process has come to an end. - -- - -- ## CARGO 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: - -- - -- * **Leaving** the state. - -- The state transition method needs to start with the name **OnLeave + 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! - -- - -- * **Entering** the state. - -- The state transition method needs to start with the name **OnEnter + the name of the state**. - -- These state transition methods need to provide a return value, which is specified at the function description. - -- - -- @field #CARGO - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @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 NearRadius (optional) --- @return #CARGO -function CARGO:New( Type, Name, Weight ) --R2.1 - - local self = BASE:Inherit( self, FSM:New() ) -- #CARGO - self:F( { Type, Name, Weight } ) - - 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.Type = Type - self.Name = Name - self.Weight = Weight - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - self:SetDeployed( false ) - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - - return self -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 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 type of the Cargo. --- @param #CARGO self --- @return #string The type of the Cargo. -function CARGO:GetType() - return self.Type -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 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 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 -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:F() - -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 -function CARGO:Smoke( SmokeColor, Range ) - self:F2() - if self:IsUnLoaded() then - if Range then - trigger.action.smoke( self.CargoObject:GetRandomVec3( Range ), 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 - - - - - - ---- Check if Cargo is the given @{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:F( { Zone } ) - - if self:IsLoaded() then - return Zone:IsPointVec2InZone( self.CargoCarrier:GetPointVec2() ) - else - self:F( { 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 - - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO self --- @param Core.Point#POINT_VEC2 PointVec2 --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). --- @return #boolean -function CARGO:IsNear( PointVec2, NearRadius ) - self:F( { PointVec2, NearRadius } ) - - --local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= NearRadius then - return true - else - return false - end -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 - ---- 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 - -end - - -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 #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number ReportRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - 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:F( { 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 - - -end -- CARGO_REPRESENTABLE - - do -- CARGO_REPORTABLE - - --- @type CARGO_REPORTABLE - -- @extends #CARGO - CARGO_REPORTABLE = { - ClassName = "CARGO_REPORTABLE" - } - - --- CARGO_REPORTABLE Constructor. - -- @param #CARGO_REPORTABLE self - -- @param Wrapper.Controllable#Controllable CargoObject - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number ReportRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_REPORTABLE - function CARGO_REPORTABLE:New( CargoObject, Type, Name, Weight, ReportRadius ) - local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight ) ) -- #CARGO_REPORTABLE - self:F( { Type, Name, Weight, ReportRadius } ) - - self.CargoSet = SET_CARGO:New() -- Core.Set#SET_CARGO - - self.ReportRadius = ReportRadius or 1000 - self.CargoObject = CargoObject - - - - return self - end - - --- Check if CargoCarrier is in the ReportRadius for the Cargo to be Loaded. - -- @param #CARGO_REPORTABLE self - -- @param Core.Point#POINT_VEC2 PointVec2 - -- @return #boolean - function CARGO_REPORTABLE:IsInRadius( PointVec2 ) - self:F( { PointVec2 } ) - - local Distance = 0 - if self:IsLoaded() then - Distance = PointVec2:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - else - Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - end - self:T( Distance ) - - if Distance <= self.ReportRadius then - return true - else - return false - end - - - end - - --- Send a CC message to a GROUP. - -- @param #CARGO_REPORTABLE self - -- @param #string Message - -- @param Wrapper.Group#GROUP TaskGroup - -- @param #sring 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 ) - - local Prefix = Name and "@ " .. Name .. ": " or "@ " .. TaskGroup:GetCallsign() .. ": " - Message = Prefix .. Message - MESSAGE:New( Message, 20, "Cargo: " .. self:GetName() ):ToGroup( TaskGroup ) - - end - - --- Get the range till cargo will board. - -- @param #CARGO_REPORTABLE self - -- @return #number The range till cargo will board. - function CARGO_REPORTABLE:GetBoardingRange() - return self.ReportRadius - end - - --- Respawn the cargo. - -- @param #CARGO_REPORTABLE self - function CARGO_REPORTABLE:Respawn() - - self:F({"Respawning"}) - - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- #CARGO - Cargo:Destroy() - Cargo:SetStartState( "UnLoaded" ) - end - - local CargoObject = self.CargoObject -- Wrapper.Group#GROUP - CargoObject:Destroy() - local Template = CargoObject:GetTemplate() - CargoObject:Respawn( Template ) - - self:SetDeployed( false ) - - local WeightGroup = 0 - - self:SetStartState( "UnLoaded" ) - - end - - -end - -do -- CARGO_UNIT - - --- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded. - -- @type CARGO_UNIT - -- @extends #CARGO_REPRESENTABLE - - --- # CARGO\_UNIT class, extends @{#CARGO_REPRESENTABLE} - -- - -- The CARGO\_UNIT class 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. - -- - -- === - -- - -- @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 ReportRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_UNIT - function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT - self:F( { Type, Name, Weight, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self:T( self.ClassName ) - - 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 - function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 60 - local DeployDistance = 9 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE - - 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 - ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 - local DirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2) - local Angle = CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3) - - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, Angle ) - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = CargoCarrierPointVec2:WaypointGround( Speed ) - - Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) - 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 - function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - 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 - function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - 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:F( { 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:ReSpawn( ToPointVec2:GetVec3(), 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 - function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier, NearRadius } ) - - local NearRadius = NearRadius or 25 - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- 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 self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:Load( CargoCarrier, NearRadius, ... ) - else - local Speed = 90 - local Angle = 180 - local Distance = 5 - - NearRadius = NearRadius or 25 - - 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 ) - - 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( -1, CargoCarrier, NearRadius ) - self.RunCount = 0 - end - end - - end - - - --- Boarding Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - -- @param #number NearRadius - function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) - - - if CargoCarrier and CargoCarrier:IsAlive() then - if CargoCarrier:InAir() == false then - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:__Load( 1, CargoCarrier, ... ) - else - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 1 - if self.RunCount >= 60 then - self.RunCount = 0 - local Speed = 90 - local Angle = 180 - local Distance = 5 - - NearRadius = NearRadius or 25 - - 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 ) - - 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, 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:E("Something is wrong") - end - - end - - - --- Enter Boarding State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) - - local Speed = 90 - local Angle = 180 - local Distance = 5 - - local NearRadius = NearRadius or 25 - - if From == "UnLoaded" or From == "Boarding" then - - 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:F( { 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.CargoObject:Destroy() - end - end - -end -- CARGO_UNIT - - -do -- CARGO_CRATE - - --- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters using the DCS menus. - -- @type CARGO_CRATE - -- @extends #CARGO_REPRESENTABLE - - --- # CARGO\_CRATE class, extends @{#CARGO_REPRESENTABLE} - -- - -- The CARGO\_CRATE class 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. - -- - -- === - -- - -- @field #CARGO_CRATE - CARGO_CRATE = { - ClassName = "CARGO_CRATE" - } - - --- CARGO_CRATE Constructor. - -- @param #CARGO_CRATE self - -- @param #string CrateName - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number ReportRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_CRATE - function CARGO_CRATE:New( CargoCrateName, Type, Name, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoCrateName, Type, Name, nil, NearRadius ) ) -- #CARGO_CRATE - self:F( { Type, Name, NearRadius } ) - - self:T( CargoCrateName ) - _DATABASE:AddStatic( CargoCrateName ) - - self.CargoObject = STATIC:FindByName( CargoCrateName ) - - self:T( self.ClassName ) - - self:SetEventPriority( 5 ) - - return self - 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:F( { 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:ReSpawn( 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:F( { 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.CargoObject:Destroy() - end - end - - - -end - -do -- CARGO_GROUP - - --- @type CARGO_GROUP - -- @extends #CARGO_REPORTABLE - - --- # CARGO\_GROUP class - -- - -- The CARGO\_GROUP class defines a cargo that is represented by a @{Group} 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\_GROUP to and from carrier. - -- - -- @field #CARGO_GROUP CARGO_GROUP - -- - CARGO_GROUP = { - ClassName = "CARGO_GROUP", - } - ---- CARGO_GROUP constructor. --- @param #CARGO_GROUP self --- @param Wrapper.Group#GROUP CargoGroup --- @param #string Type --- @param #string Name --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_GROUP -function CARGO_GROUP:New( CargoGroup, Type, Name, ReportRadius ) - local self = BASE:Inherit( self, CARGO_REPORTABLE:New( CargoGroup, Type, Name, 0, ReportRadius ) ) -- #CARGO_GROUP - self:F( { Type, Name, ReportRadius } ) - - self.CargoObject = CargoGroup - self:SetDeployed( false ) - self.CargoGroup = CargoGroup - - local WeightGroup = 0 - - for UnitID, UnitData in pairs( CargoGroup:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - local WeightUnit = Unit:GetDesc().massEmpty - WeightGroup = WeightGroup + WeightUnit - local CargoUnit = CARGO_UNIT:New( Unit, Type, Unit:GetName(), WeightUnit ) - self.CargoSet:Add( CargoUnit:GetName(), CargoUnit ) - 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.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - return self -end - ---- @param #CARGO_GROUP self --- @param Core.Event#EVENTDATA EventData -function CARGO_GROUP:OnEventCargoDead( EventData ) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() then - Destroyed = true - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- #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:E( { "Cargo group destroyed" } ) - end - -end - ---- Enter Boarding State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local NearRadius = NearRadius or 25 - - if From == "UnLoaded" then - - -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo, ... ) - Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) - end, ... - ) - - self:__Boarding( 1, CargoCarrier, NearRadius, ... ) - end - -end - ---- Enter Loaded State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... ) - self:F( { 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 - Cargo:Load( CargoCarrier ) - end - end - - --self.CargoObject:Destroy() - self.CargoCarrier = CargoCarrier - -end - ---- Leave Boarding State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local NearRadius = NearRadius or 25 - - 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( 1, CargoCarrier, NearRadius, ... ) - else - self:__Load( 1, CargoCarrier, ... ) - end - else - self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... ) - end - else - self:__Destroyed( 1, CargoCarrier, NearRadius, ... ) - end - -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 - - ---- Enter UnBoarding State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( {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( - function( Cargo, NearRadius ) - - Cargo:__UnBoard( Timer, ToPointVec2, NearRadius ) - Timer = Timer + 10 - end, { NearRadius } - ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - -end - ---- Leave UnBoarding State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( { 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.current ) - if not Cargo:is( "UnLoaded" ) then - UnBoarded = false - end - end - - if UnBoarded then - return true - else - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - - return false - end - -end - ---- UnBoard Event. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - --local NearRadius = NearRadius or 25 - - self:__UnLoad( 1, ToPointVec2, ... ) -end - - - ---- Enter UnLoaded State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) - self:F( { 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=ToPointVec2:GetRandomPointVec2InRadius(10) - Cargo:UnLoad( RandomVec2 ) - end - ) - - end - -end - - - --- Respawn the cargo when destroyed - -- @param #CARGO_GROUP self - -- @param #boolean RespawnDestroyed - function CARGO_GROUP:RespawnOnDestroyed( RespawnDestroyed ) - self:F({"In function RespawnOnDestroyed"}) - if RespawnDestroyed then - self.onenterDestroyed = function( self ) - self:F("IN FUNCTION") - self:Respawn() - end - else - self.onenterDestroyed = nil - end - - end - -end -- CARGO_GROUP - -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 ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO_PACKAGE - self:F( { Type, Name, Weight, ReportRadius, 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:F() - - 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:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - 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 -function CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - 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 #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:F() - - 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 -function CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) - self:F() - - 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:F() - - 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 #number Distance --- @param #number Angle -function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - 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/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index d90e01284..82e195f82 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -7,13 +7,14 @@ -- -- === -- --- @module Database +-- @module Core.Database +-- @image Core_Database.JPG --- @type DATABASE -- @extends Core.Base#BASE ---- # DATABASE class, extends @{Base#BASE} +--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. -- -- Mission designers can use the DATABASE class to refer to: -- @@ -58,6 +59,7 @@ DATABASE = { ZONENAMES = {}, HITS = {}, DESTROYS = {}, + ZONES = {}, } local _DATABASECoalition = @@ -95,6 +97,8 @@ function DATABASE:New() self:HandleEvent( EVENTS.Hit, self.AccountHits ) self:HandleEvent( EVENTS.NewCargo ) self:HandleEvent( EVENTS.DeleteCargo ) + self:HandleEvent( EVENTS.NewZone ) + self:HandleEvent( EVENTS.DeleteZone ) -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. @@ -242,35 +246,178 @@ function DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end ---- Adds a Cargo based on the Cargo Name in the DATABASE. --- @param #DATABASE self --- @param #string CargoName The name of the airbase -function DATABASE:AddCargo( Cargo ) - if not self.CARGOS[Cargo.Name] then - self.CARGOS[Cargo.Name] = Cargo +do -- Zones + + --- Finds a @{Zone} based on the zone name. + -- @param #DATABASE self + -- @param #string ZoneName The name of the zone. + -- @return Core.Zone#ZONE_BASE The found ZONE. + function DATABASE:FindZone( ZoneName ) + + local ZoneFound = self.ZONES[ZoneName] + return ZoneFound + end + + --- Adds a @{Zone} based on the zone name in the DATABASE. + -- @param #DATABASE self + -- @param #string ZoneName The name of the zone. + -- @param Core.Zone#ZONE_BASE Zone The zone. + function DATABASE:AddZone( ZoneName, Zone ) + + if not self.ZONES[ZoneName] then + self.ZONES[ZoneName] = Zone + end + end + + + --- Deletes a @{Zone} from the DATABASE based on the zone name. + -- @param #DATABASE self + -- @param #string ZoneName The name of the zone. + function DATABASE:DeleteZone( ZoneName ) + + self.ZONES[ZoneName] = nil + end + + --- Finds an @{Zone} based on the zone name in the DATABASE. + -- @param #DATABASE self + -- @param #string ZoneName + -- @return Core.Zone#ZONE_BASE The found @{Zone}. + function DATABASE:FindZone( ZoneName ) + + local ZoneFound = self.ZONES[ZoneName] + return ZoneFound end -end ---- Deletes a Cargo from the DATABASE based on the Cargo Name. --- @param #DATABASE self --- @param #string CargoName The name of the airbase -function DATABASE:DeleteCargo( CargoName ) + --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. + -- @param #DATABASE self + -- @return #DATABASE self + function DATABASE:_RegisterZones() - self.CARGOS[CargoName] = nil -end + for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do + local ZoneName = ZoneData.name ---- Finds an CARGO based on the CargoName. --- @param #DATABASE self --- @param #string CargoName --- @return Wrapper.Cargo#CARGO The found CARGO. -function DATABASE:FindCargo( CargoName ) + self:I( { "Register ZONE:", Name = ZoneName } ) + local Zone = ZONE:New( ZoneName ) + self.ZONENAMES[ZoneName] = ZoneName + self:AddZone( ZoneName, Zone ) + end + + for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do + if ZoneGroupName:match("~ZONE_POLYGON") then + local ZoneName1 = ZoneGroupName:match("(.*)~ZONE_POLYGON") + local ZoneName2 = ZoneGroupName:match(".*~ZONE_POLYGON(.*)") + local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) + + self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) + local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) + self.ZONENAMES[ZoneName] = ZoneName + self:AddZone( ZoneName, Zone_Polygon ) + end + end + + end - local CargoFound = self.CARGOS[CargoName] - return CargoFound -end +end -- zone + + +do -- cargo + + --- Adds a Cargo based on the Cargo Name in the DATABASE. + -- @param #DATABASE self + -- @param #string CargoName The name of the airbase + function DATABASE:AddCargo( Cargo ) + + if not self.CARGOS[Cargo.Name] then + self.CARGOS[Cargo.Name] = Cargo + end + end + + + --- Deletes a Cargo from the DATABASE based on the Cargo Name. + -- @param #DATABASE self + -- @param #string CargoName The name of the airbase + function DATABASE:DeleteCargo( CargoName ) + + self.CARGOS[CargoName] = nil + end + + --- Finds an CARGO based on the CargoName. + -- @param #DATABASE self + -- @param #string CargoName + -- @return Wrapper.Cargo#CARGO The found CARGO. + function DATABASE:FindCargo( CargoName ) + + local CargoFound = self.CARGOS[CargoName] + return CargoFound + end + + --- Checks if the Template name has a ~CARGO tag. + -- If yes, the group is a cargo. + -- @param #DATABASE self + -- @param #string TemplateName + -- @return #boolean + function DATABASE:IsCargo( TemplateName ) + + TemplateName = env.getValueDictByKey( TemplateName ) + + local Cargo = TemplateName:match( "~(CARGO)" ) + + return Cargo and Cargo == "CARGO" + end + + --- Private method that registers new Static Templates within the DATABASE Object. + -- @param #DATABASE self + -- @return #DATABASE self + function DATABASE:_RegisterCargos() + + + for CargoGroupName, CargoGroup in pairs( self.GROUPS ) do + if self:IsCargo( CargoGroupName ) then + local CargoInfo = CargoGroupName:match("~CARGO(.*)") + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") + local CargoName1 = CargoGroupName:match("(.*)~CARGO%(.*%)") + local CargoName2 = CargoGroupName:match(".*~CARGO%(.*%)(.*)") + self:E({CargoName1 = CargoName1, CargoName2 = CargoName2 }) + local CargoName = CargoName1 .. ( CargoName2 or "" ) + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + + self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) + end + end + + for CargoStaticName, CargoStatic in pairs( self.STATICS ) do + if self:IsCargo( CargoStaticName ) then + local CargoInfo = CargoStaticName:match("~CARGO(.*)") + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") + local CargoName = CargoStaticName:match("(.*)~CARGO") + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") + local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?") + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + + if Category == "SLING" then + self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) + else + if Category == "CRATE" then + self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) + end + end + end + end + + end + +end -- cargo --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self @@ -442,16 +589,14 @@ end --- Private method that registers new Group Templates within the DATABASE Object. -- @param #DATABASE self -- @param #table GroupTemplate --- @param Dcs.DCScoalition#coalition.side CoalitionSide The coalition.side of the object. --- @param Dcs.DCSObject#Object.Category CategoryID The Object.category of the object. --- @param Dcs.DCScountry#country.id CountryID the country.id of the object +-- @param DCS#coalition.side CoalitionSide The coalition.side of the object. +-- @param DCS#Object.Category CategoryID The Object.category of the object. +-- @param DCS#country.id CountryID the country.id of the object -- @return #DATABASE self function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name ) - local TraceTable = {} - if not self.Templates.Groups[GroupTemplateName] then self.Templates.Groups[GroupTemplateName] = {} self.Templates.Groups[GroupTemplateName].Status = nil @@ -475,18 +620,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide self.Templates.Groups[GroupTemplateName].CountryID = CountryID - - TraceTable[#TraceTable+1] = "Group" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName - - TraceTable[#TraceTable+1] = "Coalition" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID - TraceTable[#TraceTable+1] = "Category" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID - TraceTable[#TraceTable+1] = "Country" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID - - TraceTable[#TraceTable+1] = "Units" + local UnitNames = {} for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do @@ -510,10 +644,16 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName + UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end - self:E( TraceTable ) + self:I( { Group = self.Templates.Groups[GroupTemplateName].GroupName, + Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, + Category = self.Templates.Groups[GroupTemplateName].CategoryID, + Country = self.Templates.Groups[GroupTemplateName].CountryID, + Units = UnitNames + } + ) end function DATABASE:GetGroupTemplate( GroupName ) @@ -530,8 +670,6 @@ end -- @return #DATABASE self function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID ) - local TraceTable = {} - local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} @@ -547,28 +685,22 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID self.Templates.Statics[StaticTemplateName].CountryID = CountryID + self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, + Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, + Category = self.Templates.Statics[StaticTemplateName].CategoryID, + Country = self.Templates.Statics[StaticTemplateName].CountryID + } + ) + + self:AddStatic( StaticTemplateName ) - TraceTable[#TraceTable+1] = "Static" - TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].StaticName - - TraceTable[#TraceTable+1] = "Coalition" - TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CoalitionID - TraceTable[#TraceTable+1] = "Category" - TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CategoryID - TraceTable[#TraceTable+1] = "Country" - TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CountryID - - self:E( TraceTable ) end --- @param #DATABASE self function DATABASE:GetStaticUnitTemplate( StaticName ) local StaticTemplate = self.Templates.Statics[StaticName].UnitTemplate - StaticTemplate.SpawnCoalitionID = self.Templates.Statics[StaticName].CoalitionID - StaticTemplate.SpawnCategoryID = self.Templates.Statics[StaticName].CategoryID - StaticTemplate.SpawnCountryID = self.Templates.Statics[StaticName].CountryID - return StaticTemplate + return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID end @@ -703,7 +835,7 @@ function DATABASE:_RegisterAirbases() local DCSAirbaseName = DCSAirbase:getName() - self:E( { "Register Airbase:", DCSAirbaseName } ) + self:E( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } ) self:AddAirbase( DCSAirbaseName ) end end @@ -734,7 +866,7 @@ function DATABASE:_EventOnBirth( Event ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) local PlayerName = Event.IniUnit:GetPlayerName() self:E( { "PlayerName:", PlayerName } ) - if PlayerName ~= "" then + if PlayerName then self:E( { "Player Joined:", PlayerName } ) if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) @@ -803,7 +935,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) if Event.IniUnit then if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() - if self.PLAYERS[PlayerName] then + if PlayerName and self.PLAYERS[PlayerName] then self:E( { "Player Left:", PlayerName } ) local Settings = SETTINGS:Set( PlayerName ) Settings:RemovePlayerMenu( Event.IniUnit ) @@ -988,6 +1120,31 @@ function DATABASE:OnEventDeleteCargo( EventData ) end +--- Handles the OnEventNewZone event. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA EventData +function DATABASE:OnEventNewZone( EventData ) + self:F2( { EventData } ) + + if EventData.Zone then + self:AddZone( EventData.Zone ) + end +end + + +--- Handles the OnEventDeleteZone. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA EventData +function DATABASE:OnEventDeleteZone( EventData ) + self:F2( { EventData } ) + + if EventData.Zone then + self:DeleteZone( EventData.Zone.ZoneName ) + end +end + + + --- Gets the player settings -- @param #DATABASE self -- @param #string PlayerName @@ -1025,7 +1182,6 @@ function DATABASE:_RegisterTemplates() local CoalitionSide = coalition.side[string.upper(CoalitionName)] - ---------------------------------------------- -- build nav points DB self.Navpoints[CoalitionName] = {} if coa_data.nav_points then --navpoints @@ -1040,8 +1196,9 @@ function DATABASE:_RegisterTemplates() self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y end + end end - end + ------------------------------------------------- if coa_data.country then --there is a country table for cntry_id, cntry_data in pairs(coa_data.country) do @@ -1094,11 +1251,6 @@ function DATABASE:_RegisterTemplates() end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then end --for coa_name, coa_data in pairs(mission.coalition) do - for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do - local ZoneName = ZoneData.name - self.ZONENAMES[ZoneName] = ZoneName - end - return self end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 2924fdb46..a1be00587 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1,7 +1,5 @@ --- **Core** -- EVENT models DCS **event dispatching** using a **publish-subscribe** model. -- --- ![Banner Image](..\Presentations\EVENT\Dia1.JPG) --- -- === -- -- # 1) Event Handling Overview @@ -61,8 +59,8 @@ -- So, when the DCS event occurs, the class will be notified of that event. -- There are two functions which you use to subscribe to or unsubscribe from an event. -- --- * @{Base#BASE.HandleEvent}(): Subscribe to a DCS Event. --- * @{Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. +-- * @{Core.Base#BASE.HandleEvent}(): Subscribe to a DCS Event. +-- * @{Core.Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. -- -- Note that for a UNIT, the event will be handled **for that UNIT only**! -- Note that for a GROUP, the event will be handled **for all the UNITs in that GROUP only**! @@ -74,7 +72,7 @@ -- ### 1.3.2 Event Handling of DCS Events -- -- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information +-- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information -- about the event that occurred. -- -- Find below an example of the prototype how to write an event handling function for two units: @@ -112,11 +110,11 @@ -- # 2) EVENTS type -- -- The EVENTS structure contains names for all the different DCS events that objects can subscribe to using the --- @{Base#BASE.HandleEvent}() method. +-- @{Core.Base#BASE.HandleEvent}() method. -- -- # 3) EVENTDATA type -- --- The @{Event#EVENTDATA} structure contains all the fields that are populated with event information before +-- The @{Core.Event#EVENTDATA} structure contains all the fields that are populated with event information before -- an Event Handler method is being called by the event dispatcher. -- The Event Handler received the EVENTDATA object as a parameter, and can be used to investigate further the different events. -- There are basically 4 main categories of information stored in the EVENTDATA structure: @@ -164,23 +162,28 @@ -- -- === -- --- @module Event +-- @module Core.Event +-- @image Core_Event.JPG --- The EVENT structure +-- -- @type EVENT -- @field #EVENT.Events Events -- @extends Core.Base#BASE EVENT = { ClassName = "EVENT", ClassID = 0, + MissionEnd = false, } world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000 world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001 +world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002 +world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003 --- The different types of events supported by MOOSE. --- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method. +-- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method. -- @type EVENTS EVENTS = { Shot = world.event.S_EVENT_SHOT, @@ -209,9 +212,10 @@ EVENTS = { MarkAdded = world.event.S_EVENT_MARK_ADDED, MarkChange = world.event.S_EVENT_MARK_CHANGE, MarkRemoved = world.event.S_EVENT_MARK_REMOVED, - ShootingEnd = world.event.S_EVENT_SHOOTING_END, NewCargo = world.event.S_EVENT_NEW_CARGO, DeleteCargo = world.event.S_EVENT_DELETE_CARGO, + NewZone = world.event.S_EVENT_NEW_ZONE, + DeleteZone = world.event.S_EVENT_DELETE_ZONE, } --- The Event structure @@ -223,34 +227,34 @@ EVENTS = { -- @type EVENTDATA -- @field #number id The identifier of the event. -- --- @field Dcs.DCSUnit#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{Dcs.DCSUnit#Unit} or @{Dcs.DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. +-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}. +-- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). +-- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}. -- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name. --- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Unit#UNIT} of the initiator Unit object. +-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object. -- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName). --- @field Dcs.DCSGroup#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}. +-- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}. -- @field #string IniDCSGroupName (UNIT) The initiating Group name. --- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Group#GROUP} of the initiator Group object. +-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object. -- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName). -- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side IniCoalition (UNIT) The coalition of the initiator. --- @field Dcs.DCSUnit#Unit.Category IniCategory (UNIT) The category of the initiator. +-- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator. +-- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator. -- @field #string IniTypeName (UNIT) The type name of the initiator. -- --- @field Dcs.DCSUnit#Unit target (UNIT/STATIC) The target @{Dcs.DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. +-- @field DCS#Unit target (UNIT/STATIC) The target @{DCS#Unit} or @{DCS#StaticObject}. +-- @field DCS#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ). +-- @field DCS#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCS#Unit} or @{DCS#StaticObject}. -- @field #string TgtDCSUnitName (UNIT/STATIC) The target Unit name. --- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Unit#UNIT} of the target Unit object. +-- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Wrapper.Unit#UNIT} of the target Unit object. -- @field #string TgtUnitName (UNIT/STATIC) The target UNIT name (same as TgtDCSUnitName). --- @field Dcs.DCSGroup#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}. +-- @field DCS#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}. -- @field #string TgtDCSGroupName (UNIT) The target Group name. --- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Group#GROUP} of the target Group object. +-- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Wrapper.Group#GROUP} of the target Group object. -- @field #string TgtGroupName (UNIT) The target GROUP name (same as TgtDCSGroupName). -- @field #string TgtPlayerName (UNIT) The name of the target player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side TgtCoalition (UNIT) The coalition of the target. --- @field Dcs.DCSUnit#Unit.Category TgtCategory (UNIT) The category of the target. +-- @field DCS#coalition.side TgtCoalition (UNIT) The coalition of the target. +-- @field DCS#Unit.Category TgtCategory (UNIT) The category of the target. -- @field #string TgtTypeName (UNIT) The type name of the target. -- -- @field weapon The weapon used during the event. @@ -427,6 +431,16 @@ local _EVENTMETA = { Event = "OnEventDeleteCargo", Text = "S_EVENT_DELETE_CARGO" }, + [EVENTS.NewZone] = { + Order = 1, + Event = "OnEventNewZone", + Text = "S_EVENT_NEW_ZONE" + }, + [EVENTS.DeleteZone] = { + Order = 1, + Event = "OnEventDeleteZone", + Text = "S_EVENT_DELETE_ZONE" + }, } @@ -444,7 +458,7 @@ end --- Initializes the Events structure for the event -- @param #EVENT self --- @param Dcs.DCSWorld#world.event EventID +-- @param DCS#world.event EventID -- @param Core.Base#BASE EventClass -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) @@ -470,7 +484,7 @@ end --- Removes a subscription -- @param #EVENT self -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID +-- @param DCS#world.event EventID -- @return #EVENT.Events function EVENT:RemoveEvent( EventClass, EventID ) @@ -490,7 +504,7 @@ end --- Resets subscriptions -- @param #EVENT self -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID +-- @param DCS#world.event EventID -- @return #EVENT.Events function EVENT:Reset( EventObject ) --R2.1 @@ -513,7 +527,7 @@ end ---- Clears all event subscriptions for a @{Base#BASE} derived object. +--- Clears all event subscriptions for a @{Core.Base#BASE} derived object. -- @param #EVENT self -- @param Core.Base#BASE EventObject function EVENT:RemoveAll( EventObject ) @@ -731,6 +745,36 @@ do -- Event Creation world.onEvent( Event ) end + --- Creation of a New Zone Event. + -- @param #EVENT self + -- @param Core.Zone#ZONE_BASE Zone The Zone created. + function EVENT:CreateEventNewZone( Zone ) + self:F( { Zone } ) + + local Event = { + id = EVENTS.NewZone, + time = timer.getTime(), + zone = Zone, + } + + world.onEvent( Event ) + end + + --- Creation of a Zone Deletion Event. + -- @param #EVENT self + -- @param Core.Zone#ZONE_BASE Zone The Zone created. + function EVENT:CreateEventDeleteZone( Zone ) + self:F( { Zone } ) + + local Event = { + id = EVENTS.DeleteZone, + time = timer.getTime(), + zone = Zone, + } + + world.onEvent( Event ) + end + --- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event. -- @param #EVENT self -- @param Wrapper.Unit#UNIT PlayerUnit. @@ -770,8 +814,13 @@ function EVENT:onEvent( Event ) if self and self.Events and self.Events[Event.id] and + self.MissionEnd == false and ( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then + if Event.id and Event.id == EVENTS.MissionEnd then + self.MissionEnd = true + end + if Event.initiator then Event.IniObjectCategory = Event.initiator:getCategory() @@ -810,6 +859,16 @@ function EVENT:onEvent( Event ) Event.IniTypeName = Event.IniDCSUnit:getTypeName() end + if Event.IniObjectCategory == Object.Category.CARGO then + Event.IniDCSUnit = Event.initiator + Event.IniDCSUnitName = Event.IniDCSUnit:getName() + Event.IniUnitName = Event.IniDCSUnitName + Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) + Event.IniCoalition = Event.IniDCSUnit:getCoalition() + Event.IniCategory = Event.IniDCSUnit:getDesc().category + Event.IniTypeName = Event.IniDCSUnit:getTypeName() + end + if Event.IniObjectCategory == Object.Category.SCENERY then Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() @@ -874,11 +933,28 @@ function EVENT:onEvent( Event ) Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + +-- @FC: something like this should be added. +--[[ + if Event.idx then + Event.MarkID=Event.idx + Event.MarkVec3=Event.pos + Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) + Event.MarkText=Event.text + Event.MarkCoalition=Event.coalition + Event.MarkGroupID = Event.groupID + end +]] if Event.cargo then Event.Cargo = Event.cargo Event.CargoName = Event.cargo.Name end + + if Event.zone then + Event.Zone = Event.zone + Event.ZoneName = Event.zone.ZoneName + end local PriorityOrder = EventMeta.Order local PriorityBegin = PriorityOrder == -1 and 5 or 1 @@ -894,7 +970,7 @@ function EVENT:onEvent( Event ) -- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do - + --if Event.IniObjectCategory ~= Object.Category.STATIC then -- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } ) --end @@ -1043,6 +1119,16 @@ function EVENT:onEvent( Event ) end end end + + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- But we need to switch that flag off after the event handlers have been called. + if Event.id == EVENTS.DeleteCargo then + Event.Cargo.NoDestroy = nil + end else self:T( { EventMeta.Text, Event } ) end diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 0c49c79e3..a4a833da2 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -1,8 +1,6 @@ --- **Core** -- The **FSM** (**F**inite **S**tate **M**achine) class and derived **FSM\_** classes -- are design patterns allowing efficient (long-lasting) processes and workflows. -- --- ![Banner Image](..\Presentations\FSM\Dia1.JPG) --- -- === -- -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. @@ -52,7 +50,7 @@ -- -- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s. --- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Controllable}s, which are @{Group}s, @{Unit}s, @{Client}s. +-- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. -- * @{#FSM_SET}: Models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. -- @@ -64,7 +62,8 @@ -- -- === -- --- @module Fsm +-- @module Core.Fsm +-- @image Core_Finite_State_Machine.JPG do -- FSM @@ -72,9 +71,7 @@ do -- FSM -- @extends Core.Base#BASE - --- # FSM class, extends @{Base#BASE} - -- - -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. + --- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. -- -- A FSM can only be in one of a finite number of states. -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. @@ -337,7 +334,7 @@ do -- FSM --- Creates a new FSM object. -- @param #FSM self -- @return #FSM - function FSM:New( FsmT ) + function FSM:New() -- Inherits from BASE self = BASE:Inherit( self, BASE:New() ) @@ -410,7 +407,7 @@ do -- FSM return self._Transitions or {} end - --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Wrapper.Controllable} by the task. -- @param #FSM self -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. -- @param #string Event The Event name. @@ -441,6 +438,8 @@ do -- FSM -- @return #table function FSM:GetProcesses() + self:F( { Processes = self._Processes } ) + return self._Processes or {} end @@ -455,6 +454,18 @@ do -- FSM error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) end + function FSM:SetProcess( From, Event, Fsm ) + + for ProcessID, Process in pairs( self:GetProcesses() ) do + if Process.From == From and Process.Event == Event then + Process.fsm = Fsm + return true + end + end + + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) + end + --- Adds an End state. function FSM:AddEndState( State ) @@ -557,8 +568,9 @@ do -- FSM end - function FSM:_call_handler( handler, params, EventName ) + function FSM:_call_handler( step, trigger, params, EventName ) + local handler = step .. trigger local ErrorHandler = function( errmsg ) env.info( "Error in SCHEDULER function:" .. errmsg ) @@ -569,83 +581,117 @@ do -- FSM return errmsg end if self[handler] then - self:T2( "Calling " .. handler ) + self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] ) self._EventSchedules[EventName] = nil local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) return Value end end + --- @param #FSM self function FSM._handler( self, EventName, ... ) - local Can, to = self:can( EventName ) + local Can, To = self:can( EventName ) - if to == "*" then - to = self.current + if To == "*" then + To = self.current end if Can then - local from = self.current - local params = { from, EventName, to, ... } + local From = self.current + local Params = { From, EventName, To, ... } - if self.Controllable then - self:T( "FSM Transition for " .. self.Controllable.ControllableName .. " :" .. self.current .. " --> " .. EventName .. " --> " .. to ) + + if self["onleave".. From] or + self["OnLeave".. From] or + self["onbefore".. EventName] or + self["OnBefore".. EventName] or + self["onafter".. EventName] or + self["OnAfter".. EventName] or + self["onenter".. To] or + self["OnEnter".. To] + then + if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then + self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName ) + return false + else + if self:_call_handler( "OnBefore", EventName, Params, EventName ) == false then + self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName ) + return false + else + if self:_call_handler( "onleave", From, Params, EventName ) == false then + self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From ) + return false + else + if self:_call_handler( "OnLeave", From, Params, EventName ) == false then + self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From ) + return false + end + end + end + end else - self:T( "FSM Transition:" .. self.current .. " --> " .. EventName .. " --> " .. to ) - end + local ClassName = self:GetClassName() + if ClassName == "FSM" then + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To ) + end - if ( self:_call_handler("onbefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("OnBefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("onleave" .. from, params, EventName ) == false ) - or ( self:_call_handler("OnLeave" .. from, params, EventName ) == false ) then - self:T( "Cancel Transition" ) - return false + if ClassName == "FSM_TASK" then + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.TaskName ) + end + + if ClassName == "FSM_CONTROLLABLE" then + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end + + if ClassName == "FSM_PROCESS" then + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end end - self.current = to + self.current = To local execute = true - local subtable = self:_gosub( from, EventName ) + local subtable = self:_gosub( From, EventName ) for _, sub in pairs( subtable ) do --if sub.nextevent then -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) --end - self:T( "calling sub start event: " .. sub.StartEvent ) + self:T( "*** FSM *** Sub *** " .. sub.StartEvent ) sub.fsm.fsmparent = self sub.fsm.ReturnEvents = sub.ReturnEvents sub.fsm[sub.StartEvent]( sub.fsm ) execute = false end - local fsmparent, Event = self:_isendstate( to ) + local fsmparent, Event = self:_isendstate( To ) if fsmparent and Event then - self:F2( { "end state: ", fsmparent, Event } ) - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - self:_call_handler("onstatechange", params, EventName ) + self:T( "*** FSM *** End *** " .. Event ) + self:_call_handler("onenter", To, Params, EventName ) + self:_call_handler("OnEnter", To, Params, EventName ) + self:_call_handler("onafter", EventName, Params, EventName ) + self:_call_handler("OnAfter", EventName, Params, EventName ) + self:_call_handler("onstate", "change", Params, EventName ) fsmparent[Event]( fsmparent ) execute = false end if execute then + self:_call_handler("onafter", EventName, Params, EventName ) + self:_call_handler("OnAfter", EventName, Params, EventName ) + -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! --if from ~= to then - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) + self:_call_handler("onenter", To, Params, EventName ) + self:_call_handler("OnEnter", To, Params, EventName ) --end - - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - - self:_call_handler("onstatechange", params, EventName ) + + self:_call_handler("onstate", "change", Params, EventName ) end else - self:T( "Cannot execute transition." ) - self:T( { From = self.current, Event = EventName, To = to, Can = Can } ) + self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) end return nil @@ -691,17 +737,16 @@ do -- FSM function FSM:_isendstate( Current ) local FSMParent = self.fsmparent if FSMParent and self.endstates[Current] then - self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + --self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) FSMParent.current = Current local ParentFrom = FSMParent.current - self:T( ParentFrom ) - self:T( self.ReturnEvents ) + --self:T( { ParentFrom, self.ReturnEvents } ) local Event = self.ReturnEvents[Current] - self:T( { ParentFrom, Event, self.ReturnEvents } ) + --self:T( { Event } ) if Event then return FSMParent, Event else - self:T( { "Could not find parent event name for state ", ParentFrom } ) + --self:T( { "Could not find parent event name for state ", ParentFrom } ) end end @@ -724,6 +769,10 @@ do -- FSM return self.current end + function FSM:GetCurrentState() + return self.current + end + function FSM:Is( State ) return self.current == State @@ -752,9 +801,7 @@ do -- FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM - --- # FSM_CONTROLLABLE, extends @{#FSM} - -- - -- FSM_CONTROLLABLE class models Finite State Machines for @{Controllable}s, which are @{Group}s, @{Unit}s, @{Client}s. + --- Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. -- -- === -- @@ -769,10 +816,10 @@ do -- FSM_CONTROLLABLE -- @param #table FSMT Finite State Machine Table -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @return #FSM_CONTROLLABLE - function FSM_CONTROLLABLE:New( FSMT, Controllable ) + function FSM_CONTROLLABLE:New( Controllable ) -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE if Controllable then self:SetControllable( Controllable ) @@ -855,7 +902,9 @@ do -- FSM_CONTROLLABLE return self.Controllable end - function FSM_CONTROLLABLE:_call_handler( handler, params, EventName ) + function FSM_CONTROLLABLE:_call_handler( step, trigger, params, EventName ) + + local handler = step .. trigger local ErrorHandler = function( errmsg ) @@ -868,7 +917,7 @@ do -- FSM_CONTROLLABLE end if self[handler] then - self:F3( "Calling " .. handler ) + self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() ) self._EventSchedules[EventName] = nil local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) return Value @@ -885,9 +934,7 @@ do -- FSM_PROCESS -- @extends Core.Fsm#FSM_CONTROLLABLE - --- # FSM_PROCESS, extends @{#FSM} - -- - -- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s. + --- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s. -- -- === -- @@ -905,9 +952,9 @@ do -- FSM_PROCESS local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS --self:F( Controllable ) - + self:Assign( Controllable, Task ) - + return self end @@ -915,7 +962,9 @@ do -- FSM_PROCESS self:T( "No Initialisation" ) end - function FSM_PROCESS:_call_handler( handler, params, EventName ) + function FSM_PROCESS:_call_handler( step, trigger, params, EventName ) + + local handler = step .. trigger local ErrorHandler = function( errmsg ) @@ -928,9 +977,13 @@ do -- FSM_PROCESS end if self[handler] then - self:F3( "Calling " .. handler ) + if handler ~= "onstatechange" then + self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable:GetName() ) + end self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) + if self.Controllable and self.Controllable:IsAlive() == true then + local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) + end return Value --return self[handler]( self, self.Controllable, unpack( params ) ) end @@ -946,7 +999,7 @@ do -- FSM_PROCESS local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS NewFsm:Assign( Controllable, Task ) - + -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS NewFsm:Init( self ) @@ -1037,26 +1090,26 @@ do -- FSM_PROCESS -- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. --- Send a message of the @{Task} to the Group of the Unit. --- @param #FSM_PROCESS self -function FSM_PROCESS:Message( Message ) - self:F( { Message = Message } ) - - local CC = self:GetCommandCenter() - local TaskGroup = self.Controllable:GetGroup() + -- @param #FSM_PROCESS self + function FSM_PROCESS:Message( Message ) + self:F( { Message = Message } ) - local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit - PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. - local Callsign = self.Controllable:GetCallsign() - local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - - Message = Prefix .. ": " .. Message - CC:MessageToGroup( Message, TaskGroup ) -end + local CC = self:GetCommandCenter() + local TaskGroup = self.Controllable:GetGroup() + + local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit + PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. + local Callsign = self.Controllable:GetCallsign() + local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" + + Message = Prefix .. ": " .. Message + CC:MessageToGroup( Message, TaskGroup ) + end - --- Assign the process to a @{Unit} and activate the process. + --- Assign the process to a @{Wrapper.Unit} and activate the process. -- @param #FSM_PROCESS self -- @param Task.Tasking#TASK Task -- @param Wrapper.Unit#UNIT ProcessUnit @@ -1072,14 +1125,16 @@ end return self end - function FSM_PROCESS:onenterAssigned( ProcessUnit ) - self:T( "Assign" ) +-- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) +-- +-- if From( "Planned" ) then +-- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) +-- self.Task:Assign() +-- end +-- end - self.Task:Assign() - end - - function FSM_PROCESS:onenterFailed( ProcessUnit ) - self:T( "Failed" ) + function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To ) + self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) self.Task:Fail() end @@ -1091,14 +1146,17 @@ end -- @param #string Event -- @param #string From -- @param #string To - function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To, Dummy ) - self:T( { ProcessUnit:GetName(), From, Event, To, Dummy, self:IsTrace() } ) + function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To ) - if self:IsTrace() then - --MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + if From ~= To then + self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) end - self:T( { Scores = self._Scores, To = To } ) +-- if self:IsTrace() then +-- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() +-- self:F2( { Scores = self._Scores, To = To } ) +-- end + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... if self._Scores[To] then @@ -1119,9 +1177,7 @@ do -- FSM_TASK -- @field Tasking.Task#TASK Task -- @extends #FSM - --- # FSM_TASK, extends @{#FSM} - -- - -- FSM_TASK class models Finite State Machines for @{Task}s. + --- Models Finite State Machines for @{Tasking.Task}s. -- -- === -- @@ -1133,22 +1189,23 @@ do -- FSM_TASK --- Creates a new FSM_TASK object. -- @param #FSM_TASK self - -- @param #table FSMT - -- @param Tasking.Task#TASK Task - -- @param Wrapper.Unit#UNIT TaskUnit + -- @param #string TaskName The name of the task. -- @return #FSM_TASK - function FSM_TASK:New( FSMT ) + function FSM_TASK:New( TaskName ) - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK self["onstatechange"] = self.OnStateChange + self.TaskName = TaskName return self end - function FSM_TASK:_call_handler( handler, params, EventName ) + function FSM_TASK:_call_handler( step, trigger, params, EventName ) + local handler = step .. trigger + if self[handler] then - self:T( "Calling " .. handler ) + self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName ) self._EventSchedules[EventName] = nil return self[handler]( self, unpack( params ) ) end @@ -1164,9 +1221,7 @@ do -- FSM_SET -- @extends Core.Fsm#FSM - --- # FSM_SET, extends @{#FSM} - -- - -- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here + --- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. -- -- === @@ -1210,9 +1265,10 @@ do -- FSM_SET return self.Controllable end - function FSM_SET:_call_handler( handler, params, EventName ) + function FSM_SET:_call_handler( step, trigger, params, EventName ) + local handler = step .. trigger if self[handler] then - self:T( "Calling " .. handler ) + self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] ) self._EventSchedules[EventName] = nil return self[handler]( self, self.Set, unpack( params ) ) end diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua index ff6976877..926e8eca1 100644 --- a/Moose Development/Moose/Core/Goal.lua +++ b/Moose Development/Moose/Core/Goal.lua @@ -10,7 +10,8 @@ -- -- === -- --- @module Goal +-- @module Core.Goal +-- @image Core_Goal.JPG do -- Goal @@ -18,9 +19,7 @@ do -- Goal -- @extends Core.Fsm#FSM - --- # GOAL class, extends @{Fsm#FSM} - -- - -- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. + --- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. -- -- ## 1. GOAL constructor -- diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 9bb30be21..fd677b5c5 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -13,15 +13,15 @@ -- -- ### To manage **main menus**, the classes begin with **MENU_**: -- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. +-- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. -- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: -- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. +-- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. -- -- === --- @@ -30,7 +30,8 @@ -- -- === -- --- @module Menu +-- @module Core.Menu +-- @image Core_Menu.JPG MENU_INDEX = {} @@ -182,8 +183,7 @@ do -- MENU_BASE --- @type MENU_BASE -- @extends Base#BASE - --- # MENU_BASE class, extends @{Base#BASE} - -- The MENU_BASE class defines the main MENU class where other MENU classes are derived from. + --- Defines the main MENU class where other MENU classes are derived from. -- This is an abstract class, so don't use it. -- @field #MENU_BASE MENU_BASE = { @@ -213,6 +213,7 @@ do -- MENU_BASE self.Menus = {} self.MenuCount = 0 self.MenuTime = timer.getTime() + self.MenuRemoveParent = false if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} @@ -226,14 +227,30 @@ do -- MENU_BASE if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus[MenuText] = Menu + self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 end end function MENU_BASE:ClearParentMenu( MenuText ) if self.ParentMenu and self.ParentMenu.Menus[MenuText] then self.ParentMenu.Menus[MenuText] = nil + self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 + if self.ParentMenu.MenuCount == 0 then + --self.ParentMenu:Remove() + end end end + + --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. + -- @param #MENU_BASE self + -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. + -- @return #MENU_BASE + function MENU_BASE:SetRemoveParent( RemoveParent ) + --self:F( { RemoveParent } ) + self.MenuRemoveParent = RemoveParent + return self + end + --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self @@ -269,9 +286,7 @@ do -- MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE - --- # MENU_COMMAND_BASE class, extends @{Base#BASE} - -- ---------------------------------------------------------- - -- The MENU_COMMAND_BASE class defines the main MENU class where other MENU COMMAND_ + --- Defines the main MENU class where other MENU COMMAND_ -- classes are derived from, in order to set commands. -- -- @field #MENU_COMMAND_BASE @@ -341,9 +356,8 @@ do -- MENU_MISSION --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - --- # MENU_MISSION class, extends @{Menu#MENU_BASE} + --- Manages the main menus for a complete mission. -- - -- The MENU_MISSION class manages the main menus for a complete mission. -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- @field #MENU_MISSION @@ -438,9 +452,8 @@ do -- MENU_MISSION_COMMAND --- @type MENU_MISSION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - --- # MENU_MISSION_COMMAND class, extends @{Menu#MENU_COMMAND_BASE} - -- - -- The MENU_MISSION_COMMAND class manages the command menus for a complete mission, which allow players to execute functions during mission execution. + --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. -- @@ -452,7 +465,7 @@ do -- MENU_MISSION_COMMAND --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. -- @param #MENU_MISSION_COMMAND self -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_MISSION ParentMenu The parent menu. + -- @param Core.Menu#MENU_MISSION ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_MISSION_COMMAND self @@ -525,9 +538,8 @@ do -- MENU_COALITION --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE - --- # MENU_COALITION class, extends @{Menu#MENU_BASE} + --- Manages the main menus for @{DCS.coalition}s. -- - -- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. -- @@ -576,7 +588,7 @@ do -- MENU_COALITION --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param DCS#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @return #MENU_COALITION self @@ -663,9 +675,8 @@ do -- MENU_COALITION_COMMAND --- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - --- # MENU_COALITION_COMMAND class, extends @{Menu#MENU_COMMAND_BASE} + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. -- - -- The MENU_COALITION_COMMAND class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- @@ -676,9 +687,9 @@ do -- MENU_COALITION_COMMAND --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param DCS#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_COALITION ParentMenu The parent menu. + -- @param Core.Menu#MENU_COALITION ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_COALITION_COMMAND @@ -763,9 +774,8 @@ do -- @extends Core.Menu#MENU_BASE - --- #MENU_GROUP class, extends @{Menu#MENU_BASE} + --- Manages the main menus for @{Wrapper.Group}s. -- - -- The MENU_GROUP class manages the main menus for coalitions. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- @@ -900,8 +910,8 @@ do self:RemoveSubMenus( MenuTime, MenuTag ) if not MenuTime or self.MenuTime ~= MenuTime then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then - self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -921,9 +931,7 @@ do --- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - --- # MENU_GROUP_COMMAND class, extends @{Menu#MENU_COMMAND_BASE} - -- - -- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. + --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- @@ -992,8 +1000,8 @@ do if GroupMenu == self then if not MenuTime or self.MenuTime ~= MenuTime then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then - self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -1018,9 +1026,7 @@ do -- @extends Core.Menu#MENU_BASE - --- #MENU_GROUP_DELAYED class, extends @{Menu#MENU_BASE} - -- - -- The MENU_GROUP_DELAYED class manages the main menus for groups. + --- The MENU_GROUP_DELAYED class manages the main menus for groups. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- The creation of the menu item is delayed however, and must be created using the @{#MENU_GROUP.Set} method. @@ -1133,8 +1139,8 @@ do self:RemoveSubMenus( MenuTime, MenuTag ) if not MenuTime or self.MenuTime ~= MenuTime then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then - self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -1154,9 +1160,8 @@ do --- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE - --- # MENU_GROUP_COMMAND_DELAYED class, extends @{Menu#MENU_COMMAND_BASE} + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. -- - -- The @{Menu#MENU_GROUP_COMMAND_DELAYED} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND_DELAYED.New} method, which constructs a MENU_GROUP_COMMAND_DELAYED object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND_DELAYED.Remove}. -- @@ -1244,8 +1249,8 @@ do if GroupMenu == self then if not MenuTime or self.MenuTime ~= MenuTime then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then - self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 11a1e19dc..b50f52ddb 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -1,43 +1,40 @@ --- **Core** -- MESSAGE class takes are of the **real-time notifications** and **messages to players** during a simulation. -- --- ![Banner Image](..\Presentations\MESSAGE\Dia1.JPG) --- -- === -- --- @module Message +-- @module Core.Message +-- @image Core_Message.JPG --- The MESSAGE class -- @type MESSAGE -- @extends Core.Base#BASE ---- # MESSAGE class, extends @{Base#BASE} --- --- Message System to display Messages to Clients, Coalitions or All. +--- Message System to display Messages to Clients, Coalitions or All. -- Messages are shown on the display panel for an amount of seconds, and will then disappear. -- Messages can contain a category which is indicating the category of the message. -- -- ## MESSAGE construction -- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. +-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. -- To send messages, you need to use the To functions. -- -- ## Send messages to an audience -- -- Messages are sent: -- --- * To a @{Client} using @{Message#MESSAGE.ToClient}(). --- * To a @{Group} using @{Message#MESSAGE.ToGroup}() --- * To a coalition using @{Message#MESSAGE.ToCoalition}(). --- * To the red coalition using @{Message#MESSAGE.ToRed}(). --- * To the blue coalition using @{Message#MESSAGE.ToBlue}(). --- * To all Players using @{Message#MESSAGE.ToAll}(). +-- * To a @{Client} using @{#MESSAGE.ToClient}(). +-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}() +-- * To a coalition using @{#MESSAGE.ToCoalition}(). +-- * To the red coalition using @{#MESSAGE.ToRed}(). +-- * To the blue coalition using @{#MESSAGE.ToBlue}(). +-- * To all Players using @{#MESSAGE.ToAll}(). -- -- ## Send conditionally to an audience -- -- Messages can be sent conditionally to an audience (when a condition is true): -- --- * To all players using @{Message#MESSAGE.ToAllIf}(). --- * To a coalition using @{Message#MESSAGE.ToCoalitionIf}(). +-- * To all players using @{#MESSAGE.ToAllIf}(). +-- * To a coalition using @{#MESSAGE.ToCoalitionIf}(). -- -- === -- @@ -69,6 +66,7 @@ MESSAGE.Type = { -- @param #string MessageText is the text of the Message. -- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. -- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". +-- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @return #MESSAGE -- @usage -- -- Create a series of new Messages. @@ -80,7 +78,7 @@ MESSAGE.Type = { -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) -- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) +function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText, MessageDuration, MessageCategory } ) @@ -97,6 +95,11 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) else self.MessageCategory = "" end + + self.ClearScreen=false + if ClearScreen~=nil then + self.ClearScreen=ClearScreen + end self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() @@ -117,18 +120,24 @@ end -- @param self -- @param #string MessageText is the text of the Message. -- @param #MESSAGE.Type MessageType The type of the message. +-- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) -function MESSAGE:NewType( MessageText, MessageType ) +function MESSAGE:NewType( MessageText, MessageType, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText } ) self.MessageType = MessageType + + self.ClearScreen=false + if ClearScreen~=nil then + self.ClearScreen=ClearScreen + end self.MessageTime = timer.getTime() self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) @@ -138,6 +147,15 @@ end +--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. +-- @param #MESSAGE self +-- @return #MESSAGE +function MESSAGE:Clear() + self:F() + self.ClearScreen=true + return self +end + --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". @@ -173,7 +191,7 @@ function MESSAGE:ToClient( Client, Settings ) if self.MessageDuration ~= 0 then local ClientGroupID = Client:GetClientGroupID() self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) end end @@ -197,7 +215,7 @@ function MESSAGE:ToGroup( Group, Settings ) if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end @@ -265,7 +283,7 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings ) if CoalitionSide then if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end @@ -308,7 +326,7 @@ function MESSAGE:ToAll() if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end return self diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b5556abea..b7688ad82 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1,7 +1,5 @@ --- **Core** -- **POINT\_VEC** classes define an **extensive API** to **manage 3D points** in the simulation space. -- --- ![Banner Image](..\Presentations\POINT\Dia1.JPG) --- -- === -- -- # Demo Missions @@ -26,8 +24,8 @@ -- -- ### Contributions: -- --- @module Point - +-- @module Core.Point +-- @image Core_Coordinate.JPG @@ -38,17 +36,15 @@ do -- COORDINATE -- @extends Core.Base#BASE - --- # COORDINATE class, extends @{Base#BASE} - -- - -- COORDINATE defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- ## COORDINATE constructor -- -- A new COORDINATE object can be created with: -- -- * @{#COORDINATE.New}(): a 3D point. - -- * @{#COORDINATE.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}. - -- * @{#COORDINATE.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}. + -- * @{#COORDINATE.NewFromVec2}(): a 2D point created from a @{DCS#Vec2}. + -- * @{#COORDINATE.NewFromVec3}(): a 3D point created from a @{DCS#Vec3}. -- -- ## Create waypoints for routes -- @@ -57,7 +53,7 @@ do -- COORDINATE -- * @{#COORDINATE.WaypointAir}(): Build an air route point. -- * @{#COORDINATE.WaypointGround}(): Build a ground route point. -- - -- Route points can be used in the Route methods of the @{Group#GROUP} class. + -- Route points can be used in the Route methods of the @{Wrapper.Group#GROUP} class. -- -- -- ## Smoke, flare, explode, illuminate @@ -178,9 +174,9 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. + -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. + -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. -- @return #COORDINATE function COORDINATE:New( x, y, z ) @@ -192,10 +188,24 @@ do -- COORDINATE return self end + --- COORDINATE constructor. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate. + -- @return #COORDINATE + function COORDINATE:NewFromCoordinate( Coordinate ) + + local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE + self.x = Coordinate.x + self.y = Coordinate.y + self.z = Coordinate.z + + return self + end + --- Create a new COORDINATE object from Vec2 coordinates. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. - -- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @param DCS#Vec2 Vec2 The Vec2 point. + -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return #COORDINATE function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) @@ -214,7 +224,7 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec3 coordinates. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. + -- @param DCS#Vec3 Vec3 The Vec3 point. -- @return #COORDINATE function COORDINATE:NewFromVec3( Vec3 ) @@ -228,7 +238,7 @@ do -- COORDINATE --- Return the coordinates of the COORDINATE in Vec3 format. -- @param #COORDINATE self - -- @return Dcs.DCSTypes#Vec3 The Vec3 format coordinate. + -- @return DCS#Vec3 The Vec3 format coordinate. function COORDINATE:GetVec3() return { x = self.x, y = self.y, z = self.z } end @@ -236,30 +246,69 @@ do -- COORDINATE --- Return the coordinates of the COORDINATE in Vec2 format. -- @param #COORDINATE self - -- @return Dcs.DCSTypes#Vec2 The Vec2 format coordinate. + -- @return DCS#Vec2 The Vec2 format coordinate. function COORDINATE:GetVec2() return { x = self.x, y = self.z } end - --TODO: check this to replace - --- Calculate the distance from a reference @{DCSTypes#Vec2}. + --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. - -- @return Dcs.DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. - function COORDINATE:DistanceFromVec2( Vec2Reference ) - self:F2( Vec2Reference ) + -- @param #number latitude Latitude in decimal degrees. + -- @param #number longitude Longitude in decimal degrees. + -- @param #number altitude (Optional) Altitude in meters. Default is the land height at the coordinate. + -- @return #COORDINATE + function COORDINATE:NewFromLLDD( latitude, longitude, altitude) + + -- Returns a point from latitude and longitude in the vec3 format. + local vec3=coord.LLtoLO(latitude, longitude) + + -- Convert vec3 to coordinate object. + local _coord=self:NewFromVec3(vec3) + + -- Adjust height + if altitude==nil then + _coord.y=altitude + end - local Distance = ( ( Vec2Reference.x - self.x ) ^ 2 + ( Vec2Reference.y - self.z ) ^2 ) ^0.5 + return _coord + end + + + --- Returns if the 2 coordinates are at the same 2D position. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate + -- @param #number Precision + -- @return #boolean true if at the same position. + function COORDINATE:IsAtCoordinate2D( Coordinate, Precision ) + + self:F( { Coordinate = Coordinate:GetVec2() } ) + self:F( { self = self:GetVec2() } ) + + local x = Coordinate.x + local z = Coordinate.z + + return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z + end + + + + --- Calculate the distance from a reference @{#COORDINATE}. + -- @param #COORDINATE self + -- @param #COORDINATE PointVec2Reference The reference @{#COORDINATE}. + -- @return DCS#Distance The distance from the reference @{#COORDINATE} in meters. + function COORDINATE:DistanceFromPointVec2( PointVec2Reference ) + self:F2( PointVec2Reference ) + + local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 self:T2( Distance ) return Distance end - --- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. - -- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. + -- @param DCS#Distance Distance The Distance to be added in meters. + -- @param DCS#Angle Angle The Angle in degrees. -- @return #COORDINATE The new calculated COORDINATE. function COORDINATE:Translate( Distance, Angle ) local SX = self.x @@ -273,9 +322,9 @@ do -- COORDINATE --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Distance OuterRadius - -- @param Dcs.DCSTypes#Distance InnerRadius - -- @return Dcs.DCSTypes#Vec2 Vec2 + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return DCS#Vec2 Vec2 function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -303,11 +352,23 @@ do -- COORDINATE end + --- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. + -- @param #COORDINATE self + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return #COORDINATE + function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + end + + --- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Distance OuterRadius - -- @param Dcs.DCSTypes#Distance InnerRadius - -- @return Dcs.DCSTypes#Vec3 Vec3 + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return DCS#Vec3 Vec3 function COORDINATE:GetRandomVec3InRadius( OuterRadius, InnerRadius ) local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) @@ -370,7 +431,7 @@ do -- COORDINATE --- Return a direction vector Vec3 from COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE TargetCoordinate The target COORDINATE. - -- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. + -- @return DCS#Vec3 DirectionVec3 The direction vector in Vec3 format. function COORDINATE:GetDirectionVec3( TargetCoordinate ) return { x = TargetCoordinate.x - self.x, y = TargetCoordinate.y - self.y, z = TargetCoordinate.z - self.z } end @@ -389,7 +450,7 @@ do -- COORDINATE --- Return an angle in radians from the COORDINATE using a direction vector in Vec3 format. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. + -- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The angle in radians. function COORDINATE:GetAngleRadians( DirectionVec3 ) local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) @@ -402,7 +463,7 @@ do -- COORDINATE --- Return an angle in degrees from the COORDINATE using a direction vector in Vec3 format. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. + -- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The angle in degrees. function COORDINATE:GetAngleDegrees( DirectionVec3 ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) @@ -414,7 +475,7 @@ do -- COORDINATE --- Return the 2D distance in meters between the target COORDINATE and the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE TargetCoordinate The target COORDINATE. - -- @return Dcs.DCSTypes#Distance Distance The distance in meters. + -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance( TargetCoordinate ) local TargetVec3 = TargetCoordinate:GetVec3() local SourceVec3 = self:GetVec3() @@ -426,8 +487,8 @@ do -- COORDINATE -- @param height (Optional) parameter specifying the height ASL. -- @return Temperature in Degrees Celsius. function COORDINATE:GetTemperature(height) + self:F2(height) local y=height or self.y - env.info("FF height = "..y) local point={x=self.x, y=height or self.y, z=self.z} -- get temperature [K] and pressure [Pa] at point local T,P=atmosphere.getTemperatureAndPressure(point) @@ -578,7 +639,7 @@ do -- COORDINATE --- Return the 3D distance in meters between the target COORDINATE and the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE TargetCoordinate The target COORDINATE. - -- @return Dcs.DCSTypes#Distance Distance The distance in meters. + -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get3DDistance( TargetCoordinate ) local TargetVec3 = TargetCoordinate:GetVec3() local SourceVec3 = self:GetVec3() @@ -714,8 +775,8 @@ do -- COORDINATE --- Add a Distance in meters from the COORDINATE horizontal plane, with the given angle, and calculate the new COORDINATE. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. - -- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. + -- @param DCS#Distance Distance The Distance to be added in meters. + -- @param DCS#Angle Angle The Angle in degrees. -- @return #COORDINATE The new calculated COORDINATE. function COORDINATE:Translate( Distance, Angle ) local SX = self.x @@ -734,7 +795,7 @@ do -- COORDINATE -- @param #COORDINATE.WaypointAltType AltType The altitude type. -- @param #COORDINATE.WaypointType Type The route point type. -- @param #COORDINATE.WaypointAction Action The route point action. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. Default is 500 km/h. -- @param #boolean SpeedLocked true means the speed is locked. -- @return #table The route point. function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked ) @@ -777,7 +838,7 @@ do -- COORDINATE --- Build a Waypoint Air "Turning Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. function COORDINATE:WaypointAirTurningPoint( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed ) @@ -787,7 +848,7 @@ do -- COORDINATE --- Build a Waypoint Air "Fly Over Point". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. function COORDINATE:WaypointAirFlyOverPoint( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, Speed ) @@ -797,7 +858,7 @@ do -- COORDINATE --- Build a Waypoint Air "Take Off Parking Hot". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. function COORDINATE:WaypointAirTakeOffParkingHot( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParkingHot, COORDINATE.WaypointAction.FromParkingAreaHot, Speed ) @@ -807,7 +868,7 @@ do -- COORDINATE --- Build a Waypoint Air "Take Off Parking". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. function COORDINATE:WaypointAirTakeOffParking( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, Speed ) @@ -817,7 +878,7 @@ do -- COORDINATE --- Build a Waypoint Air "Take Off Runway". -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. function COORDINATE:WaypointAirTakeOffRunway( AltType, Speed ) return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOff, COORDINATE.WaypointAction.FromRunway, Speed ) @@ -826,7 +887,7 @@ do -- COORDINATE --- Build a Waypoint Air "Landing". -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. + -- @param DCS#Speed Speed Airspeed in km/h. -- @return #table The route point. -- @usage -- @@ -844,17 +905,19 @@ do -- COORDINATE --- Build an ground type route point. -- @param #COORDINATE self - -- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h. + -- @param #number Speed (optional) Speed in km/h. The default speed is 20 km/h. -- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right". -- @return #table The route point. function COORDINATE:WaypointGround( Speed, Formation ) self:F2( { Formation, Speed } ) + local RoutePoint = {} RoutePoint.x = self.x RoutePoint.y = self.z RoutePoint.action = Formation or "" + --RoutePoint.formation_template = Formation and "" or nil RoutePoint.speed = ( Speed or 20 ) / 3.6 @@ -890,21 +953,41 @@ do -- COORDINATE return COORDINATE:NewFromVec2(vec2) end - --- Returns a table of coordinates to a destination. + --- Returns a table of coordinates to a destination using only roads. + -- The first point is the closest point on road of the given coordinate. The last point is the closest point on road of the ToCoord. Hence, the coordinate itself and the final ToCoord are not necessarily included in the path. -- @param #COORDINATE self -- @param #COORDINATE ToCoord Coordinate of destination. - -- @return #table Table of coordinates on road. + -- @return #table Table of coordinates on road. If no path on road can be found, nil is returned. function COORDINATE:GetPathOnRoad(ToCoord) - local Path={} + + -- DCS API function returning a table of vec2. local path = land.findPathOnRoads("roads", self.x, self.z, ToCoord.x, ToCoord.z) - for i, v in ipairs(path) do - --self:E(v) - local coord=COORDINATE:NewFromVec2(v) - Path[#Path+1]=COORDINATE:NewFromVec2(v) + + local Path={} + + if path then + --Path[#Path+1]=self + for i, v in ipairs(path) do + Path[#Path+1]=COORDINATE:NewFromVec2(v) + end + --Path[#Path+1]=ToCoord + else + -- There are cases where no path on road can be found. + return nil end + return Path end + --- Gets the surface type at the coordinate. + -- @param #COORDINATE self + -- @return DCS#SurfaceType Surface type. + function COORDINATE:GetSurfaceType() + local vec2=self:GetVec2() + local surface=land.getSurfaceType(vec2) + return surface + end + --- Creates an explosion at the point of a certain intensity. -- @param #COORDINATE self -- @param #number ExplosionIntensity @@ -964,10 +1047,92 @@ do -- COORDINATE self:Smoke( SMOKECOLOR.Blue ) end + --- Big smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (0=small smoke and fire, 1=medium smoke and fire, 2=large smoke and fire, 3=huge smoke and fire, 4=small smoke, 5=medium smoke, 6=large smoke, 7=huge smoke). + -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. + function COORDINATE:BigSmokeAndFire( preset, density ) + self:F2( { preset=preset, density=density } ) + density=density or 0.5 + trigger.action.effectSmokeBig( self:GetVec3(), preset, density ) + end + + --- Small smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireSmall( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density) + end + + --- Medium smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireMedium( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density) + end + + --- Large smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireLarge( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density) + end + + --- Huge smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeAndFireHuge( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density) + end + + --- Small smoke at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeSmall( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density) + end + + --- Medium smoke at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeMedium( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density) + end + + --- Large smoke at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeLarge( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density) + end + + --- Huge smoke at the coordinate. + -- @param #COORDINATE self + -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + function COORDINATE:BigSmokeHuge( density ) + self:F2( { density=density } ) + density=density or 0.5 + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density) + end + --- Flares the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#FLARECOLOR FlareColor - -- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -975,7 +1140,7 @@ do -- COORDINATE --- Flare the COORDINATE White. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareWhite( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.White, Azimuth ) @@ -983,7 +1148,7 @@ do -- COORDINATE --- Flare the COORDINATE Yellow. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareYellow( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Yellow, Azimuth ) @@ -991,7 +1156,7 @@ do -- COORDINATE --- Flare the COORDINATE Green. -- @param #COORDINATE self - -- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareGreen( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Green, Azimuth ) @@ -1009,13 +1174,19 @@ do -- COORDINATE --- Mark to All -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" ) - function COORDINATE:MarkToAll( MarkText ) + function COORDINATE:MarkToAll( MarkText, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToAll( MarkID, MarkText, self:GetVec3(), ReadOnly, text) return MarkID end @@ -1023,50 +1194,66 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. -- @param Coalition + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED ) - function COORDINATE:MarkToCoalition( MarkText, Coalition ) + function COORDINATE:MarkToCoalition( MarkText, Coalition, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition, ReadOnly, text ) return MarkID end --- Mark to Red Coalition -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalitionRed( "This is a target for the red coalition" ) - function COORDINATE:MarkToCoalitionRed( MarkText ) - return self:MarkToCoalition( MarkText, coalition.side.RED ) + function COORDINATE:MarkToCoalitionRed( MarkText, ReadOnly, Text ) + return self:MarkToCoalition( MarkText, coalition.side.RED, ReadOnly, Text ) end --- Mark to Blue Coalition -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkID = TargetCoord:MarkToCoalitionBlue( "This is a target for the blue coalition" ) - function COORDINATE:MarkToCoalitionBlue( MarkText ) - return self:MarkToCoalition( MarkText, coalition.side.BLUE ) + function COORDINATE:MarkToCoalitionBlue( MarkText, ReadOnly, Text ) + return self:MarkToCoalition( MarkText, coalition.side.BLUE, ReadOnly, Text ) end --- Mark to Group -- @param #COORDINATE self -- @param #string MarkText Free format text that shows the marking clarification. - -- @param Wrapper.Group#GROUP MarkGroup The @{Group} that receives the mark. + -- @param Wrapper.Group#GROUP MarkGroup The @{Wrapper.Group} that receives the mark. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID which is a number. -- @usage -- local TargetCoord = TargetGroup:GetCoordinate() -- local MarkGroup = GROUP:FindByName( "AttackGroup" ) -- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup ) - function COORDINATE:MarkToGroup( MarkText, MarkGroup ) + function COORDINATE:MarkToGroup( MarkText, MarkGroup, ReadOnly, Text ) local MarkID = UTILS.GetMarkID() - trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), false, "" ) + if ReadOnly==nil then + ReadOnly=false + end + local text=Text or "" + trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID(), ReadOnly, text ) return MarkID end @@ -1163,7 +1350,7 @@ do -- COORDINATE --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition. + -- @param DCS#coalition.side Coalition The coalition. -- @return #string The BR text. function COORDINATE:ToStringBULLS( Coalition, Settings ) local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) ) @@ -1244,7 +1431,7 @@ do -- COORDINATE -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringFromRP( ReferenceCoord, ReferenceName, Controllable, Settings ) -- R2.2 - self:F( { ReferenceCoord = ReferenceCoord, ReferenceName = ReferenceName } ) + self:F2( { ReferenceCoord = ReferenceCoord, ReferenceName = ReferenceName } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1273,7 +1460,7 @@ do -- COORDINATE -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2G( Controllable, Settings ) -- R2.2 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1308,7 +1495,7 @@ do -- COORDINATE -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToStringA2A( Controllable, Settings ) -- R2.2 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1348,7 +1535,7 @@ do -- COORDINATE -- @return #string The coordinate Text in the configured coordinate system. function COORDINATE:ToString( Controllable, Settings, Task ) -- R2.2 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1397,7 +1584,7 @@ do -- COORDINATE -- @return #string The pressure text in the configured measurement system. function COORDINATE:ToStringPressure( Controllable, Settings ) -- R2.3 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1413,7 +1600,7 @@ do -- COORDINATE -- @return #string The wind text in the configured measurement system. function COORDINATE:ToStringWind( Controllable, Settings ) -- R2.3 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1429,7 +1616,7 @@ do -- COORDINATE -- @return #string The temperature text in the configured measurement system. function COORDINATE:ToStringTemperature( Controllable, Settings ) -- R2.3 - self:F( { Controllable = Controllable and Controllable:GetName() } ) + self:F2( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS @@ -1453,9 +1640,7 @@ do -- POINT_VEC3 -- @extends Core.Point#COORDINATE - --- # POINT_VEC3 class, extends @{Point#COORDINATE} - -- - -- POINT_VEC3 defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. + --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, @@ -1468,7 +1653,7 @@ do -- POINT_VEC3 -- A new POINT_VEC3 object can be created with: -- -- * @{#POINT_VEC3.New}(): a 3D point. - -- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}. + -- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCS#Vec3}. -- -- -- ## Manupulate the X, Y, Z coordinates of the POINT_VEC3 @@ -1532,9 +1717,9 @@ do -- POINT_VEC3 --- Create a new POINT_VEC3 object. -- @param #POINT_VEC3 self - -- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. - -- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. + -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing Upwards. + -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. -- @return Core.Point#POINT_VEC3 function POINT_VEC3:New( x, y, z ) @@ -1546,8 +1731,8 @@ do -- POINT_VEC3 --- Create a new POINT_VEC3 object from Vec2 coordinates. -- @param #POINT_VEC3 self - -- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. - -- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) Add a landheight. + -- @param DCS#Vec2 Vec2 The Vec2 point. + -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. -- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) @@ -1560,7 +1745,7 @@ do -- POINT_VEC3 --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self - -- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. + -- @param DCS#Vec3 Vec3 The Vec3 point. -- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec3( Vec3 ) @@ -1649,8 +1834,8 @@ do -- POINT_VEC3 --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self - -- @param Dcs.DCSTypes#Distance OuterRadius - -- @param Dcs.DCSTypes#Distance InnerRadius + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius -- @return #POINT_VEC3 function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) @@ -1662,20 +1847,18 @@ end do -- POINT_VEC2 --- @type POINT_VEC2 - -- @field Dcs.DCSTypes#Distance x The x coordinate in meters. - -- @field Dcs.DCSTypes#Distance y the y coordinate in meters. + -- @field DCS#Distance x The x coordinate in meters. + -- @field DCS#Distance y the y coordinate in meters. -- @extends Core.Point#COORDINATE - --- # POINT_VEC2 class, extends @{Point#COORDINATE} - -- - -- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. + --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- -- ## POINT_VEC2 constructor -- -- A new POINT_VEC2 instance can be created with: -- - -- * @{Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. - -- * @{Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}. + -- * @{Core.Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. + -- * @{Core.Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCS#Vec2}. -- -- ## Manupulate the X, Altitude, Y coordinates of the 2D point -- @@ -1700,9 +1883,9 @@ do -- POINT_VEC2 --- POINT_VEC2 constructor. -- @param #POINT_VEC2 self - -- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. + -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. -- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) @@ -1719,7 +1902,7 @@ do -- POINT_VEC2 --- Create a new POINT_VEC2 object from Vec2 coordinates. -- @param #POINT_VEC2 self - -- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. + -- @param DCS#Vec2 Vec2 The Vec2 point. -- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) @@ -1736,7 +1919,7 @@ do -- POINT_VEC2 --- Create a new POINT_VEC2 object from Vec3 coordinates. -- @param #POINT_VEC2 self - -- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. + -- @param DCS#Vec3 Vec3 The Vec3 point. -- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec3( Vec3 ) @@ -1856,8 +2039,8 @@ do -- POINT_VEC2 --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. -- @param #POINT_VEC2 self - -- @param Dcs.DCSTypes#Distance OuterRadius - -- @param Dcs.DCSTypes#Distance InnerRadius + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius -- @return #POINT_VEC2 function POINT_VEC2:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -1869,7 +2052,7 @@ do -- POINT_VEC2 --- Calculate the distance from a reference @{#POINT_VEC2}. -- @param #POINT_VEC2 self -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. - -- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. + -- @return DCS#Distance The distance from the reference @{#POINT_VEC2} in meters. function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index 1fee7ddb4..435cc7914 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -1,7 +1,5 @@ --- **Core** -- The RADIO Module is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions... -- --- ![Banner Image](..\Presentations\RADIO\Dia1.JPG) --- -- === -- -- The Radio contains 2 classes : RADIO and BEACON @@ -18,10 +16,10 @@ -- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file), -- * For simplicty sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission. -- --- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Unit#UNIT} or a @{Group#GROUP} or by any other @{Positionable#POSITIONABLE} +-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE} -- --- * If the transmitter is a @{Unit#UNIT} or a @{Group#GROUP}, DCS will set the power of the transmission automatically, --- * If the transmitter is any other @{Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped. +-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically, +-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped. -- -- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft, -- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below). @@ -32,39 +30,40 @@ -- -- ### Author: Hugues "Grey_Echo" Bousquet -- --- @module Radio +-- @module Core.Radio +-- @image Core_Radio.JPG ---- # RADIO class, extends @{Base#BASE} +--- Models the radio capabilty. -- -- ## RADIO usage -- -- There are 3 steps to a successful radio transmission. -- --- * First, you need to **"add a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function, +-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function, -- * Then, you will **set the relevant parameters** to the transmission (see below), -- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function. -- --- Methods to set relevant parameters for both a @{Unit#UNIT} or a @{Group#GROUP} or any other @{Positionable#POSITIONABLE} +-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE} -- -- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"), -- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission. -- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission. -- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead... -- --- Additional Methods to set relevant parameters if the transmiter is a @{Unit#UNIT} or a @{Group#GROUP} +-- Additional Methods to set relevant parameters if the transmiter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} -- -- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration, -- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call -- --- Additional Methods to set relevant parameters if the transmiter is any other @{Positionable#POSITIONABLE} +-- Additional Methods to set relevant parameters if the transmiter is any other @{Wrapper.Positionable#POSITIONABLE} -- -- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts -- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call -- -- What is this power thing ? -- --- * If your transmission is sent by a @{Positionable#POSITIONABLE} other than a @{Unit#UNIT} or a @{Group#GROUP}, you can set the power of the antenna, +-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna, -- * Otherwise, DCS sets it automatically, depending on what's available on your Unit, -- * If the player gets **too far** from the transmiter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**, -- * This an automated DCS calculation you have no say on, @@ -93,7 +92,7 @@ RADIO = { } --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast --- If you want to create a RADIO, you probably should use @{Positionable#POSITIONABLE.GetRadio}() instead +-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead -- @param #RADIO self -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @return #RADIO Radio @@ -262,7 +261,7 @@ end --- Create a new transmission, that is to say, populate the RADIO with relevant data -- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP, --- but it will work for any @{Positionable#POSITIONABLE}. +-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}. -- Only the RADIO and the Filename are mandatory. -- @param #RADIO self -- @param #string FileName @@ -291,7 +290,7 @@ end --- Actually Broadcast the transmission -- * The Radio has to be populated with the new transmission before broadcasting. --- * Please use RADIO setters or either @{Radio#RADIO.NewGenericTransmission} or @{Radio#RADIO.NewUnitTransmission} +-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission} -- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE -- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission() -- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command @@ -343,22 +342,20 @@ function RADIO:StopBroadcast() end ---- # BEACON class, extends @{Base#BASE} --- --- After attaching a @{#BEACON} to your @{Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. +--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. -- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. -- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is -- attach to a cargo crate, for exemple. -- -- ## AA TACAN Beacon usage -- --- This beacon only works with airborne @{Unit#UNIT} or a @{Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. +-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. -- Use @#BEACON:StopAATACAN}() to stop it. -- -- ## General Purpose Radio Beacon usage -- --- This beacon will work with any @{Positionable#POSITIONABLE}, but **it won't follow the @{Positionable#POSITIONABLE}** ! This means that you should only use it with --- @{Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. +-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with +-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. -- Use @{#BEACON:StopRadioBeacon}() to stop it. -- -- @type BEACON @@ -368,7 +365,7 @@ BEACON = { } --- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic} --- If you want to create a BEACON, you probably should use @{Positionable#POSITIONABLE.GetBeacon}() instead. +-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. -- @param #BEACON self -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @return #BEACON Beacon diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index 4b32cdaf1..c570bb39b 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -1,3 +1,17 @@ +--- **Core** -- **REPORT** class provides a handy means to create messages and reports. +-- +-- === +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- ### Contributions: +-- +-- @module Core.Report +-- @image Core_Report.JPG + + --- The REPORT class -- @type REPORT -- @extends Core.Base#BASE diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 57073a516..8173c85c8 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -22,7 +22,7 @@ -- -- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. -- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Scheduler#SCHEDULER.Schedule}() method. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. -- The Schedule() method returns the CallID that is the reference ID for each planned schedule. -- -- === @@ -30,7 +30,8 @@ -- ### Contributions: - -- ### Authors: FlightControl : Design & Programming -- --- @module ScheduleDispatcher +-- @module Core.ScheduleDispatcher +-- @image Core_Schedule_Dispatcher.JPG --- The SCHEDULEDISPATCHER structure -- @type SCHEDULEDISPATCHER diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index e576aa2aa..b8a75c5b8 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -1,7 +1,5 @@ --- **Core** -- SCHEDULER prepares and handles the **execution of functions over scheduled time (intervals)**. -- --- ![Banner Image](..\Presentations\SCHEDULER\Dia1.JPG) --- -- === -- -- SCHEDULER manages the **scheduling of functions**: @@ -39,8 +37,8 @@ -- -- === -- --- @module Scheduler - +-- @module Core.Scheduler +-- @image Core_Scheduler.JPG --- The SCHEDULER class -- @type SCHEDULER @@ -48,9 +46,7 @@ -- @extends Core.Base#BASE ---- # SCHEDULER class, extends @{Base#BASE} --- --- The SCHEDULER class creates schedule. +--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization. -- -- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**. -- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called. diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 939973fd1..78607d83d 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1,7 +1,5 @@ --- **Core** -- SET_ classes define **collections** of objects to perform **bulk actions** and logically **group** objects. -- --- ![Banner Image](..\Presentations\SET\Dia1.JPG) --- -- === -- -- SET_ classes group objects of the same type into a collection, which is either: @@ -11,10 +9,10 @@ -- -- Various types of SET_ classes are available: -- --- * @{#SET_UNIT}: Defines a colleciton of @{Unit}s filtered by filter criteria. --- * @{#SET_GROUP}: Defines a collection of @{Group}s filtered by filter criteria. +-- * @{#SET_UNIT}: Defines a colleciton of @{Wrapper.Unit}s filtered by filter criteria. +-- * @{#SET_GROUP}: Defines a collection of @{Wrapper.Group}s filtered by filter criteria. -- * @{#SET_CLIENT}: Defines a collection of @{Client}s filterd by filter criteria. --- * @{#SET_AIRBASE}: Defines a collection of @{Airbase}s filtered by filter criteria. +-- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria. -- -- These classes are derived from @{#SET_BASE}, which contains the main methods to manage SETs. -- @@ -30,7 +28,8 @@ -- -- === -- --- @module Set +-- @module Core.Set +-- @image Core_Sets.JPG --- @type SET_BASE @@ -41,20 +40,19 @@ -- @extends Core.Base#BASE ---- # 1) SET_BASE class, extends @{Base#BASE} --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. +--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. -- The default **"time interval"** is after 0.001 seconds. -- --- ## 1.1) Add or remove objects from the SET +-- ## Add or remove objects from the SET -- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. +-- Some key core functions are @{Core.Set#SET_BASE.Add} and @{Core.Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. -- --- ## 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** +-- ## Define the SET iterator **"yield interval"** and the **"time interval"** -- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. +-- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- @field #SET_BASE SET_BASE @@ -76,10 +74,35 @@ SET_BASE = { function SET_BASE:New( Database ) -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Core.Set#SET_BASE + local self = BASE:Inherit( self, FSM:New() ) -- Core.Set#SET_BASE self.Database = Database + self:SetStartState( "Started" ) + + --- Added Handler OnAfter for SET_BASE + -- @function [parent=#SET_BASE] OnAfterAdded + -- @param #SET_BASE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #string ObjectName The name of the object. + -- @param Object The object. + + + self:AddTransition( "*", "Added", "*" ) + + --- Removed Handler OnAfter for SET_BASE + -- @function [parent=#SET_BASE] OnAfterRemoved + -- @param #SET_BASE self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #string ObjectName The name of the object. + -- @param Object The object. + + self:AddTransition( "*", "Removed", "*" ) + self.YieldInterval = 10 self.TimeInterval = 0.001 @@ -93,7 +116,7 @@ function SET_BASE:New( Database ) return self end ---- Finds an @{Base#BASE} object based on the object Name. +--- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName -- @return Core.Base#BASE The Object found. @@ -145,10 +168,12 @@ function SET_BASE:GetSetObjects() -- R2.3 end ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. +--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName -function SET_BASE:Remove( ObjectName ) +-- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. +function SET_BASE:Remove( ObjectName, NoTriggerEvent ) + self:F2( { ObjectName = ObjectName } ) local Object = self.Set[ObjectName] @@ -157,33 +182,36 @@ function SET_BASE:Remove( ObjectName ) if Key == ObjectName then table.remove( self.Index, Index ) self.Set[ObjectName] = nil - self:Flush(self) break end end - + -- When NoTriggerEvent is true, then no Removed event will be triggered. + if not NoTriggerEvent then + self:Removed( ObjectName, Object ) + end end - end ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using a given ObjectName as the index. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName -- @param Core.Base#BASE Object -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:F3( { ObjectName = ObjectName, Object = Object } ) + self:F2( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then - self:Remove( ObjectName ) + self:Remove( ObjectName, true ) end self.Set[ObjectName] = Object table.insert( self.Index, ObjectName ) + + self:Added( ObjectName, Object ) end ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. -- @param #SET_BASE self -- @param Wrapper.Object#OBJECT Object -- @return Core.Base#BASE The added BASE Object. @@ -199,7 +227,7 @@ end ---- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. +--- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName -- @return Core.Base#BASE @@ -212,7 +240,7 @@ function SET_BASE:Get( ObjectName ) return Object end ---- Gets the first object from the @{Set#SET_BASE} and derived classes. +--- Gets the first object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetFirst() @@ -223,7 +251,7 @@ function SET_BASE:GetFirst() return FirstObject end ---- Gets the last object from the @{Set#SET_BASE} and derived classes. +--- Gets the last object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetLast() @@ -234,7 +262,7 @@ function SET_BASE:GetLast() return LastObject end ---- Gets a random object from the @{Set#SET_BASE} and derived classes. +--- Gets a random object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetRandom() @@ -245,7 +273,7 @@ function SET_BASE:GetRandom() end ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +--- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() @@ -313,10 +341,6 @@ function SET_BASE:_FilterStart() end end - self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) --self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) @@ -357,9 +381,9 @@ function SET_BASE:FilterStop() return self end ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. +--- Iterate the SET_BASE while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_BASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. -- @return Core.Base#BASE The closest object. function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -429,7 +453,7 @@ end -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) + self:F( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) @@ -621,27 +645,25 @@ end --- @type SET_GROUP -- @extends Core.Set#SET_BASE ---- # SET_GROUP class, extends @{Set#SET_BASE} --- --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: +--- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: -- -- * Coalitions -- * Categories -- * Countries -- * Starting with certain prefix strings. -- --- ## 1. SET_GROUP constructor +-- ## SET_GROUP constructor -- -- Create a new SET_GROUP object with the @{#SET_GROUP.New} method: -- -- * @{#SET_GROUP.New}: Creates a new SET_GROUP object. -- --- ## 2. Add or Remove GROUP(s) from SET_GROUP +-- ## Add or Remove GROUP(s) from SET_GROUP -- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. +-- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. -- --- ## 3. SET_GROUP filter criteria +-- ## SET_GROUP filter criteria -- -- You can set filter criteria to define the set of groups within the SET_GROUP. -- Filter criteria are defined by: @@ -666,9 +688,9 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. +-- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. -- --- ## 4. SET_GROUP iterators +-- ## SET_GROUP iterators -- -- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. -- The iterator methods will walk the SET_GROUP set, and call for each element within the set a function that you provide. @@ -679,6 +701,52 @@ end -- * @{#SET_GROUP.ForEachGroupPartlyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. -- +-- +-- ## SET_GROUP trigger events on the GROUP objects. +-- +-- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_GROUP. +-- +-- ### When a GROUP object crashes or is dead, the SET_GROUP will trigger a **Dead** event. +-- +-- You can handle the event using the OnBefore and OnAfter event handlers. +-- The event handlers need to have the paramters From, Event, To, GroupObject. +-- The GroupObject is the GROUP object that is dead and within the SET_GROUP, and is passed as a parameter to the event handler. +-- See the following example: +-- +-- -- Create the SetCarrier SET_GROUP collection. +-- +-- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() +-- +-- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. +-- +-- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject ) +-- self:F( { GroupObject = GroupObject:GetName() } ) +-- end +-- +-- While this is a good example, there is a catch. +-- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. +-- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. +-- See the modified example: +-- +-- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. +-- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. +-- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. +-- +-- function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones ) +-- +-- local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER +-- +-- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. +-- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. +-- +-- function SetHelicopter.OnAfterDead( SetHelicopter, From, Event, To, GroupObject ) +-- SetHelicopter:F( { GroupObject = GroupObject:GetName() } ) +-- self.PickupCargo[GroupObject] = nil -- So here I clear the PickupCargo table entry of the self object AI_CARGO_DISPATCHER. +-- self.CarrierHome[GroupObject] = nil +-- end +-- +-- end +-- -- === -- @field #SET_GROUP SET_GROUP SET_GROUP = { @@ -784,9 +852,9 @@ function SET_GROUP:FindGroup( GroupName ) return GroupFound end ---- Iterate the SET_GROUP while identifying the nearest object from a @{Point#POINT_VEC2}. +--- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_GROUP self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. -- @return Wrapper.Group#GROUP The closest group. function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -935,6 +1003,9 @@ function SET_GROUP:FilterStart() if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end @@ -947,7 +1018,7 @@ end -- @param #SET_GROUP self -- @param Core.Event#EVENTDATA Event function SET_GROUP:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) + self:F( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) @@ -1348,9 +1419,7 @@ do -- SET_UNIT --- @type SET_UNIT -- @extends Core.Set#SET_BASE - --- # 3) SET_UNIT class, extends @{Set#SET_BASE} - -- - -- Mission designers can use the SET_UNIT class to build sets of units belonging to certain: + --- Mission designers can use the SET_UNIT class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -1358,18 +1427,18 @@ do -- SET_UNIT -- * Unit types -- * Starting with certain prefix strings. -- - -- ## 3.1) SET_UNIT constructor + -- ## SET_UNIT constructor -- -- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: -- -- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. -- - -- ## 3.2) Add or Remove UNIT(s) from SET_UNIT + -- ## Add or Remove UNIT(s) from SET_UNIT -- - -- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. + -- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. -- - -- ## 3.3) SET_UNIT filter criteria + -- ## SET_UNIT filter criteria -- -- You can set filter criteria to define the set of units within the SET_UNIT. -- Filter criteria are defined by: @@ -1386,9 +1455,9 @@ do -- SET_UNIT -- -- Planned filter criteria within development are (so these are not yet available): -- - -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. + -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. -- - -- ## 3.4) SET_UNIT iterators + -- ## SET_UNIT iterators -- -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. -- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. @@ -1404,12 +1473,65 @@ do -- SET_UNIT -- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. -- - -- ## 3.5 ) SET_UNIT atomic methods + -- ## SET_UNIT atomic methods -- -- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT: -- - -- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Unit}s in the SET, delimited by a comma. + -- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by a comma. -- + -- ## SET_UNIT iterators + -- + -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. + -- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_UNIT: + -- + -- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive group it finds within the SET_UNIT. + -- * @{#SET_UNIT.ForEachUnitInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence completely in a @{Zone}, providing the UNIT object and optional parameters to the called function. + -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence not in a @{Zone}, providing the UNIT object and optional parameters to the called function. + -- + -- ## SET_UNIT trigger events on the UNIT objects. + -- + -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the UNIT objects in the SET_UNIT. + -- + -- ### When a UNIT object crashes or is dead, the SET_UNIT will trigger a **Dead** event. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. + -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The GroupObject is the UNIT object that is dead and within the SET_UNIT, and is passed as a parameter to the event handler. + -- See the following example: + -- + -- -- Create the SetCarrier SET_UNIT collection. + -- + -- local SetHelicopter = SET_UNIT:New():FilterPrefixes( "Helicopter" ):FilterStart() + -- + -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier unit is destroyed, that all internal parameters are reset. + -- + -- function SetHelicopter:OnAfterDead( From, Event, To, UnitObject ) + -- self:F( { UnitObject = UnitObject:GetName() } ) + -- end + -- + -- While this is a good example, there is a catch. + -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. + -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. + -- See the modified example: + -- + -- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. + -- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. + -- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. + -- + -- function ACLASS:New( SetCarrier, SetCargo, SetDeployZones ) + -- + -- local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER + -- + -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. + -- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. + -- + -- function SetHelicopter.OnAfterDead( SetHelicopter, From, Event, To, UnitObject ) + -- SetHelicopter:F( { UnitObject = UnitObject:GetName() } ) + -- self.array[UnitObject] = nil -- So here I clear the array table entry of the self object ACLASS. + -- end + -- + -- end -- === -- @field #SET_UNIT SET_UNIT SET_UNIT = { @@ -1643,10 +1765,15 @@ do -- SET_UNIT if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end return self end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! @@ -2059,7 +2186,7 @@ do -- SET_UNIT --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self - -- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType + -- @param DCS#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) self:F2( RadarType ) @@ -2259,10 +2386,10 @@ do -- SET_UNIT end - --- Retrieve the type names of the @{Unit}s in the SET, delimited by an optional delimiter. + --- Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by an optional delimiter. -- @param #SET_UNIT self -- @param #string Delimiter (optional) The delimiter, which is default a comma. - -- @return #string The types of the @{Unit}s delimited. + -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) Delimiter = Delimiter or ", " @@ -2290,9 +2417,7 @@ do -- SET_STATIC --- @type SET_STATIC -- @extends Core.Set#SET_BASE - --- # 3) SET_STATIC class, extends @{Set#SET_BASE} - -- - -- Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain: + --- Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain: -- -- * Coalitions -- * Categories @@ -2300,18 +2425,18 @@ do -- SET_STATIC -- * Static types -- * Starting with certain prefix strings. -- - -- ## 3.1) SET_STATIC constructor + -- ## SET_STATIC constructor -- -- Create a new SET_STATIC object with the @{#SET_STATIC.New} method: -- -- * @{#SET_STATIC.New}: Creates a new SET_STATIC object. -- - -- ## 3.2) Add or Remove STATIC(s) from SET_STATIC + -- ## Add or Remove STATIC(s) from SET_STATIC -- - -- STATICs can be added and removed using the @{Set#SET_STATIC.AddStaticsByName} and @{Set#SET_STATIC.RemoveStaticsByName} respectively. + -- STATICs can be added and removed using the @{Core.Set#SET_STATIC.AddStaticsByName} and @{Core.Set#SET_STATIC.RemoveStaticsByName} respectively. -- These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC. -- - -- ## 3.3) SET_STATIC filter criteria + -- ## SET_STATIC filter criteria -- -- You can set filter criteria to define the set of units within the SET_STATIC. -- Filter criteria are defined by: @@ -2328,9 +2453,9 @@ do -- SET_STATIC -- -- Planned filter criteria within development are (so these are not yet available): -- - -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Zone#ZONE}. + -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Core.Zone#ZONE}. -- - -- ## 3.4) SET_STATIC iterators + -- ## SET_STATIC iterators -- -- Once the filters have been defined and the SET_STATIC has been built, you can iterate the SET_STATIC with the available iterator methods. -- The iterator methods will walk the SET_STATIC set, and call for each element within the set a function that you provide. @@ -2346,7 +2471,7 @@ do -- SET_STATIC -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. -- - -- ## 3.5 ) SET_STATIC atomic methods + -- ## SET_STATIC atomic methods -- -- Various methods exist for a SET_STATIC to perform actions or calculations and retrieve results from the SET_STATIC: -- @@ -2559,6 +2684,9 @@ do -- SET_STATIC if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end return self @@ -2963,9 +3091,7 @@ end ---- # 4) SET_CLIENT class, extends @{Set#SET_BASE} --- --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: +--- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -2973,18 +3099,18 @@ end -- * Client types -- * Starting with certain prefix strings. -- --- ## 4.1) SET_CLIENT constructor +-- ## SET_CLIENT constructor -- -- Create a new SET_CLIENT object with the @{#SET_CLIENT.New} method: -- -- * @{#SET_CLIENT.New}: Creates a new SET_CLIENT object. -- --- ## 4.2) Add or Remove CLIENT(s) from SET_CLIENT +-- ## Add or Remove CLIENT(s) from SET_CLIENT -- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. +-- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. -- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. -- --- ## 4.3) SET_CLIENT filter criteria +-- ## SET_CLIENT filter criteria -- -- You can set filter criteria to define the set of clients within the SET_CLIENT. -- Filter criteria are defined by: @@ -3001,9 +3127,9 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. +-- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. -- --- ## 4.4) SET_CLIENT iterators +-- ## SET_CLIENT iterators -- -- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. -- The iterator methods will walk the SET_CLIENT set, and call for each element within the set a function that you provide. @@ -3200,6 +3326,9 @@ function SET_CLIENT:FilterStart() if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end return self @@ -3373,17 +3502,15 @@ end ---- # 4) SET_PLAYER class, extends @{Set#SET_BASE} +--- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: -- --- Mission designers can use the @{Set#SET_PLAYER} class to build sets of units belonging to alive players: --- --- ## 4.1) SET_PLAYER constructor +-- ## SET_PLAYER constructor -- -- Create a new SET_PLAYER object with the @{#SET_PLAYER.New} method: -- -- * @{#SET_PLAYER.New}: Creates a new SET_PLAYER object. -- --- ## 4.3) SET_PLAYER filter criteria +-- ## SET_PLAYER filter criteria -- -- You can set filter criteria to define the set of clients within the SET_PLAYER. -- Filter criteria are defined by: @@ -3400,9 +3527,9 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_PLAYER.FilterZones}: Builds the SET_PLAYER with the clients within a @{Zone#ZONE}. +-- * @{#SET_PLAYER.FilterZones}: Builds the SET_PLAYER with the clients within a @{Core.Zone#ZONE}. -- --- ## 4.4) SET_PLAYER iterators +-- ## SET_PLAYER iterators -- -- Once the filters have been defined and the SET_PLAYER has been built, you can iterate the SET_PLAYER with the available iterator methods. -- The iterator methods will walk the SET_PLAYER set, and call for each element within the set a function that you provide. @@ -3599,6 +3726,9 @@ function SET_PLAYER:FilterStart() if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end return self @@ -3767,24 +3897,22 @@ end --- @type SET_AIRBASE -- @extends Core.Set#SET_BASE ---- # 5) SET_AIRBASE class, extends @{Set#SET_BASE} --- --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: +--- Mission designers can use the @{Core.Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: -- -- * Coalitions -- --- ## 5.1) SET_AIRBASE constructor +-- ## SET_AIRBASE constructor -- -- Create a new SET_AIRBASE object with the @{#SET_AIRBASE.New} method: -- -- * @{#SET_AIRBASE.New}: Creates a new SET_AIRBASE object. -- --- ## 5.2) Add or Remove AIRBASEs from SET_AIRBASE +-- ## Add or Remove AIRBASEs from SET_AIRBASE -- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. +-- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. -- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. -- --- ## 5.3) SET_AIRBASE filter criteria +-- ## SET_AIRBASE filter criteria -- -- You can set filter criteria to define the set of clients within the SET_AIRBASE. -- Filter criteria are defined by: @@ -3795,7 +3923,7 @@ end -- -- * @{#SET_AIRBASE.FilterStart}: Starts the filtering of the airbases within the SET_AIRBASE. -- --- ## 5.4) SET_AIRBASE iterators +-- ## SET_AIRBASE iterators -- -- Once the filters have been defined and the SET_AIRBASE has been built, you can iterate the SET_AIRBASE with the available iterator methods. -- The iterator methods will walk the SET_AIRBASE set, and call for each airbase within the set a function that you provide. @@ -3997,10 +4125,10 @@ function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) return self end ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. +--- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. -- @param #SET_AIRBASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Wrapper.Airbase#AIRBASE The closest @{Airbase#AIRBASE}. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. +-- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -4056,9 +4184,7 @@ end --- @type SET_CARGO -- @extends Core.Set#SET_BASE ---- # (R2.1) SET_CARGO class, extends @{Set#SET_BASE} --- --- Mission designers can use the @{Set#SET_CARGO} class to build sets of cargos optionally belonging to certain: +--- Mission designers can use the @{Core.Set#SET_CARGO} class to build sets of cargos optionally belonging to certain: -- -- * Coalitions -- * Types @@ -4072,7 +4198,7 @@ end -- -- ## Add or Remove CARGOs from SET_CARGO -- --- CARGOs can be added and removed using the @{Set#SET_CARGO.AddCargosByName} and @{Set#SET_CARGO.RemoveCargosByName} respectively. +-- CARGOs can be added and removed using the @{Core.Set#SET_CARGO.AddCargosByName} and @{Core.Set#SET_CARGO.RemoveCargosByName} respectively. -- These methods take a single CARGO name or an array of CARGO names to be added or removed from SET_CARGO. -- -- ## SET_CARGO filter criteria @@ -4118,7 +4244,7 @@ SET_CARGO = { } ---- (R2.1) Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. +--- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO -- @usage @@ -4131,6 +4257,19 @@ function SET_CARGO:New() --R2.1 return self end + +--- (R2.1) Add CARGO to SET_CARGO. +-- @param Core.Set#SET_CARGO self +-- @param Cargo.Cargo#CARGO Cargo A single cargo. +-- @return self +function SET_CARGO:AddCargo( Cargo ) --R2.4 + + self:Add( Cargo:GetName(), Cargo ) + + return self +end + + --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. @@ -4257,10 +4396,9 @@ function SET_CARGO:FilterStart() --R2.1 if _DATABASE then self:_FilterStart() + self:HandleEvent( EVENTS.NewCargo ) + self:HandleEvent( EVENTS.DeleteCargo ) end - - self:HandleEvent( EVENTS.NewCargo ) - self:HandleEvent( EVENTS.DeleteCargo ) return self end @@ -4302,10 +4440,10 @@ function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 return self end ---- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo#CARGO} from a @{Point#POINT_VEC2}. +--- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#POINT_VEC2}. -- @param #SET_CARGO self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Cargo#CARGO}. --- @return Wrapper.Cargo#CARGO The closest @{Cargo#CARGO}. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. +-- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 self:F2( PointVec2 ) @@ -4313,6 +4451,71 @@ function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 return NearestCargo end +function SET_CARGO:FirstCargoWithState( State ) + + local FirstCargo = nil + + for CargoName, Cargo in pairs( self.Set ) do + if Cargo:Is( State ) then + FirstCargo = Cargo + break + end + end + + return FirstCargo +end + +function SET_CARGO:FirstCargoWithStateAndNotDeployed( State ) + + local FirstCargo = nil + + for CargoName, Cargo in pairs( self.Set ) do + if Cargo:Is( State ) and not Cargo:IsDeployed() then + FirstCargo = Cargo + break + end + end + + return FirstCargo +end + + +--- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. +-- @param #SET_CARGO self +-- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. +function SET_CARGO:FirstCargoUnLoaded() + local FirstCargo = self:FirstCargoWithState( "UnLoaded" ) + return FirstCargo +end + + +--- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. +-- @param #SET_CARGO self +-- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. +function SET_CARGO:FirstCargoUnLoadedAndNotDeployed() + local FirstCargo = self:FirstCargoWithStateAndNotDeployed( "UnLoaded" ) + return FirstCargo +end + + +--- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. +-- @param #SET_CARGO self +-- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. +function SET_CARGO:FirstCargoLoaded() + local FirstCargo = self:FirstCargoWithState( "Loaded" ) + return FirstCargo +end + + +--- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. +-- @param #SET_CARGO self +-- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. +function SET_CARGO:FirstCargoDeployed() + local FirstCargo = self:FirstCargoWithState( "Deployed" ) + return FirstCargo +end + + --- (R2.1) @@ -4336,7 +4539,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 MCargoCoalition = true end end - self:T( { "Evaluated Coalition", MCargoCoalition } ) + self:F( { "Evaluated Coalition", MCargoCoalition } ) MCargoInclude = MCargoInclude and MCargoCoalition end @@ -4348,7 +4551,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 MCargoType = true end end - self:T( { "Evaluated Type", MCargoType } ) + self:F( { "Evaluated Type", MCargoType } ) MCargoInclude = MCargoInclude and MCargoType end @@ -4360,7 +4563,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 MCargoPrefix = true end end - self:T( { "Evaluated Prefix", MCargoPrefix } ) + self:F( { "Evaluated Prefix", MCargoPrefix } ) MCargoInclude = MCargoInclude and MCargoPrefix end end @@ -4374,6 +4577,8 @@ end -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 + self:F( { "New Cargo", EventData } ) + if EventData.Cargo then if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then self:Add( EventData.Cargo.Name , EventData.Cargo ) @@ -4390,8 +4595,307 @@ function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - self:Remove( Cargo.Name ) + + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { CargoNoDestroy=Cargo.NoDestroy } ) + if Cargo.NoDestroy then + else + self:Remove( Cargo.Name ) + end end end end + + +--- @type SET_ZONE +-- @extends Core.Set#SET_BASE + +--- Mission designers can use the @{Core.Set#SET_ZONE} class to build sets of zones of various types. +-- +-- ## SET_ZONE constructor +-- +-- Create a new SET_ZONE object with the @{#SET_ZONE.New} method: +-- +-- * @{#SET_ZONE.New}: Creates a new SET_ZONE object. +-- +-- ## Add or Remove ZONEs from SET_ZONE +-- +-- ZONEs can be added and removed using the @{Core.Set#SET_ZONE.AddZonesByName} and @{Core.Set#SET_ZONE.RemoveZonesByName} respectively. +-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_ZONE. +-- +-- ## SET_ZONE filter criteria +-- +-- You can set filter criteria to build the collection of zones in SET_ZONE. +-- Filter criteria are defined by: +-- +-- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix. +-- +-- Once the filter criteria have been set for the SET_ZONE, you can start filtering using: +-- +-- * @{#SET_ZONE.FilterStart}: Starts the filtering of the zones within the SET_ZONE. +-- +-- ## SET_ZONE iterators +-- +-- Once the filters have been defined and the SET_ZONE has been built, you can iterate the SET_ZONE with the available iterator methods. +-- The iterator methods will walk the SET_ZONE set, and call for each airbase within the set a function that you provide. +-- The following iterator methods are currently available within the SET_ZONE: +-- +-- * @{#SET_ZONE.ForEachZone}: Calls a function for each zone it finds within the SET_ZONE. +-- +-- === +-- @field #SET_ZONE SET_ZONE +SET_ZONE = { + ClassName = "SET_ZONE", + Zones = {}, + Filter = { + Prefixes = nil, + }, + FilterMeta = { + }, +} + + +--- Creates a new SET_ZONE object, building a set of zones. +-- @param #SET_ZONE self +-- @return #SET_ZONE self +-- @usage +-- -- Define a new SET_ZONE Object. The DatabaseSet will contain a reference to all Zones. +-- DatabaseSet = SET_ZONE:New() +function SET_ZONE:New() + -- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.ZONES ) ) + + return self +end + +--- Add ZONEs to SET_ZONE. +-- @param Core.Set#SET_ZONE self +-- @param #string AddZoneNames A single name or an array of ZONE_BASE names. +-- @return self +function SET_ZONE:AddZonesByName( AddZoneNames ) + + local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } + + for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do + self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) + end + + return self +end + +--- Remove ZONEs from SET_ZONE. +-- @param Core.Set#SET_ZONE self +-- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. +-- @return self +function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) + + local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + + for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do + self:Remove( RemoveZoneName ) + end + + return self +end + + +--- Finds a Zone based on the Zone Name. +-- @param #SET_ZONE self +-- @param #string ZoneName +-- @return Core.Zone#ZONE_BASE The found Zone. +function SET_ZONE:FindZone( ZoneName ) + + local ZoneFound = self.Set[ZoneName] + return ZoneFound +end + + +--- Get a random zone from the set. +-- @param #SET_ZONE self +-- @return Core.Zone#ZONE_BASE The random Zone. +-- @return #nil if no zone in the collection. +function SET_ZONE:GetRandomZone() + + if self:Count() ~= 0 then + + local Index = self.Index + local ZoneFound = nil -- Core.Zone#ZONE_BASE + + -- Loop until a zone has been found. + -- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected. + -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! + while not ZoneFound do + local ZoneRandom = math.random( 1, #Index ) + ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() + end + + return ZoneFound + end + + return nil +end + + +--- Set a zone probability. +-- @param #SET_ZONE self +-- @param #string ZoneName The name of the zone. +function SET_ZONE:SetZoneProbability( ZoneName, ZoneProbability ) + local Zone = self:FindZone( ZoneName ) + Zone:SetZoneProbability( ZoneProbability ) +end + + + + +--- Builds a set of zones of defined zone prefixes. +-- All the zones starting with the given prefixes will be included within the set. +-- @param #SET_ZONE self +-- @param #string Prefixes The prefix of which the zone name starts with. +-- @return #SET_ZONE self +function SET_ZONE:FilterPrefixes( Prefixes ) + if not self.Filter.Prefixes then + self.Filter.Prefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.Prefixes[Prefix] = Prefix + end + return self +end + + +--- Starts the filtering. +-- @param #SET_ZONE self +-- @return #SET_ZONE self +function SET_ZONE:FilterStart() + + if _DATABASE then + + -- We initialize the first set. + for ObjectName, Object in pairs( self.Database ) do + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + else + self:RemoveZonesByName( ObjectName ) + end + end + end + + self:HandleEvent( EVENTS.NewZone ) + self:HandleEvent( EVENTS.DeleteZone ) + + return self +end + +--- Handles the Database to check on an event (birth) that the Object was added in the Database. +-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! +-- @param #SET_ZONE self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the AIRBASE +-- @return #table The AIRBASE +function SET_ZONE:AddInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Handles the Database to check on any event that Object exists in the Database. +-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! +-- @param #SET_ZONE self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the AIRBASE +-- @return #table The AIRBASE +function SET_ZONE:FindInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters. +-- @param #SET_ZONE self +-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. +-- @return #SET_ZONE self +function SET_ZONE:ForEachZone( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self:GetSet() ) + + return self +end + + +--- +-- @param #SET_ZONE self +-- @param Core.Zone#ZONE_BASE MZone +-- @return #SET_ZONE self +function SET_ZONE:IsIncludeObject( MZone ) + self:F2( MZone ) + + local MZoneInclude = true + + if MZone then + local MZoneName = MZone:GetName() + + if self.Filter.Prefixes then + local MZonePrefix = false + for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do + self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) + if string.find( MZoneName, ZonePrefix, 1 ) then + MZonePrefix = true + end + end + self:T( { "Evaluated Prefix", MZonePrefix } ) + MZoneInclude = MZoneInclude and MZonePrefix + end + end + + self:T2( MZoneInclude ) + return MZoneInclude +end + +--- Handles the OnEventNewZone event for the Set. +-- @param #SET_ZONE self +-- @param Core.Event#EVENTDATA EventData +function SET_ZONE:OnEventNewZone( EventData ) --R2.1 + + self:F( { "New Zone", EventData } ) + + if EventData.Zone then + if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then + self:Add( EventData.Zone.ZoneName , EventData.Zone ) + end + end +end + +--- Handles the OnDead or OnCrash event for alive units set. +-- @param #SET_ZONE self +-- @param Core.Event#EVENTDATA EventData +function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 + self:F3( { EventData } ) + + if EventData.Zone then + local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) + if Zone and Zone.ZoneName then + + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONEs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + if Zone.NoDestroy then + else + self:Remove( Zone.ZoneName ) + end + end + end +end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 8b8a2c601..819690607 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -1,7 +1,5 @@ --- **Core** -- Manages various settings for MOOSE classes. -- --- ![Banner Image](..\Presentations\SETTINGS\Dia1.JPG) --- -- === -- -- The documentation of the SETTINGS class can be found further in this document. @@ -16,21 +14,17 @@ -- -- * **FlightControl**: Design & Programming -- --- @module Settings +-- @module Core.Settings +-- @image Core_Settings.JPG --- @type SETTINGS -- @extends Core.Base#BASE ---- # SETTINGS class, extends @{Base#BASE} --- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. -- -- === -- --- ![Banner Image](..\Presentations\SETTINGS\Dia1.JPG) --- --- === --- -- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. -- SETTINGS can work on 2 levels: -- @@ -39,29 +33,29 @@ -- -- So, when there isn't any **Player setting** defined for a player for a specific setting, or, the player cannot be identified, the **Default setting** will be used instead. -- --- ## 1. \_SETTINGS object +-- # 1) \_SETTINGS object -- -- MOOSE defines by default a singleton object called **\_SETTINGS**. Use this object to modify all the **Default settings** for a running mission. -- For each player, MOOSE will automatically allocate also a **player settings** object, and will expose a radio menu to allow the player to adapt the settings to his own preferences. -- --- ## 2. SETTINGS Menu +-- # 2) SETTINGS Menu -- -- Settings can be adapted by the Players and by the Mission Administrator through **radio menus, which are automatically available in the mission**. -- These menus can be found **on level F10 under "Settings"**. There are two kinds of menus generated by the system. -- --- ### 2.1. Default settings menu +-- ## 2.1) Default settings menu -- -- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! -- Note that the **Default settings** will only be used when a player has not choosen its own settings. -- --- ### 2.2. Player settings menu +-- ## 2.2) Player settings menu -- -- A menu is created automatically per Player Slot (group) that allows to modify the **Player** settings. -- So, when joining a slot, a menu wil be available that allows to change the settings parameters **FOR THE PLAYER ONLY**! -- Note that when a player has not chosen a specific setting, the **Default settings** will be used. -- --- ### 2.3. Show or Hide the Player Setting menus +-- ## 2.3) Show or Hide the Player Setting menus -- -- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. @@ -75,14 +69,14 @@ -- -- But only when a player exits and reenters the slot these settings will have effect! -- -- --- ## 3. Settings +-- # 3) Settings -- -- There are different settings that are managed and applied within the MOOSE framework. -- See below a comprehensive description of each. -- --- ### 3.1. **A2G coordinates** display formatting +-- ## 3.1) **A2G coordinates** display formatting -- --- #### 3.1.1. A2G coordinates setting **types** +-- ### 3.1.1) A2G coordinates setting **types** -- -- Will customize which display format is used to indicate A2G coordinates in text as part of the Command Center communications. -- @@ -91,11 +85,11 @@ -- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. -- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- --- #### 3.1.2. A2G coordinates setting **menu** +-- ### 3.1.2) A2G coordinates setting **menu** -- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- --- #### 3.1.3. A2G coordinates setting **methods** +-- ### 3.1.3) A2G coordinates setting **methods** -- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. -- @@ -104,14 +98,14 @@ -- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- - @{#SETTINGS.SetA2G_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- --- #### 3.1.4. A2G coordinates setting - additional notes +-- ### 3.1.4) A2G coordinates setting - additional notes -- -- One additional note on BR. In a situation when a BR coordinate should be given, -- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied! -- --- ### 3.2. **A2A coordinates** formatting +-- ## 3.2) **A2A coordinates** formatting -- --- #### 3.2.1. A2A coordinates setting **types** +-- ### 3.2.1) A2A coordinates setting **types** -- -- Will customize which display format is used to indicate A2A coordinates in text as part of the Command Center communications. -- @@ -121,11 +115,11 @@ -- - A2A LL DDM: Lattitude Longitude [Decimal Degrees and Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- - A2A BULLS: [Bullseye](http://falcon4.wikidot.com/concepts:bullseye). -- --- #### 3.2.2. A2A coordinates setting **menu** +-- ### 3.2.2) A2A coordinates setting **menu** -- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- --- #### 3.2.3. A2A coordinates setting **methods** +-- ### 3.2.3) A2A coordinates setting **methods** -- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. -- @@ -135,34 +129,34 @@ -- - @{#SETTINGS.SetA2A_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- - @{#SETTINGS.SetA2A_BULLS}(): Enable the BULLSeye display formatting by default. -- --- #### 3.2.4. A2A coordinates settings - additional notes +-- ### 3.2.4) A2A coordinates settings - additional notes -- -- One additional note on BRAA. In a situation when a BRAA coordinate should be given, -- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied! -- --- ### 3.3. **Measurements** formatting +-- ## 3.3) **Measurements** formatting -- --- #### 3.3.1. Measurements setting **types** +-- ### 3.3.1) Measurements setting **types** -- -- Will customize the measurements system being used as part as part of the Command Center communications. -- -- - **Metrics** system: Applies the [Metrics system](https://en.wikipedia.org/wiki/Metric_system) ... -- - **Imperial** system: Applies the [Imperial system](https://en.wikipedia.org/wiki/Imperial_units) ... -- --- #### 3.3.2. Measurements setting **menu** +-- ### 3.3.2) Measurements setting **menu** -- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- --- #### 3.3.3. Measurements setting **methods** +-- ### 3.3.3) Measurements setting **methods** -- -- There are different methods that can be used to change the **Default settings** using the \_SETTINGS object. -- -- - @{#SETTINGS.SetMetric}(): Enable the Metric system. -- - @{#SETTINGS.SetImperial}(): Enable the Imperial system. -- --- ### 3.4. **Message** display times +-- ## 3.4) **Message** display times -- --- #### 3.4.1. Message setting **types** +-- ### 3.4.1) Message setting **types** -- -- There are various **Message Types** that will influence the duration how long a message will appear as part of the Command Center communications. -- @@ -172,7 +166,7 @@ -- - **Overview report**: Provides a short report overview, the summary of the report. -- - **Detailed report**: Provides a complete report. -- --- #### 3.4.2. Message setting **menu** +-- ### 3.4.2) Message setting **menu** -- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- @@ -181,7 +175,7 @@ -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- Note that **Update** messages can be chosen not to be displayed at all! -- --- #### 3.4.3. Message setting **methods** +-- ### 3.4.3) Message setting **methods** -- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. -- @@ -729,7 +723,7 @@ do -- SETTINGS end --- Removes the player menu from the PlayerUnit. - --- @param #SETTINGS self + -- @param #SETTINGS self -- @param Wrapper.Client#CLIENT PlayerUnit -- @return #SETTINGS self function SETTINGS:RemovePlayerMenu( PlayerUnit ) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 5342a6e92..b07ea4421 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1,7 +1,5 @@ --- **Core** -- SPAWN class dynamically spawns new groups of units in your missions. -- --- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG) --- -- === -- -- The documentation of the SPAWN class can be found further in this document. @@ -21,7 +19,8 @@ -- -- === -- --- @module Spawn +-- @module Core.Spawn +-- @image Core_Spawn.JPG --- SPAWN Class @@ -37,13 +36,8 @@ -- @extends Core.Base#BASE ---- # SPAWN class, extends @{Base#BASE} +--- Allows to spawn dynamically new @{Core.Group}s. -- --- -- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG) --- --- === --- --- The SPAWN class allows to spawn dynamically new groups. -- Each SPAWN object needs to be have related **template groups** setup in the Mission Editor (ME), -- which is a normal group with the **Late Activation** flag set. -- This template group will never be activated in your mission. @@ -67,8 +61,8 @@ -- **Limits** can be set on how many groups can be spawn in each SPAWN object, -- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits: -- --- * The maximum amount of @{Unit}s that can be **alive** at the same time... --- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit. +-- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... +-- * The maximum amount of @{Wrapper.Group}s that can be **spawned**... This is more of a **resource**-type of limit. -- -- When new groups get spawned using the **Spawn** methods, -- it will be evaluated whether any limits have been reached. @@ -82,7 +76,7 @@ -- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group, -- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!! -- --- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!! +-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! -- -- Spawned groups get **the same name** as the name of the template group. -- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. @@ -117,7 +111,7 @@ -- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: -- -- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition). --- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name. +-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Wrapper.Group} an different name. -- -- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. -- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. @@ -149,15 +143,15 @@ -- -- ### Position randomization -- --- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. --- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. +-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. +-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Wrapper.Unit}s in the @{Wrapper.Group} that is spawned within a **radius band**, given an Outer and Inner radius. -- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. -- --- ### Enable / Disable AI when spawning a new @{Group} +-- ### Enable / Disable AI when spawning a new @{Wrapper.Group} -- --- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object. +-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Wrapper.Group} object. +-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Wrapper.Group} object. +-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Wrapper.Group} object. -- -- ### Limit scheduled spawning -- @@ -165,11 +159,11 @@ -- -- ### Delay initial scheduled spawn -- --- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object. --- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object. --- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object. +-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Wrapper.Group} object. -- --- ### Repeat spawned @{Group}s upon landing +-- ### Repeat spawned @{Wrapper.Group}s upon landing -- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. @@ -186,11 +180,11 @@ -- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). -- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). -- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. --- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. +-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Wrapper.Unit}. -- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. --- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Airbase}, which can be an airdrome, ship or helipad. +-- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Wrapper.Airbase}, which can be an airdrome, ship or helipad. -- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. +-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. -- -- ### **Scheduled** spawning methods @@ -226,21 +220,21 @@ -- This models AI that has succesfully returned to their airbase, to restart their combat activities. -- Check the @{#SPAWN.InitCleanUp}() for further info. -- --- ## Catch the @{Group} Spawn Event in a callback function! +-- ## Catch the @{Wrapper.Group} Spawn Event in a callback function! -- --- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters. --- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. +-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Wrapper.Group}s are created following the spawn time interval parameters. +-- When a new @{Wrapper.Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. -- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), -- which takes a function as a parameter that you can define locally. --- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. +-- Whenever a new @{Wrapper.Group} is spawned, the given function is called, and the @{Wrapper.Group} that was just spawned, is given as a parameter. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. -- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. -- -- ## Delay the initial spawning -- --- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group} +-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Wrapper.Group} -- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to --- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that +-- activate a delay before the first @{Wrapper.Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that -- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a -- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used. -- @@ -270,7 +264,7 @@ SPAWN.Takeoff = { -- @list SpawnZone ---- Creates the main object to spawn a @{Group} defined in the DCS ME. +--- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. -- @return #SPAWN @@ -288,20 +282,20 @@ function SPAWN:New( SpawnTemplatePrefix ) self.SpawnIndex = 0 self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. + self.Grouping = nil -- No grouping self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. else @@ -334,19 +328,19 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) self.SpawnIndex = 0 self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. self.Grouping = nil self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. @@ -361,6 +355,55 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) end +--- Creates a new SPAWN instance to create new groups based on the provided template. +-- @param #SPAWN self +-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure! +-- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn. +-- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime. +-- @return #SPAWN +-- @usage +-- -- Create a new SPAWN object based on a Group Template defined from scratch. +-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) +-- @usage +-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. +-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix ) + local self = BASE:Inherit( self, BASE:New() ) + self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) + + if SpawnTemplate then + self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.SpawnTemplatePrefix = SpawnTemplatePrefix + self.SpawnAliasPrefix = SpawnAliasPrefix + self.SpawnIndex = 0 + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit. + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. + self.SpawnUnControlled = false + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. + self.Grouping = nil + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + else + error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) + end + + self:SetEventPriority( 5 ) + self.SpawnHookScheduler = SCHEDULER:New( nil ) + + return self +end + + --- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. -- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... @@ -395,16 +438,82 @@ end -- and any spaces before and after the resulting name are removed. -- IMPORTANT! This method MUST be the first used after :New !!! -- @param #SPAWN self +-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names. -- @return #SPAWN self -function SPAWN:InitKeepUnitNames() +function SPAWN:InitKeepUnitNames( KeepUnitNames ) self:F( ) - self.SpawnInitKeepUnitNames = true + self.SpawnInitKeepUnitNames = KeepUnitNames or true return self end +--- Flags that the spawned groups must be spawned late activated. +-- @param #SPAWN self +-- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. +-- @return #SPAWN self +function SPAWN:InitLateActivated( LateActivated ) + self:F( ) + + self.LateActivated = LateActivated or true + + return self +end + + +--- Defines the Heading for the new spawned units. +-- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. +-- @param #SPAWN self +-- @param #number HeadingMin The minimum or fixed heading in degrees. +-- @param #number HeadingMax (optional) The maximum heading in degrees. This there is no maximum heading, then the heading will be fixed for all units using minimum heading. +-- @return #SPAWN self +-- @usage +-- +-- Spawn = SPAWN:New( ... ) +-- +-- -- Spawn the units pointing to 100 degrees. +-- Spawn:InitHeading( 100 ) +-- +-- -- Spawn the units pointing between 100 and 150 degrees. +-- Spawn:InitHeading( 100, 150 ) +-- +function SPAWN:InitHeading( HeadingMin, HeadingMax ) + self:F( ) + + self.SpawnInitHeadingMin = HeadingMin + self.SpawnInitHeadingMax = HeadingMax + + return self +end + + +function SPAWN:InitCoalition( Coalition ) + self:F( ) + + self.SpawnInitCoalition = Coalition + + return self +end + + +function SPAWN:InitCountry( Country ) + self:F( ) + + self.SpawnInitCountry = Country + + return self +end + + +function SPAWN:InitCategory( Category ) + self:F( ) + + self.SpawnInitCategory = Category + + return self +end + --- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. -- @param #SPAWN self -- @param #number SpawnStartPoint is the waypoint where the randomization begins. @@ -436,11 +545,11 @@ function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, return self end ---- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. +--- Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. +-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. +-- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. +-- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius ) self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) @@ -460,8 +569,8 @@ end --- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. -- @param #SPAWN self -- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. +-- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. +-- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN -- @usage -- -- NATO helicopters engaging in the battle field. @@ -789,7 +898,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) end do -- AI methods - --- Turns the AI On or Off for the @{Group} when spawning. + --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. -- @return #SPAWN The SPAWN object @@ -799,7 +908,7 @@ do -- AI methods return self end - --- Turns the AI On for the @{Group} when spawning. + --- Turns the AI On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOn() @@ -807,7 +916,7 @@ do -- AI methods return self:InitAIOnOff( true ) end - --- Turns the AI Off for the @{Group} when spawning. + --- Turns the AI Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOff() @@ -818,8 +927,8 @@ do -- AI methods end -- AI methods do -- Delay methods - --- Turns the Delay On or Off for the first @{Group} scheduled spawning. - -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}. + --- Turns the Delay On or Off for the first @{Wrapper.Group} scheduled spawning. + -- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Wrapper.Group}. -- @param #SPAWN self -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off. -- @return #SPAWN The SPAWN object @@ -829,7 +938,7 @@ do -- Delay methods return self end - --- Turns the Delay On for the @{Group} when spawning. + --- Turns the Delay On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOn() @@ -837,7 +946,7 @@ do -- Delay methods return self:InitDelayOnOff( true ) end - --- Turns the Delay Off for the @{Group} when spawning. + --- Turns the Delay Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOff() @@ -941,6 +1050,18 @@ function SPAWN:SpawnWithIndex( SpawnIndex ) end end + -- If Heading is given, point all the units towards the given Heading. + if self.SpawnInitHeadingMin then + for UnitID = 1, #SpawnTemplate.units do + SpawnTemplate.units[UnitID].heading = self.SpawnInitHeadingMax and math.random( self.SpawnInitHeadingMin, self.SpawnInitHeadingMax ) or self.SpawnInitHeadingMin + end + end + + SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID + SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID + SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID + + if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then if SpawnTemplate.route.points[1].type == "TakeOffParking" then SpawnTemplate.uncontrolled = self.SpawnUnControlled @@ -1045,7 +1166,7 @@ end --- Allows to place a CallFunction hook when a new group spawns. -- The provided method will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. +-- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. -- @param #SPAWN self -- @param #function SpawnCallBackFunction The function to be called when a group spawns. -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. @@ -1073,28 +1194,28 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) return self end ---- Will spawn a group at an @{Airbase}. +--- Will spawn a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate spawning units at an airbase. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- --- The @{Airbase#AIRBASE} object must refer to a valid airbase known in the sim. +-- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: -- --- * @{Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. -- --- Use the method @{Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. -- -- Ships and Farps are added within the mission, and are therefore not known. --- For these AIRBASE objects, there isn't an @{Airbase#AIRBASE} enumeration defined. --- You need to provide the **exact name** of the airbase as the parameter to the @{Airbase#AIRBASE.FindByName}() method! +-- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. +-- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! -- -- @param #SPAWN self --- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Airbase} where to spawn the group. +-- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot. -- @param #number TakeoffAltitude (optional) The altitude above the ground. -- @return Wrapper.Group#GROUP that was spawned. @@ -1224,7 +1345,7 @@ end -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. +-- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. @@ -1247,14 +1368,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) - local TemplateHeight = SpawnTemplate.route.points[1].alt + local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil + + SpawnTemplate.route = SpawnTemplate.route or {} + SpawnTemplate.route.points = SpawnTemplate.route.points or {} + SpawnTemplate.route.points[1] = SpawnTemplate.route.points[1] or {} + SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0 + SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0 -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) local UnitTemplate = SpawnTemplate.units[UnitID] - local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SX = UnitTemplate.x or 0 + local SY = UnitTemplate.y or 0 local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y local TX = Vec3.x + ( SX - BX ) @@ -1266,7 +1393,6 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) end self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) end - SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z if SpawnTemplate.CategoryID ~= Group.Category.SHIP then @@ -1283,12 +1409,53 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) return nil end + +--- Will spawn a group from a Coordinate in 3D space. +-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. +-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. +-- You can use the returned group to further define the route to be followed. +-- @param #SPAWN self +-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. +-- @return #nil Nothing was spawned. +function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + + return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) +end + + + +--- Will spawn a group from a PointVec3 in 3D space. +-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. +-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. +-- You can use the returned group to further define the route to be followed. +-- @param #SPAWN self +-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. +-- @return #nil Nothing was spawned. +-- @usage +-- +-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. +-- +-- -- Spawn at the zone center position at 2000 meters from the ground! +-- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) +-- +function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + + return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) +end + + --- Will spawn a group from a Vec2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. +-- @param DCS#Vec2 Vec2 The Vec2 coordinates where to spawn the group. -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. @@ -1317,6 +1484,35 @@ function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) end +--- Will spawn a group from a POINT_VEC2 in 3D space. +-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. +-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. +-- You can use the returned group to further define the route to be followed. +-- @param #SPAWN self +-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 coordinates where to spawn the group. +-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. +-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. +-- @return #nil Nothing was spawned. +-- @usage +-- +-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() +-- +-- -- Spawn at the zone center position at the height specified in the ME of the group template! +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) +-- +-- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) +-- +function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + + return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) +end + + + --- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -1377,12 +1573,12 @@ function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex ) end --- Will spawn a Group within a given @{Zone}. --- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}. --- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. +-- The @{Zone} can be of any type derived from @{Core.Zone#ZONE_BASE}. +-- Once the @{Wrapper.Group} is spawned within the zone, the @{Wrapper.Group} will continue on its route. -- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. -- @param #SPAWN self -- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. --- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. +-- @param #boolean RandomizeGroup (optional) Randomization of the @{Wrapper.Group} position in the zone. -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. @@ -1478,12 +1674,12 @@ function SPAWN:SpawnGroupName( SpawnIndex ) end ---- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. +--- Will find the first alive @{Wrapper.Group} it has spawned, and return the alive @{Wrapper.Group} object and the first Index where the first alive @{Wrapper.Group} object has been found. -- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. +-- @return Wrapper.Group#GROUP, #number The @{Wrapper.Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -1503,13 +1699,13 @@ function SPAWN:GetFirstAliveGroup() end ---- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. +--- Will find the next alive @{Wrapper.Group} object from a given Index, and return a reference to the alive @{Wrapper.Group} object and the next Index where the alive @{Wrapper.Group} has been found. -- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. --- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. +-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Wrapper.Group} object from the given Index. +-- @return Wrapper.Group#GROUP, #number The next alive @{Wrapper.Group} object found, the next Index where the next alive @{Wrapper.Group} object was found. +-- @return #nil, #nil When no alive @{Wrapper.Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -1529,12 +1725,12 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return nil, nil end ---- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. +--- Will find the last alive @{Wrapper.Group} object, and will return a reference to the last live @{Wrapper.Group} object and the last Index where the last alive @{Wrapper.Group} object has been found. -- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. +-- @return Wrapper.Group#GROUP, #number The last alive @{Wrapper.Group} object found, the last Index where the last alive @{Wrapper.Group} object was found. +-- @return #nil, #nil When no alive @{Wrapper.Group} object is found, #nil is returned. -- @usage --- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. +-- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() -- if GroupPlane then -- GroupPlane can be nil!!! -- -- Do actions with the GroupPlane object. @@ -1583,7 +1779,7 @@ end -- The method will search for a #-mark, and will return the text before the #-mark. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. +-- @param DCS#UNIT DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetPrefixFromGroup( SpawnGroup ) @@ -1693,7 +1889,10 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) local SpawnTemplate = nil - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) + local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template + self:F( { Template = Template } ) + + SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) if SpawnTemplate == nil then error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) @@ -1715,12 +1914,17 @@ end function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) +-- if not self.SpawnTemplate then +-- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) +-- end + + local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) + --local SpawnTemplate = self.SpawnTemplate SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) SpawnTemplate.groupId = nil --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = false + SpawnTemplate.lateActivation = self.LateActivated or false if SpawnTemplate.CategoryID == Group.Category.GROUND then self:T3( "For ground units, visible needs to be false..." ) @@ -1808,7 +2012,7 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) if self.SpawnRandomizeTemplate then self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route ) + self.SpawnGroups[SpawnIndex].SpawnTemplate.route = UTILS.DeepCopy( self.SpawnTemplate.route ) self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 92a816257..334201e4c 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -1,7 +1,5 @@ --- **Core** -- Spawn dynamically new STATICs in your missions. -- --- ![Banner Image](..\Presentations\SPAWNSTATIC\Dia1.JPG) --- -- === -- -- SPAWNSTATIC spawns static structures in your missions dynamically. See below the SPAWNSTATIC class documentation. @@ -29,7 +27,8 @@ -- -- === -- --- @module SpawnStatic +-- @module Core.SpawnStatic +-- @image Core_Spawnstatic.JPG @@ -37,9 +36,7 @@ -- @extends Core.Base#BASE ---- # SPAWNSTATIC class, extends @{Base#BASE} --- --- The SPAWNSTATIC class allows to spawn dynamically new @{Static}s. +--- Allows to spawn dynamically new @{Static}s. -- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), -- SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy" -- these properties to create a new static object and place it at the desired coordinate. @@ -81,14 +78,16 @@ SPAWNSTATIC = { -- @param #SPAWNSTATIC self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. -- @return #SPAWNSTATIC -function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1 +function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID ) --R2.1 local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC self:F( { SpawnTemplatePrefix } ) - local TemplateStatic = StaticObject.getByName( SpawnTemplatePrefix ) + local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticUnitTemplate( SpawnTemplatePrefix ) if TemplateStatic then self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.CountryID = CountryID + self.CountryID = SpawnCountryID or CountryID + self.CategoryID = CategoryID + self.CoalitionID = CoalitionID self.SpawnIndex = 0 else error( "SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) @@ -116,6 +115,7 @@ function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, return self end + --- Creates a new @{Static} at the original position. -- @param #SPAWNSTATIC self -- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. @@ -124,22 +124,28 @@ end function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3 self:F( { Heading, NewName } ) - local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID] - local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix ) - StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex ) - StaticTemplate.heading = ( Heading / 180 ) * math.pi + if StaticTemplate then - StaticTemplate.CountryID = nil - StaticTemplate.CoalitionID = nil - StaticTemplate.CategoryID = nil + local CountryID = self.CountryID + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - local Static = coalition.addStaticObject( self.CountryID, StaticTemplate ) + StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex ) + StaticTemplate.heading = ( Heading / 180 ) * math.pi + + StaticTemplate.CountryID = nil + StaticTemplate.CoalitionID = nil + StaticTemplate.CategoryID = nil + + local Static = coalition.addStaticObject( CountryID, StaticTemplate ) + + self.SpawnIndex = self.SpawnIndex + 1 - self.SpawnIndex = self.SpawnIndex + 1 - - return Static + return Static + end + + return nil end @@ -153,32 +159,101 @@ end function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1 self:F( { PointVec2, Heading, NewName } ) - local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID] + local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix ) + + if StaticTemplate then + + local CountryID = self.CountryID + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] + + StaticTemplate.x = PointVec2.x + StaticTemplate.y = PointVec2.z + + StaticTemplate.units = nil + StaticTemplate.route = nil + StaticTemplate.groupId = nil + + StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex ) + StaticTemplate.heading = ( Heading / 180 ) * math.pi + + StaticTemplate.CountryID = nil + StaticTemplate.CoalitionID = nil + StaticTemplate.CategoryID = nil + + local Static = coalition.addStaticObject( CountryID, StaticTemplate ) + + self.SpawnIndex = self.SpawnIndex + 1 + + return Static + end + + return nil +end + + +--- Creates the original @{Static} at a POINT_VEC2. +-- @param #SPAWNSTATIC self +-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. +-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. +-- @param #string (optional) The name of the new static. +-- @return #SPAWNSTATIC +function SPAWNSTATIC:ReSpawn() local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix ) - StaticTemplate.x = PointVec2.x - StaticTemplate.y = PointVec2.z + if StaticTemplate then - StaticTemplate.units = nil - StaticTemplate.route = nil - StaticTemplate.groupId = nil + local CountryID = self.CountryID + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] + StaticTemplate.units = nil + StaticTemplate.route = nil + StaticTemplate.groupId = nil + + StaticTemplate.CountryID = nil + StaticTemplate.CoalitionID = nil + StaticTemplate.CategoryID = nil + + local Static = coalition.addStaticObject( CountryID, StaticTemplate ) + + return Static + end - StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex ) - StaticTemplate.heading = ( Heading / 180 ) * math.pi - - StaticTemplate.CountryID = nil - StaticTemplate.CoalitionID = nil - StaticTemplate.CategoryID = nil - - local Static = coalition.addStaticObject( self.CountryID, StaticTemplate ) - - self.SpawnIndex = self.SpawnIndex + 1 - - return Static + return nil end + +--- Creates the original @{Static} at a POINT_VEC2. +-- @param #SPAWNSTATIC self +-- @param Core.Point#COORDINATE Coordinate The 2D coordinate where to spawn the static. +-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. +-- @return #SPAWNSTATIC +function SPAWNSTATIC:ReSpawnAt( Coordinate, Heading ) + + local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix ) + + if StaticTemplate then + + local CountryID = self.CountryID + + StaticTemplate.x = Coordinate.x + StaticTemplate.y = Coordinate.z + + StaticTemplate.heading = Heading and ( ( Heading / 180 ) * math.pi ) or StaticTemplate.heading + + StaticTemplate.CountryID = nil + StaticTemplate.CoalitionID = nil + StaticTemplate.CategoryID = nil + + local Static = coalition.addStaticObject( CountryID, StaticTemplate ) + + return Static + end + + return nil +end + + --- Creates a new @{Static} from a @{Zone}. -- @param #SPAWNSTATIC self -- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static. diff --git a/Moose Development/Moose/Core/Spot.lua b/Moose Development/Moose/Core/Spot.lua index 10edb6cee..4e17118f5 100644 --- a/Moose Development/Moose/Core/Spot.lua +++ b/Moose Development/Moose/Core/Spot.lua @@ -1,14 +1,12 @@ --- **Core** -- Management of SPOT logistics, that can be transported from and to transportation carriers. -- --- ![Banner Image](..\Presentations\SPOT\Dia1.JPG) --- -- === -- -- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to: -- -- * Spot for a defined duration. -- * wiggle the spot at the target. --- * Provide a @{Unit} as a target, instead of a point. +-- * Provide a @{Wrapper.Unit} as a target, instead of a point. -- * Implement a status machine, LaseOn, LaseOff. -- -- === @@ -38,7 +36,8 @@ -- -- === -- --- @module Spot +-- @module Core.Spot +-- @image Core_Spot.JPG do @@ -47,13 +46,11 @@ do -- @extends Core.Fsm#FSM - --- # SPOT class, extends @{Fsm#FSM} - -- - -- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to: + --- Implements the target spotting or marking functionality, but adds additional luxury to be able to: -- -- * Mark targets for a defined duration. -- * wiggle the spot at the target. - -- * Provide a @{Unit} as a target, instead of a point. + -- * Provide a @{Wrapper.Unit} as a target, instead of a point. -- * Implement a status machine, LaseOn, LaseOff. -- -- ## 1. SPOT constructor diff --git a/Moose Development/Moose/Core/UserFlag.lua b/Moose Development/Moose/Core/UserFlag.lua index 05fc7b29e..9bfa5a628 100644 --- a/Moose Development/Moose/Core/UserFlag.lua +++ b/Moose Development/Moose/Core/UserFlag.lua @@ -10,7 +10,9 @@ -- -- === -- --- @module UserFlag +-- @module Core.UserFlag +-- @image Core_Userflag.JPG +-- do -- UserFlag @@ -18,11 +20,9 @@ do -- UserFlag -- @extends Core.Base#BASE - --- # USERFLAG class, extends @{Base#BASE} + --- Management of DCS User Flags. -- - -- Management of DCS User Flags. - -- - -- ## 1. USERFLAG constructor + -- ## USERFLAG constructor -- -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. -- diff --git a/Moose Development/Moose/Core/UserSound.lua b/Moose Development/Moose/Core/UserSound.lua index 808b78a5f..a8a6328d7 100644 --- a/Moose Development/Moose/Core/UserSound.lua +++ b/Moose Development/Moose/Core/UserSound.lua @@ -10,7 +10,8 @@ -- -- === -- --- @module UserSound +-- @module Core.UserSound +-- @image Core_Usersound.JPG do -- UserSound @@ -18,11 +19,9 @@ do -- UserSound -- @extends Core.Base#BASE - --- # USERSOUND class, extends @{Base#BASE} + --- Management of DCS User Sound. -- - -- Management of DCS User Sound. - -- - -- ## 1. USERSOUND constructor + -- ## USERSOUND constructor -- -- * @{#USERSOUND.New}(): Creates a new USERSOUND object. -- @@ -80,7 +79,7 @@ do -- UserSound --- Play the usersound to the given coalition. -- @param #USERSOUND self - -- @param Dcs.DCScoalition#coalition Coalition The coalition to play the usersound to. + -- @param DCS#coalition Coalition The coalition to play the usersound to. -- @return #USERSOUND The usersound instance. -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) @@ -96,7 +95,7 @@ do -- UserSound --- Play the usersound to the given country. -- @param #USERSOUND self - -- @param Dcs.DCScountry#country Country The country to play the usersound to. + -- @param DCS#country Country The country to play the usersound to. -- @return #USERSOUND The usersound instance. -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) @@ -110,9 +109,9 @@ do -- UserSound end - --- Play the usersound to the given @{Group}. + --- Play the usersound to the given @{Wrapper.Group}. -- @param #USERSOUND self - -- @param Wrapper.Group#GROUP Group The @{Group} to play the usersound to. + -- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to. -- @return #USERSOUND The usersound instance. -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) diff --git a/Moose Development/Moose/Core/Velocity.lua b/Moose Development/Moose/Core/Velocity.lua index 88d047126..7e7a368f3 100644 --- a/Moose Development/Moose/Core/Velocity.lua +++ b/Moose Development/Moose/Core/Velocity.lua @@ -7,7 +7,8 @@ -- -- === -- --- @module Velocity +-- @module Core.Velocity +-- @image MOOSE.JPG do -- Velocity @@ -15,11 +16,9 @@ do -- Velocity -- @extends Core.Base#BASE - --- # VELOCITY class, extends @{Base#BASE} + --- VELOCITY models a speed, which can be expressed in various formats according the Settings. -- - -- VELOCITY models a speed, which can be expressed in various formats according the Settings. - -- - -- ## 1. VELOCITY constructor + -- ## VELOCITY constructor -- -- * @{#VELOCITY.New}(): Creates a new VELOCITY object. -- @@ -125,7 +124,7 @@ do -- VELOCITY_POSITIONABLE -- @extends Core.Base#BASE - --- # VELOCITY_POSITIONABLE class, extends @{Base#BASE} + --- # VELOCITY_POSITIONABLE class, extends @{Core.Base#BASE} -- -- VELOCITY_POSITIONABLE monitors the speed of an @{Positionable} in the simulation, which can be expressed in various formats according the Settings. -- diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 27741c2a3..4d86a72c9 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1,7 +1,5 @@ --- **Core** -- ZONE classes define **zones** within your mission of **various forms**, with **various capabilities**. -- --- ![Banner Image](..\Presentations\ZONE\Dia1.JPG) --- -- === -- -- There are essentially two core functions that zones accomodate: @@ -12,7 +10,7 @@ -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: -- -- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). +-- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. @@ -23,9 +21,9 @@ -- * @{#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. -- * @{#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. -- * @{#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- * @{#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. +-- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- -- === -- @@ -34,7 +32,8 @@ -- -- === -- --- @module Zone +-- @module Core.Zone +-- @image Core_Zones.JPG --- @type ZONE_BASE @@ -43,9 +42,7 @@ -- @extends Core.Base#BASE ---- # ZONE_BASE class, extends @{Base#BASE} --- --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +--- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. -- -- ## Each zone has a name: -- @@ -53,7 +50,7 @@ -- * @{#ZONE_BASE.SetName}(): Sets the name of the zone. -- -- --- ## Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: +-- ## Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: -- -- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a 2D vector is within the zone. -- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a 3D vector is within the zone. @@ -95,10 +92,10 @@ ZONE_BASE = { --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare --- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) --- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) --- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) --- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) +-- @field DCS#Distance x1 The lower x coordinate (left down) +-- @field DCS#Distance y1 The lower y coordinate (left down) +-- @field DCS#Distance x2 The higher x coordinate (right up) +-- @field DCS#Distance y2 The higher y coordinate (right up) --- ZONE_BASE constructor @@ -114,6 +111,8 @@ function ZONE_BASE:New( ZoneName ) return self end + + --- Returns the name of the zone. -- @param #ZONE_BASE self -- @return #string The name of the zone. @@ -136,7 +135,7 @@ end --- Returns if a Vec2 is within the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 to test. +-- @param DCS#Vec2 Vec2 The Vec2 to test. -- @return #boolean true if the Vec2 is within the zone. function ZONE_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -146,13 +145,19 @@ end --- Returns if a Vec3 is within the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. +-- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the Vec3 is within the zone. function ZONE_BASE:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) - local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) + return InZone +end +--- Returns if a Coordinate is within the zone. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Coordinate The coordinate to test. +-- @return #boolean true if the coordinate is within the zone. +function ZONE_BASE:IsCoordinateInZone( Coordinate ) + local InZone = self:IsVec2InZone( Coordinate:GetVec2() ) return InZone end @@ -161,10 +166,7 @@ end -- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test. -- @return #boolean true if the PointVec2 is within the zone. function ZONE_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) - local InZone = self:IsVec2InZone( PointVec2:GetVec2() ) - return InZone end @@ -173,26 +175,21 @@ end -- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test. -- @return #boolean true if the PointVec3 is within the zone. function ZONE_BASE:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) - local InZone = self:IsPointVec2InZone( PointVec3 ) - return InZone end ---- Returns the @{DCSTypes#Vec2} coordinate of the zone. +--- Returns the @{DCS#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() - self:F2( self.ZoneName ) - return nil end ---- Returns a @{Point#POINT_VEC2} of the zone. +--- Returns a @{Core.Point#POINT_VEC2} of the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. function ZONE_BASE:GetPointVec2() self:F2( self.ZoneName ) @@ -207,7 +204,7 @@ function ZONE_BASE:GetPointVec2() end ---- Returns a @{Point#COORDINATE} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate() @@ -223,10 +220,10 @@ function ZONE_BASE:GetCoordinate() end ---- Returns the @{DCSTypes#Vec3} of the zone. +--- Returns the @{DCS#Vec3} of the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The Vec3 of the zone. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) self:F2( self.ZoneName ) @@ -241,9 +238,9 @@ function ZONE_BASE:GetVec3( Height ) return Vec3 end ---- Returns a @{Point#POINT_VEC3} of the zone. +--- Returns a @{Core.Point#POINT_VEC3} of the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) self:F2( self.ZoneName ) @@ -257,9 +254,9 @@ function ZONE_BASE:GetPointVec3( Height ) return PointVec3 end ---- Returns a @{Point#COORDINATE} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 self:F2( self.ZoneName ) @@ -274,21 +271,21 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1 end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. +-- @return DCS#Vec2 The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() return nil end ---- Define a random @{Point#POINT_VEC2} within the zone. +--- Define a random @{Core.Point#POINT_VEC2} within the zone. -- @param #ZONE_BASE self -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. function ZONE_BASE:GetRandomPointVec2() return nil end ---- Define a random @{Point#POINT_VEC3} within the zone. +--- Define a random @{Core.Point#POINT_VEC3} within the zone. -- @param #ZONE_BASE self -- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. function ZONE_BASE:GetRandomPointVec3() @@ -322,7 +319,7 @@ end -- @param #ZONE_BASE self -- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:SetZoneProbability( ZoneProbability ) - self:F2( ZoneProbability ) + self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) self.ZoneProbability = ZoneProbability or 1 return self @@ -341,6 +338,27 @@ end -- @param #ZONE_BASE self -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. -- @return #nil The zone is not selected taking into account the randomization probability factor. +-- @usage +-- +-- local ZoneArray = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } +-- +-- -- We set a zone probability of 70% to the first zone and 30% to the second zone. +-- ZoneArray[1]:SetZoneProbability( 0.5 ) +-- ZoneArray[2]:SetZoneProbability( 0.5 ) +-- +-- local ZoneSelected = nil +-- +-- while ZoneSelected == nil do +-- for _, Zone in pairs( ZoneArray ) do +-- ZoneSelected = Zone:GetZoneMaybe() +-- if ZoneSelected ~= nil then +-- break +-- end +-- end +-- end +-- +-- -- The result should be that Zone1 would be more probable selected than Zone2. +-- function ZONE_BASE:GetZoneMaybe() self:F2() @@ -355,13 +373,11 @@ end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. --- @field Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @field DCS#Vec2 Vec2 The current location of the zone. +-- @field DCS#Distance Radius The radius of the zone. -- @extends #ZONE_BASE ---- # ZONE_RADIUS class, extends @{Zone#ZONE_BASE} --- --- The ZONE_RADIUS class defined by a zone name, a location and a radius. +--- The ZONE_RADIUS class defined by a zone name, a location and a radius. -- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. -- -- ## ZONE_RADIUS constructor @@ -375,17 +391,17 @@ end -- -- ## Manage the location of the zone -- --- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCSTypes#Vec3} of the zone, taking an additional height parameter. +-- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCS#Vec2} of the zone. +-- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCS#Vec2} of the zone. +-- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCS#Vec3} of the zone, taking an additional height parameter. -- -- ## Zone point randomization -- -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Point#POINT_VEC2} object representing a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. +-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. +-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- -- @field #ZONE_RADIUS ZONE_RADIUS = { @@ -395,8 +411,8 @@ ZONE_RADIUS = { --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @param DCS#Vec2 Vec2 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS @@ -410,8 +426,9 @@ end --- Bounds the zone with tires. -- @param #ZONE_RADIUS self --- @param #number Points (optional) The amount of points in the circle. --- @param #boolean UnBound If true the tyres will be destroyed. +-- @param #number Points (optional) The amount of points in the circle. Default 360. +-- @param DCS#country.id CountryID The country id of the tire objects, e.g. country.id.USA for blue or country.id.RUSSIA for red. +-- @param #boolean UnBound (Optional) If true the tyres will be destroyed. -- @return #ZONE_RADIUS self function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) @@ -490,7 +507,7 @@ end -- @param #ZONE_RADIUS self -- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. -- @param #number Points (optional) The amount of points in the circle. --- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. +-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) @@ -518,7 +535,7 @@ end --- Returns the radius of the zone. -- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Distance The radius of the zone. +-- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() self:F2( self.ZoneName ) @@ -529,8 +546,8 @@ end --- Sets the radius of the zone. -- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return Dcs.DCSTypes#Distance The radius of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) self:F2( self.ZoneName ) @@ -540,9 +557,9 @@ function ZONE_RADIUS:SetRadius( Radius ) return self.Radius end ---- Returns the @{DCSTypes#Vec2} of the zone. +--- Returns the @{DCS#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Vec2 The location of the zone. +-- @return DCS#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) @@ -551,10 +568,10 @@ function ZONE_RADIUS:GetVec2() return self.Vec2 end ---- Sets the @{DCSTypes#Vec2} of the zone. +--- Sets the @{DCS#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. --- @return Dcs.DCSTypes#Vec2 The new location of the zone. +-- @param DCS#Vec2 Vec2 The new location of the zone. +-- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) @@ -565,10 +582,10 @@ function ZONE_RADIUS:SetVec2( Vec2 ) return self.Vec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. +--- Returns the @{DCS#Vec3} of the ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCS#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) @@ -603,6 +620,7 @@ function ZONE_RADIUS:Scan( ObjectCategories ) self.ScanData = {} self.ScanData.Coalitions = {} self.ScanData.Scenery = {} + self.ScanData.Units = {} local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() @@ -625,6 +643,7 @@ function ZONE_RADIUS:Scan( ObjectCategories ) (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then local CoalitionDCSUnit = ZoneObject:getCoalition() self.ScanData.Coalitions[CoalitionDCSUnit] = true + self.ScanData.Units[ZoneObject] = ZoneObject self:F( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end if ObjectCategory == Object.Category.SCENERY then @@ -643,6 +662,12 @@ function ZONE_RADIUS:Scan( ObjectCategories ) end +function ZONE_RADIUS:GetScannedUnits() + + return self.ScanData.Units +end + + function ZONE_RADIUS:CountScannedCoalitions() local Count = 0 @@ -804,7 +829,7 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. +-- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -822,7 +847,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. +-- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -836,7 +861,7 @@ end -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Dcs.DCSTypes#Vec2 The random location within the zone. +-- @return DCS#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) @@ -854,11 +879,11 @@ function ZONE_RADIUS:GetRandomVec2( inner, outer ) return Point end ---- Returns a @{Point#POINT_VEC2} object reflecting a random 2D location within the zone. +--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) @@ -873,7 +898,7 @@ end -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Dcs.DCSTypes#Vec3 The random location within the zone. +-- @return DCS#Vec3 The random location within the zone. function ZONE_RADIUS:GetRandomVec3( inner, outer ) self:F( self.ZoneName, inner, outer ) @@ -885,11 +910,11 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) end ---- Returns a @{Point#POINT_VEC3} object reflecting a random 3D location within the zone. +--- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC3 The @{Point#POINT_VEC3} object reflecting the random 3D location within the zone. +-- @return Core.Point#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) self:F( self.ZoneName, inner, outer ) @@ -901,7 +926,7 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) end ---- Returns a @{Point#COORDINATE} object reflecting a random 3D location within the zone. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. @@ -922,11 +947,33 @@ end -- @extends #ZONE_RADIUS ---- # ZONE class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE class, defined by the zone name as defined within the Mission Editor. +--- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- +-- ## ZONE constructor +-- +-- * @{#ZONE.New}(): Constructor. This will search for a trigger zone with the name given, and will return for you a ZONE object. +-- +-- ## Declare a ZONE directly in the DCS mission editor! +-- +-- You can declare a ZONE using the DCS mission editor by adding a trigger zone in the mission editor. +-- +-- Then during mission startup, when loading Moose.lua, this trigger zone will be detected as a ZONE declaration. +-- Within the background, a ZONE object will be created within the @{Core.Database}. +-- The ZONE name will be the trigger zone name. +-- +-- So, you can search yourself for the ZONE object by using the @{#ZONE.FindByName}() method. +-- In this example, `local TriggerZone = ZONE:FindByName( "DefenseZone" )` would return the ZONE object +-- that was created at mission startup, and reference it into the `TriggerZone` local object. +-- +-- Refer to mission `ZON-110` for a demonstration. +-- +-- This is especially handy if you want to quickly setup a SET_ZONE... +-- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, +-- then SetZone would contain the ZONE object `DefenseZone` as part of the zone collection, +-- without much scripting overhead!!! +-- +-- -- @field #ZONE ZONE = { ClassName="ZONE", @@ -954,14 +1001,26 @@ function ZONE:New( ZoneName ) return self end +--- Find a zone in the _DATABASE using the name of the zone. +-- @param #ZONE_BASE self +-- @param #string ZoneName The name of the zone. +-- @return #ZONE_BASE self +function ZONE:FindByName( ZoneName ) + + local ZoneFound = _DATABASE:FindZone( ZoneName ) + return ZoneFound +end + + --- @type ZONE_UNIT -- @field Wrapper.Unit#UNIT ZoneUNIT -- @extends Core.Zone#ZONE_RADIUS + --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} -- --- The ZONE_UNIT class defined by a zone attached to a @{Unit#UNIT} with a radius and optional x and y offsets. +-- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_UNIT @@ -981,6 +1040,7 @@ ZONE_UNIT = { -- theta The azimuth of the zone relative to unit -- relative_to_unit If true, theta is measured clockwise from unit's direction else clockwise from north. If using dx, dy setting this to true makes +x parallel to unit heading. -- dx, dy OR rho, theta may be used, not both. + -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) @@ -1002,13 +1062,16 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() + -- Zone objects are added to the _DATABASE and SET_ZONE objects. + _EVENTDISPATCHER:CreateEventNewZone( self ) + return self end ---- Returns the current location of the @{Unit#UNIT}. +--- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location and the offset, if any. +-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. function ZONE_UNIT:GetVec2() self:F2( self.ZoneName ) @@ -1049,7 +1112,7 @@ end --- Returns a random location within the zone. -- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The random location within the zone. +-- @return DCS#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) @@ -1069,10 +1132,10 @@ function ZONE_UNIT:GetRandomVec2() return RandomVec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. +--- Returns the @{DCS#Vec3} of the ZONE_UNIT. -- @param #ZONE_UNIT self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. +-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) @@ -1091,35 +1154,36 @@ end -- @extends #ZONE_RADIUS ---- # ZONE_GROUP class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. +--- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- @field #ZONE_GROUP ZONE_GROUP = { ClassName="ZONE_GROUP", } ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. +--- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. --- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Group#GROUP ZoneGROUP The @{Wrapper.Group} as the center of the zone. +-- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) self._.ZoneGROUP = ZoneGROUP + + -- Zone objects are added to the _DATABASE and SET_ZONE objects. + _EVENTDISPATCHER:CreateEventNewZone( self ) return self end ---- Returns the current location of the @{Group}. +--- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. +-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) @@ -1130,9 +1194,9 @@ function ZONE_GROUP:GetVec2() return ZoneVec2 end ---- Returns a random location within the zone of the @{Group}. +--- Returns a random location within the zone of the @{Wrapper.Group}. -- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. +-- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetRandomVec2() self:F( self.ZoneName ) @@ -1148,11 +1212,11 @@ function ZONE_GROUP:GetRandomVec2() return Point end ---- Returns a @{Point#POINT_VEC2} object reflecting a random 2D location within the zone. +--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_GROUP self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_GROUP:GetRandomPointVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) @@ -1165,14 +1229,12 @@ end --- @type ZONE_POLYGON_BASE --- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. +-- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE ---- # ZONE_POLYGON_BASE class, extends @{Zone#ZONE_BASE} --- --- The ZONE_POLYGON_BASE class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. +--- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. -- -- ## Zone point randomization @@ -1180,8 +1242,8 @@ end -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { @@ -1190,13 +1252,13 @@ ZONE_POLYGON_BASE = { --- A points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. +--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -1217,7 +1279,7 @@ end --- Returns the center location of the polygon. -- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. +-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) @@ -1324,7 +1386,7 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. +-- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -1352,9 +1414,9 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) return InPolygon end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. +-- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() self:F2() @@ -1378,9 +1440,9 @@ function ZONE_POLYGON_BASE:GetRandomVec2() return Vec2 end ---- Return a @{Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. +--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. -- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC2} +-- @return @{Core.Point#POINT_VEC2} function ZONE_POLYGON_BASE:GetRandomPointVec2() self:F2() @@ -1391,9 +1453,9 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2() return PointVec2 end ---- Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +--- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC3} +-- @return @{Core.Point#POINT_VEC3} function ZONE_POLYGON_BASE:GetRandomPointVec3() self:F2() @@ -1405,7 +1467,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3() end ---- Return a @{Point#COORDINATE} object representing a random 3D point at landheight within the zone. +--- Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. -- @param #ZONE_POLYGON_BASE self -- @return Core.Point#COORDINATE function ZONE_POLYGON_BASE:GetRandomCoordinate() @@ -1446,18 +1508,36 @@ end -- @extends #ZONE_POLYGON_BASE ---- # ZONE_POLYGON class, extends @{Zone#ZONE_POLYGON_BASE} +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- ## Declare a ZONE_POLYGON directly in the DCS mission editor! +-- +-- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name. +-- +-- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`. +-- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration. +-- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group. +-- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag. +-- +-- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method. +-- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object +-- that was created at mission startup, and reference it into the `PolygonZone` local object. +-- +-- Mission `ZON-510` shows a demonstration of this feature or method. +-- +-- This is especially handy if you want to quickly setup a SET_ZONE... +-- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, +-- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, +-- without much scripting overhead!!! -- -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", } ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the @{Wrapper.Group#GROUP} defined within the Mission Editor. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self -- @param #string ZoneName Name of the zone. -- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. @@ -1473,8 +1553,8 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) end ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Wrapper.Group#GROUP} defined within the Mission Editor. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self -- @param #string ZoneName Name of the zone. -- @param #string GroupName The group name of the GROUP defining the waypoints within the Mission Editor to define the polygon shape. @@ -1491,3 +1571,15 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) return self end + +--- Find a polygon zone in the _DATABASE using the name of the polygon zone. +-- @param #ZONE_POLYGON self +-- @param #string ZoneName The name of the polygon zone. +-- @return #ZONE_POLYGON self +function ZONE_POLYGON:FindByName( ZoneName ) + + local ZoneFound = _DATABASE:FindZone( ZoneName ) + return ZoneFound +end + + diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua new file mode 100644 index 000000000..19bb8e47c --- /dev/null +++ b/Moose Development/Moose/DCS.lua @@ -0,0 +1,1072 @@ +--- DCS API prototypes +-- @module DCS +-- @image MOOSE.JPG + +do -- world + + --- @type world + -- @field #world.event event + + --- @type world.event + -- @field S_EVENT_INVALID + -- @field S_EVENT_SHOT + -- @field S_EVENT_HIT + -- @field S_EVENT_TAKEOFF + -- @field S_EVENT_LAND + -- @field S_EVENT_CRASH + -- @field S_EVENT_EJECTION + -- @field S_EVENT_REFUELING + -- @field S_EVENT_DEAD + -- @field S_EVENT_PILOT_DEAD + -- @field S_EVENT_BASE_CAPTURED + -- @field S_EVENT_MISSION_START + -- @field S_EVENT_MISSION_END + -- @field S_EVENT_TOOK_CONTROL + -- @field S_EVENT_REFUELING_STOP + -- @field S_EVENT_BIRTH + -- @field S_EVENT_HUMAN_FAILURE + -- @field S_EVENT_ENGINE_STARTUP + -- @field S_EVENT_ENGINE_SHUTDOWN + -- @field S_EVENT_PLAYER_ENTER_UNIT + -- @field S_EVENT_PLAYER_LEAVE_UNIT + -- @field S_EVENT_PLAYER_COMMENT + -- @field S_EVENT_SHOOTING_START + -- @field S_EVENT_SHOOTING_END + -- @field S_EVENT_MAX + + world = {} --#world + +end -- world + + +do -- env + + --- @type env + + --- Add message to simulator log with caption "INFO". Message box is optional. + -- @function [parent=#env] info + -- @field #string message message string to add to log. + -- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + + --- Add message to simulator log with caption "WARNING". Message box is optional. + -- @function [parent=#env] warning + -- @field #string message message string to add to log. + -- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + + --- Add message to simulator log with caption "ERROR". Message box is optional. + -- @function [parent=#env] error + -- @field #string message message string to add to log. + -- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + + --- Enables/disables appearance of message box each time lua error occurs. + -- @function [parent=#env] setErrorMessageBoxEnabled + -- @field #boolean on if true message box appearance is enabled. + + env = {} --#env + +end -- env + + +do -- timer + + --- @type timer + + + --- Returns model time in seconds. + -- @function [parent=#timer] getTime + -- @return #Time + + --- Returns mission time in seconds. + -- @function [parent=#timer] getAbsTime + -- @return #Time + + --- Returns mission start time in seconds. + -- @function [parent=#timer] getTime0 + -- @return #Time + + --- Schedules function to call at desired model time. + -- Time function FunctionToCall(any argument, Time time) + -- + -- ... + -- + -- return ... + -- + -- end + -- + -- Must return model time of next call or nil. Note that the DCS scheduler calls the function in protected mode and any Lua errors in the called function will be trapped and not reported. If the function triggers a Lua error then it will be terminated and not scheduled to run again. + -- @function [parent=#timer] scheduleFunction + -- @param #FunctionToCall functionToCall Lua-function to call. Must have prototype of FunctionToCall. + -- @param functionArgument Function argument of any type to pass to functionToCall. + -- @param #Time time Model time of the function call. + -- @return functionId + + --- Re-schedules function to call at another model time. + -- @function [parent=#timer] setFunctionTime + -- @param functionId Lua-function to call. Must have prototype of FunctionToCall. + -- @param #Time time Model time of the function call. + + + --- Removes the function from schedule. + -- @function [parent=#timer] removeFunction + -- @param functionId Function identifier to remove from schedule + + timer = {} --#timer + +end + + +do -- land + + --- @type land + -- @field #land.SurfaceType SurfaceType + + + --- @type land.SurfaceType + -- @field LAND + -- @field SHALLOW_WATER + -- @field WATER + -- @field ROAD + -- @field RUNWAY + + --- Returns altitude MSL of the point. + -- @function [parent=#land] getHeight + -- @param #Vec2 point point on the ground. + -- @return #Distance + + --- returns surface type at the given point. + -- @function [parent=#land] getSurfaceType + -- @param #Vec2 point Point on the land. + -- @return #land.SurfaceType + + land = {} --#land + +end -- land + +do -- country + + --- @type country + -- @field #country.id id + + --- @type country.id + -- @field RUSSIA + -- @field UKRAINE + -- @field USA + -- @field TURKEY + -- @field UK + -- @field FRANCE + -- @field GERMANY + -- @field CANADA + -- @field SPAIN + -- @field THE_NETHERLANDS + -- @field BELGIUM + -- @field NORWAY + -- @field DENMARK + -- @field ISRAEL + -- @field GEORGIA + -- @field INSURGENTS + -- @field ABKHAZIA + -- @field SOUTH_OSETIA + -- @field ITALY + + country = {} -- #country + +end -- country + +do -- Command + + --- @type Command + -- @field #string id + -- @field #Command.params params + + --- @type Command.params + +end -- Command + +do -- coalition + + --- @type coalition + -- @field #coalition.side side + + --- @type coalition.side + -- @field NEUTRAL + -- @field RED + -- @field BLUE + + --- @function [parent=#coalition] getCountryCoalition + -- @param #number countryId + -- @return #number coalitionId + + coalition = {} -- #coalition + +end -- coalition + + +do -- Types + + --- @type Desc + -- @field #TypeName typeName type name + -- @field #string displayName localized display name + -- @field #table attributes object type attributes + + --- A distance type + -- @type Distance + + --- An angle type + -- @type Angle + + --- Time is given in seconds. + -- @type Time + -- @extends #number + + --- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. + -- @type ModelTime + -- @extends #number + + --- Mission time is a model time plus time of the mission start. + -- @type MissionTime + -- @extends #number + + + --- Distance is given in meters. + -- @type Distance + -- @extends #number + + --- Angle is given in radians. + -- @type Angle + -- @extends #number + + --- Azimuth is an angle of rotation around world axis y counter-clockwise. + -- @type Azimuth + -- @extends #number + + --- Mass is given in kilograms. + -- @type Mass + -- @extends #number + + --- Vec3 type is a 3D-vector. + -- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. + -- @type Vec3 + -- @field #Distance x is directed to the north + -- @field #Distance z is directed to the east + -- @field #Distance y is directed up + + --- Vec2 is a 2D-vector for the ground plane as a reference plane. + -- @type Vec2 + -- @field #Distance x Vec2.x = Vec3.x + -- @field #Distance y Vec2.y = Vec3.z + + --- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: + -- @type Position3 + -- @field #Vec3 p + -- @field #Vec3 x + -- @field #Vec3 y + -- @field #Vec3 z + + --- 3-dimensional box. + -- @type Box3 + -- @field #Vec3 min + -- @field #Vec3 max + + --- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". + -- @type TypeName + -- @extends #string + + --- AttributeName = string + -- Each object type may have attributes. + -- Attributes are enlisted in ./Scripts/Database/db_attributes.Lua. + -- To know what attributes the object type has, look for the unit type script in sub-directories planes/, helicopter/s, vehicles, navy/ of ./Scripts/Database/ directory. + -- @type AttributeName + -- @extends #string + + --- List of @{#AttributeName} + -- @type AttributeNameArray + -- @list <#AttributeName> + + --- @type Zone + -- @field DCSVec3#Vec3 point + -- @field #number radius + + Zone = {} + + --- @type ModelTime + -- @extends #number + + --- @type Time + -- @extends #number + + --- A task descriptor (internal structure for DCS World) + -- @type Task + -- @field #string id + -- @field #Task.param param + + --- @type Task.param + + --- List of @{#Task} + -- @type TaskArray + -- @list <#Task> + + +end -- + +do -- Object + + --- @type Object + -- @field #Object.Category Category + -- @field #Object.Desc Desc + + --- @type Object.Category + -- @field UNIT + -- @field WEAPON + -- @field STATIC + -- @field SCENERY + -- @field BASE + + --- @type Object.Desc + -- @extends #Desc + -- @field #number life initial life level + -- @field #Box3 box bounding box of collision geometry + + --- @function [parent=#Object] isExist + -- @param #Object self + -- @return #boolean + + --- @function [parent=#Object] destroy + -- @param #Object self + + --- @function [parent=#Object] getCategory + -- @param #Object self + -- @return #Object.Category + + --- Returns type name of the Object. + -- @function [parent=#Object] getTypeName + -- @param #Object self + -- @return #string + + --- Returns object descriptor. + -- @function [parent=#Object] getDesc + -- @param #Object self + -- @return #Object.Desc + + --- Returns true if the object belongs to the category. + -- @function [parent=#Object] hasAttribute + -- @param #Object self + -- @param #AttributeName attributeName Attribute name to check. + -- @return #boolean + + --- Returns name of the object. This is the name that is assigned to the object in the Mission Editor. + -- @function [parent=#Object] getName + -- @param #Object self + -- @return #string + + --- Returns object coordinates for current time. + -- @function [parent=#Object] getPoint + -- @param #Object self + -- @return #Vec3 + + --- Returns object position for current time. + -- @function [parent=#Object] getPosition + -- @param #Object self + -- @return #Position3 + + --- Returns the unit's velocity vector. + -- @function [parent=#Object] getVelocity + -- @param #Object self + -- @return #Vec3 + + --- Returns true if the unit is in air. + -- @function [parent=#Object] inAir + -- @param #Object self + -- @return #boolean + + Object = {} --#Object + +end -- Object + +do -- CoalitionObject + + --- @type CoalitionObject + -- @extends #Object + + + --- Returns coalition of the object. + -- @function [parent=#CoalitionObject] getCoalition + -- @param #CoalitionObject self + -- @return #coalition.side + + --- Returns object country. + -- @function [parent=#CoalitionObject] getCountry + -- @param #CoalitionObject self + -- @return #country.id + +CoalitionObject = {} --#CoalitionObject + +end -- CoalitionObject + + +do -- Airbase + + --- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. + -- @type Airbase + -- @extends #CoalitionObject + -- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. + -- @field #Airbase.Category Category enum contains identifiers of airbase categories. + -- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. + + --- Enum contains identifiers of airbase categories. + -- @type Airbase.Category + -- @field AIRDROME + -- @field HELIPAD + -- @field SHIP + + --- Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. + -- @type Airbase.Desc + -- @extends #Desc + -- @field #Airbase.Category category Category of the airbase type. + + --- Returns airbase by its name. If no airbase found the function will return nil. + -- @function [parent=#Airbase] getByName + -- @param #string name + -- @return #Airbase + + --- Returns airbase descriptor by type name. If no descriptor is found the function will return nil. + -- @function [parent=#Airbase] getDescByName + -- @param #TypeName typeName Airbase type name. + -- @return #Airbase.Desc + + --- Returns Unit that is corresponded to the airbase. Works only for ships. + -- @function [parent=#Airbase] getUnit + -- @param self + -- @return #Unit + + --- Returns identifier of the airbase. + -- @function [parent=#Airbase] getID + -- @param self + -- @return #Airbase.ID + + --- Returns the airbase's callsign - the localized string. + -- @function [parent=#Airbase] getCallsign + -- @param self + -- @return #string + + --- Returns descriptor of the airbase. + -- @function [parent=#Airbase] getDesc + -- @param self + -- @return #Airbase.Desc + + Airbase = {} --#Airbase + +end -- Airbase + + + +do -- Controller + --- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. + -- + -- This class has 2 types of functions: + -- + -- * Tasks + -- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics. + -- @type Controller + -- @field #Controller.Detection Detection Enum contains identifiers of surface types. + + --- Enables and disables the controller. + -- Note: Now it works only for ground / naval groups! + -- @function [parent=#Controller] setOnOff + -- @param self + -- @param #boolean value Enable / Disable. + + -- Tasks + + --- Resets current task and then sets the task to the controller. Task is a table that contains task identifier and task parameters. + -- @function [parent=#Controller] setTask + -- @param self + -- @param #Task task + + --- Resets current task of the controller. + -- @function [parent=#Controller] resetTask + -- @param self + + --- Pushes the task to the front of the queue and makes the task active. Further call of function Controller.setTask() function will stop current task, clear the queue and set the new task active. If the task queue is empty the function will work like function Controller.setTask() function. + -- @function [parent=#Controller] pushTask + -- @param self + -- @param #Task task + + --- Pops current (front) task from the queue and makes active next task in the queue (if exists). If no more tasks in the queue the function works like function Controller.resetTask() function. Does nothing if the queue is empty. + -- @function [parent=#Controller] popTask + -- @param self + + --- Returns true if the controller has a task. + -- @function [parent=#Controller] hasTask + -- @param self + -- @return #boolean + + -- Commands + + --TODO: describe #Command structure + --- Sets the command to perform by controller. + -- @function [parent=#Controller] setCommand + -- @param self + -- @param #Command command Table that contains command identifier and command parameters. + + + -- Behaviours + + --- Sets the option to the controller. + -- Option is a pair of identifier and value. Behavior options are global parameters those affect controller behavior in all tasks it performs. + -- Option identifiers and values are stored in table AI.Option in subtables Air, Ground and Naval. + -- + -- OptionId = @{#AI.Option.Air.id} or @{#AI.Option.Ground.id} or @{#AI.Option.Naval.id} + -- OptionValue = AI.Option.Air.val[optionName] or AI.Option.Ground.val[optionName] or AI.Option.Naval.val[optionName] + -- + -- @function [parent=#Controller] setOption + -- @param self + -- @param #OptionId optionId Option identifier. + -- @param #OptionValue optionValue Value of the option. + + + -- Detection + + --- Enum contains identifiers of surface types. + -- @type Controller.Detection + -- @field VISUAL + -- @field OPTIC + -- @field RADAR + -- @field IRST + -- @field RWR + -- @field DLINK + + --- Detected target. + -- @type DetectedTarget + -- @field Wrapper.Object#Object object The target + -- @field #boolean visible The target is visible + -- @field #boolean type The target type is known + -- @field #boolean distance Distance to the target is known + + + --- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. + -- @function [parent=#Controller] isTargetDetected + -- @param self + -- @param Wrapper.Object#Object target Target to check + -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN + -- @return #boolean detected True if the target is detected. + -- @return #boolean visible Has effect only if detected is true. True if the target is visible now. + -- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen. + -- @return #boolean type Has effect only if detected is true. True if the target type is known. + -- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known. + -- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen. + -- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen. + + + --- Returns list of detected targets. If one or more detection method is specified the function will return targets which were detected by at least one of these methods. If no detection methods are specified the function will return targets which were detected by any method. + -- @function [parent=#Controller] getDetectedTargets + -- @param self + -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN + -- @return #list<#DetectedTarget> array of DetectedTarget + + --- Know a target. + -- @function [parent=#Controller] knowTarget + -- @param self + -- @param Wrapper.Object#Object object The target. + -- @param #boolean type Target type is known. + -- @param #boolean distance Distance to target is known. + + + Controller = {} --#Controller + +end -- Controller + + +do -- Unit + + --- @type Unit + -- @extends #CoalitionObject + -- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. + -- @field #Unit.Category Category + -- @field #Unit.RefuelingSystem RefuelingSystem + -- @field #Unit.SensorType SensorType + -- @field #Unit.OpticType OpticType + -- @field #Unit.RadarType RadarType + -- @field #Unit.Desc Desc + -- @field #Unit.DescAircraft DescAircraft + -- @field #Unit.DescAirplane DescAirplane + -- @field #Unit.DescHelicopter DescHelicopter + -- @field #Unit.DescVehicle DescVehicle + -- @field #Unit.DescShip DescShip + -- @field #Unit.AmmoItem AmmoItem + -- @field #list<#Unit.AmmoItem> Ammo + -- @field #Unit.Sensor Sensor + -- @field #Unit.Optic Optic + -- @field #Unit.Radar Radar + -- @field #Unit.IRST IRST + + + --- Enum that stores unit categories. + -- @type Unit.Category + -- @field AIRPLANE + -- @field HELICOPTER + -- @field GROUND_UNIT + -- @field SHIP + -- @field STRUCTURE + + --- Enum that stores aircraft refueling system types. + -- @type Unit.RefuelingSystem + -- @field BOOM_AND_RECEPTACLE + -- @field PROBE_AND_DROGUE + + --- Enum that stores sensor types. + -- @type Unit.SensorType + -- @field OPTIC + -- @field RADAR + -- @field IRST + -- @field RWR + + --- Enum that stores types of optic sensors. + -- @type Unit.OpticType + -- @field TV TV-sensor + -- @field LLTV Low-level TV-sensor + -- @field IR Infra-Red optic sensor + + --- Enum that stores radar types. + -- @type Unit.RadarType + -- @field AS air search radar + -- @field SS surface/land search radar + + + --- A unit descriptor. + -- @type Unit.Desc + -- @extends #Object.Desc + -- @field #Unit.Category category Unit Category + -- @field #Mass massEmpty mass of empty unit + -- @field #number speedMax istance / Time, --maximal velocity + + --- An aircraft descriptor. + -- @type Unit.DescAircraft + -- @extends #Unit.Desc + -- @field #Mass fuelMassMax maximal inner fuel mass + -- @field #Distance range Operational range + -- @field #Distance Hmax Ceiling + -- @field #number VyMax #Distance / #Time, --maximal climb rate + -- @field #number NyMin minimal safe acceleration + -- @field #number NyMax maximal safe acceleration + -- @field #Unit.RefuelingSystem tankerType refueling system type + + --- An airplane descriptor. + -- @type Unit.DescAirplane + -- @extends #Unit.DescAircraft + -- @field #number speedMax0 Distance / Time maximal TAS at ground level + -- @field #number speedMax10K Distance / Time maximal TAS at altitude of 10 km + + --- A helicopter descriptor. + -- @type Unit.DescHelicopter + -- @extends #Unit.DescAircraft + -- @field #Distance HmaxStat static ceiling + + --- A vehicle descriptor. + -- @type Unit.DescVehicle + -- @extends #Unit.Desc + -- @field #Angle maxSlopeAngle maximal slope angle + -- @field #boolean riverCrossing can the vehicle cross a rivers + + --- A ship descriptor. + -- @type Unit.DescShip + -- @extends #Unit.Desc + + --- ammunition item: "type-count" pair. + -- @type Unit.AmmoItem + -- @field #Weapon.Desc desc ammunition descriptor + -- @field #number count ammunition count + + --- A unit sensor. + -- @type Unit.Sensor + -- @field #TypeName typeName + -- @field #Unit.SensorType type + + --- An optic sensor. + -- @type Unit.Optic + -- @extends #Unit.Sensor + -- @field #Unit.OpticType opticType + + --- A radar. + -- @type Unit.Radar + -- @extends #Unit.Sensor + -- @field #Distance detectionDistanceRBM detection distance for RCS=1m^2 in real-beam mapping mode, nil if radar doesn't support surface/land search + -- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM + -- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search + + --- @type Unit.Radar.detectionDistanceAir + -- @field #Unit.Radar.detectionDistanceAir.upperHemisphere upperHemisphere + -- @field #Unit.Radar.detectionDistanceAir.lowerHemisphere lowerHemisphere + + --- @type Unit.Radar.detectionDistanceAir.upperHemisphere + -- @field #Distance headOn + -- @field #Distance tailOn + + --- @type Unit.Radar.detectionDistanceAir.lowerHemisphere + -- @field #Distance headOn + -- @field #Distance tailOn + + --- An IRST. + -- @type Unit.IRST + -- @extends #Unit.Sensor + -- @field #Distance detectionDistanceIdle detection of tail-on target with heat signature = 1 in upper hemisphere, engines are in idle + -- @field #Distance detectionDistanceMaximal ..., engines are in maximal mode + -- @field #Distance detectionDistanceAfterburner ..., engines are in afterburner mode + + --- An RWR. + -- @type Unit.RWR + -- @extends #Unit.Sensor + + --- table that stores all unit sensors. + -- TODO @type Sensors + -- + + + --- Returns unit object by the name assigned to the unit in Mission Editor. If there is unit with such name or the unit is destroyed the function will return nil. The function provides access to non-activated units too. + -- @function [parent=#Unit] getByName + -- @param #string name + -- @return #Unit + + --- Returns if the unit is activated. + -- @function [parent=#Unit] isActive + -- @param #Unit self + -- @return #boolean + + --- Returns name of the player that control the unit or nil if the unit is controlled by A.I. + -- @function [parent=#Unit] getPlayerName + -- @param #Unit self + -- @return #string + + --- returns the unit's unique identifier. + -- @function [parent=#Unit] getID + -- @param #Unit self + -- @return #Unit.ID + + + --- Returns the unit's number in the group. The number is the same number the unit has in ME. It may not be changed during the mission. If any unit in the group is destroyed, the numbers of another units will not be changed. + -- @function [parent=#Unit] getNumber + -- @param #Unit self + -- @return #number + + --- Returns controller of the unit if it exist and nil otherwise + -- @function [parent=#Unit] getController + -- @param #Unit self + -- @return #Controller + + --- Returns the unit's group if it exist and nil otherwise + -- @function [parent=#Unit] getGroup + -- @param #Unit self + -- @return #Group + + --- Returns the unit's callsign - the localized string. + -- @function [parent=#Unit] getCallsign + -- @param #Unit self + -- @return #string + + --- Returns the unit's health. Dead units has health <= 1.0 + -- @function [parent=#Unit] getLife + -- @param #Unit self + -- @return #number + + --- returns the unit's initial health. + -- @function [parent=#Unit] getLife0 + -- @param #Unit self + -- @return #number + + --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. + -- @function [parent=#Unit] getFuel + -- @param #Unit self + -- @return #number + + --- Returns the unit ammunition. + -- @function [parent=#Unit] getAmmo + -- @param #Unit self + -- @return #Unit.Ammo + + --- Returns the unit sensors. + -- @function [parent=#Unit] getSensors + -- @param #Unit self + -- @return #Unit.Sensors + + --- Returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. + -- @function [parent=#Unit] hasSensors + -- @param #Unit self + -- @param #Unit.SensorType sensorType (= nil) Sensor type. + -- @param ... Additional parameters. + -- @return #boolean + -- @usage + -- If sensorType is Unit.SensorType.OPTIC, additional parameters are optic sensor types. Following example checks if the unit has LLTV or IR optics: + -- unit:hasSensors(Unit.SensorType.OPTIC, Unit.OpticType.LLTV, Unit.OpticType.IR) + -- If sensorType is Unit.SensorType.RADAR, additional parameters are radar types. Following example checks if the unit has air search radars: + -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) + -- If no additional parameters are specified the function returns true if the unit has at least one sensor of specified type. + -- If sensor type is not specified the function returns true if the unit has at least one sensor of any type. + -- + + --- returns two values: + -- First value indicates if at least one of the unit's radar(s) is on. + -- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. + -- @function [parent=#Unit] getRadar + -- @param #Unit self + -- @return #boolean, Wrapper.Object#Object + + --- Returns unit descriptor. Descriptor type depends on unit category. + -- @function [parent=#Unit] getDesc + -- @param #Unit self + -- @return #Unit.Desc + + + Unit = {} --#Unit + +end -- Unit + + +do -- Group + + --- Represents group of Units. + -- @type Group + -- @field #ID ID Identifier of a group. It is assigned to a group by Mission Editor automatically. + -- @field #Group.Category Category Enum contains identifiers of group types. + + --- Enum contains identifiers of group types. + -- @type Group.Category + -- @field AIRPLANE + -- @field HELICOPTER + -- @field GROUND + -- @field SHIP + + -- Static Functions + + --- Returns group by the name assigned to the group in Mission Editor. + -- @function [parent=#Group] getByName + -- @param #string name + -- @return #Group + + -- Member Functions + + --- returns true if the group exist or false otherwise. + -- @function [parent=#Group] isExist + -- @param #Group self + -- @return #boolean + + --- Destroys the group and all of its units. + -- @function [parent=#Group] destroy + -- @param #Group self + + --- Returns category of the group. + -- @function [parent=#Group] getCategory + -- @param #Group self + -- @return #Group.Category + + --TODO check coalition.side + + --- Returns the coalition of the group. + -- @function [parent=#Group] getCoalition + -- @param #Group self + -- @return #coalition.side + + --- Returns the group's name. This is the same name assigned to the group in Mission Editor. + -- @function [parent=#Group] getName + -- @param #Group self + -- @return #string + + --- Returns the group identifier. + -- @function [parent=#Group] getID + -- @param #Group self + -- @return #ID + + --- Returns the unit with number unitNumber. If the unit is not exists the function will return nil. + -- @function [parent=#Group] getUnit + -- @param #Group self + -- @param #number unitNumber + -- @return #Unit + + --- Returns current size of the group. If some of the units will be destroyed, As units are destroyed the size of the group will be changed. + -- @function [parent=#Group] getSize + -- @param #Group self + -- @return #number + + --- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function. + -- @function [parent=#Group] getInitialSize + -- @param #Group self + -- @return #number + + --- Returns array of the units present in the group now. Destroyed units will not be enlisted at all. + -- @function [parent=#Group] getUnits + -- @param #Group self + -- @return #list<#Unit> array of Units + + --- Returns controller of the group. + -- @function [parent=#Group] getController + -- @param #Group self + -- @return #Controller + + Group = {} --#Group + +end -- Group + + +do -- AI + + --- @type AI + -- @field #AI.Skill Skill + -- @field #AI.Task Task + -- @field #AI.Option Option + + --- @type AI.Skill + -- @field AVERAGE + -- @field GOOD + -- @field HIGH + -- @field EXCELLENT + -- @field PLAYER + -- @field CLIENT + + --- @type AI.Task + -- @field #AI.Task.WeaponExpend WeaponExpend + -- @field #AI.Task.OrbitPattern OrbitPattern + -- @field #AI.Task.Designation Designation + -- @field #AI.Task.WaypointType WaypointType + -- @field #AI.Task.TurnMethod TurnMethod + -- @field #AI.Task.AltitudeType AltitudeType + -- @field #AI.Task.VehicleFormation VehicleFormation + + --- @type AI.Task.WeaponExpend + -- @field ONE + -- @field TWO + -- @field FOUR + -- @field QUARTER + -- @field HALF + -- @field ALL + + --- @type AI.Task.OrbitPattern + -- @field CIRCLE + -- @field RACE_TRACK + + --- @type AI.Task.Designation + -- @field NO + -- @field AUTO + -- @field WP + -- @field IR_POINTER + -- @field LASER + + --- @type AI.Task.WaypointType + -- @field TAKEOFF + -- @field TAKEOFF_PARKING + -- @field TURNING_POINT + -- @field LAND + + --- @type AI.Task.TurnMethod + -- @field FLY_OVER_POINT + -- @field FIN_POINT + + --- @type AI.Task.AltitudeType + -- @field BARO + -- @field RADIO + + --- @type AI.Task.VehicleFormation + -- @field OFF_ROAD + -- @field ON_ROAD + -- @field RANK + -- @field CONE + -- @field DIAMOND + -- @field VEE + -- @field ECHELON_LEFT + -- @field ECHELON_RIGHT + + --- @type AI.Option + -- @field #AI.Option.Air Air + -- @field #AI.Option.Ground Ground + -- @field #AI.Option.Naval Naval + + --- @type AI.Option.Air + -- @field #AI.Option.Air.id id + -- @field #AI.Option.Air.val val + + --- @type AI.Option.Ground + -- @field #AI.Option.Ground.id id + -- @field #AI.Option.Ground.val val + + --- @type AI.Option.Naval + -- @field #AI.Option.Naval.id id + -- @field #AI.Option.Naval.val val + + --TODO: work on formation + --- @type AI.Option.Air.id + -- @field NO_OPTION + -- @field ROE + -- @field REACTION_ON_THREAT + -- @field RADAR_USING + -- @field FLARE_USING + -- @field FORMATION + -- @field RTB_ON_BINGO + -- @field SILENCE + + --- @type AI.Option.Air.val + -- @field #AI.Option.Air.val.ROE ROE + -- @field #AI.Option.Air.val.REACTION_ON_THREAT REACTION_ON_THREAT + -- @field #AI.Option.Air.val.RADAR_USING RADAR_USING + -- @field #AI.Option.Air.val.FLARE_USING FLARE_USING + + --- @type AI.Option.Air.val.ROE + -- @field WEAPON_FREE + -- @field OPEN_FIRE_WEAPON_FREE + -- @field OPEN_FIRE + -- @field RETURN_FIRE + -- @field WEAPON_HOLD + + --- @type AI.Option.Air.val.REACTION_ON_THREAT + -- @field NO_REACTION + -- @field PASSIVE_DEFENCE + -- @field EVADE_FIRE + -- @field BYPASS_AND_ESCAPE + -- @field ALLOW_ABORT_MISSION + + --- @type AI.Option.Air.val.RADAR_USING + -- @field NEVER + -- @field FOR_ATTACK_ONLY + -- @field FOR_SEARCH_IF_REQUIRED + -- @field FOR_CONTINUOUS_SEARCH + + --- @type AI.Option.Air.val.FLARE_USING + -- @field NEVER + -- @field AGAINST_FIRED_MISSILE + -- @field WHEN_FLYING_IN_SAM_WEZ + -- @field WHEN_FLYING_NEAR_ENEMIES + + --- @type AI.Option.Ground.id + -- @field NO_OPTION + -- @field ROE @{#AI.Option.Ground.val.ROE} + -- @field DISPERSE_ON_ATTACK true or false + -- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE} + + --- @type AI.Option.Ground.val + -- @field #AI.Option.Ground.val.ROE ROE + -- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE + + --- @type AI.Option.Ground.val.ROE + -- @field OPEN_FIRE + -- @field RETURN_FIRE + -- @field WEAPON_HOLD + + --- @type AI.Option.Ground.val.ALARM_STATE + -- @field AUTO + -- @field GREEN + -- @field RED + + --- @type AI.Option.Naval.id + -- @field NO_OPTION + -- @field ROE + + --- @type AI.Option.Naval.val + -- @field #AI.Option.Naval.val.ROE ROE + + --- @type AI.Option.Naval.val.ROE + -- @field OPEN_FIRE + -- @field RETURN_FIRE + -- @field WEAPON_HOLD + + AI = {} --#AI + +end -- AI + + + diff --git a/Moose Development/Moose/Dcs/DCSAirbase.lua b/Moose Development/Moose/Dcs/DCSAirbase.lua deleted file mode 100644 index d47ddc067..000000000 --- a/Moose Development/Moose/Dcs/DCSAirbase.lua +++ /dev/null @@ -1,54 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSAirbase - - ---- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. --- @type Airbase --- @extends Dcs.DCSCoalitionWrapper.Object#CoalitionObject --- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. --- @field #Airbase.Category Category enum contains identifiers of airbase categories. --- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. - ---- Enum contains identifiers of airbase categories. --- @type Airbase.Category --- @field AIRDROME --- @field HELIPAD --- @field SHIP - ---- Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. --- @type Airbase.Desc --- @extends #Desc --- @field #Airbase.Category category Category of the airbase type. - ---- Returns airbase by its name. If no airbase found the function will return nil. --- @function [parent=#Airbase] getByName --- @param #string name --- @return #Airbase - ---- Returns airbase descriptor by type name. If no descriptor is found the function will return nil. --- @function [parent=#Airbase] getDescByName --- @param #TypeName typeName Airbase type name. --- @return #Airbase.Desc - ---- Returns Unit that is corresponded to the airbase. Works only for ships. --- @function [parent=#Airbase] getUnit --- @param self --- @return Wrapper.Unit#Unit - ---- Returns identifier of the airbase. --- @function [parent=#Airbase] getID --- @param self --- @return #Airbase.ID - ---- Returns the airbase's callsign - the localized string. --- @function [parent=#Airbase] getCallsign --- @param self --- @return #string - ---- Returns descriptor of the airbase. --- @function [parent=#Airbase] getDesc --- @param self --- @return #Airbase.Desc - - -Airbase = {} --#Airbase diff --git a/Moose Development/Moose/Dcs/DCSCoalitionObject.lua b/Moose Development/Moose/Dcs/DCSCoalitionObject.lua deleted file mode 100644 index be7dc106b..000000000 --- a/Moose Development/Moose/Dcs/DCSCoalitionObject.lua +++ /dev/null @@ -1,20 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSCoalitionObject - ---- @type CoalitionObject --- @extends Dcs.DCSWrapper.Object#Object - -coalition = {} --#coalition - ---- Returns coalition of the object. --- @function [parent=#CoalitionObject] getCoalition --- @param #CoalitionObject self --- @return Dcs.DCSTypes#coalition.side - ---- Returns object country. --- @function [parent=#CoalitionObject] getCountry --- @param #CoalitionObject self --- @return #country.id - - -CoalitionObject = {} --#CoalitionObject diff --git a/Moose Development/Moose/Dcs/DCSCommand.lua b/Moose Development/Moose/Dcs/DCSCommand.lua deleted file mode 100644 index 5e0040332..000000000 --- a/Moose Development/Moose/Dcs/DCSCommand.lua +++ /dev/null @@ -1,10 +0,0 @@ ---- @module DCSCommand - - ---- @type Command --- @field #string id --- @field #Command.params params - ---- @type Command.params - -env.info( "Command defined" ) \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCSController.lua b/Moose Development/Moose/Dcs/DCSController.lua deleted file mode 100644 index 09b8d4b1e..000000000 --- a/Moose Development/Moose/Dcs/DCSController.lua +++ /dev/null @@ -1,115 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSController - ---- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. --- --- This class has 2 types of functions: --- --- * Tasks --- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics. --- @type Controller --- @field #Controller.Detection Detection Enum contains identifiers of surface types. - ---- Enables and disables the controller. --- Note: Now it works only for ground / naval groups! --- @function [parent=#Controller] setOnOff --- @param self --- @param #boolean value Enable / Disable. - --- Tasks - ---- Resets current task and then sets the task to the controller. Task is a table that contains task identifier and task parameters. --- @function [parent=#Controller] setTask --- @param self --- @param #Task task - ---- Resets current task of the controller. --- @function [parent=#Controller] resetTask --- @param self - ---- Pushes the task to the front of the queue and makes the task active. Further call of function Controller.setTask() function will stop current task, clear the queue and set the new task active. If the task queue is empty the function will work like function Controller.setTask() function. --- @function [parent=#Controller] pushTask --- @param self --- @param #Task task - ---- Pops current (front) task from the queue and makes active next task in the queue (if exists). If no more tasks in the queue the function works like function Controller.resetTask() function. Does nothing if the queue is empty. --- @function [parent=#Controller] popTask --- @param self - ---- Returns true if the controller has a task. --- @function [parent=#Controller] hasTask --- @param self --- @return #boolean - --- Commands - ---TODO: describe #Command structure ---- Sets the command to perform by controller. --- @function [parent=#Controller] setCommand --- @param self --- @param #Command command Table that contains command identifier and command parameters. - - --- Behaviours - ---- Sets the option to the controller. --- Option is a pair of identifier and value. Behavior options are global parameters those affect controller behavior in all tasks it performs. --- Option identifiers and values are stored in table AI.Option in subtables Air, Ground and Naval. --- --- OptionId = @{#AI.Option.Air.id} or @{#AI.Option.Ground.id} or @{#AI.Option.Naval.id} --- OptionValue = AI.Option.Air.val[optionName] or AI.Option.Ground.val[optionName] or AI.Option.Naval.val[optionName] --- --- @function [parent=#Controller] setOption --- @param self --- @param #OptionId optionId Option identifier. --- @param #OptionValue optionValue Value of the option. - - --- Detection - ---- Enum contains identifiers of surface types. --- @type Controller.Detection --- @field VISUAL --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR --- @field DLINK - ---- Detected target. --- @type DetectedTarget --- @field Wrapper.Object#Object object The target --- @field #boolean visible The target is visible --- @field #boolean type The target type is known --- @field #boolean distance Distance to the target is known - - ---- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. --- @function [parent=#Controller] isTargetDetected --- @param self --- @param Wrapper.Object#Object target Target to check --- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN --- @return #boolean detected True if the target is detected. --- @return #boolean visible Has effect only if detected is true. True if the target is visible now. --- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen. --- @return #boolean type Has effect only if detected is true. True if the target type is known. --- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known. --- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen. --- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen. - - ---- Returns list of detected targets. If one or more detection method is specified the function will return targets which were detected by at least one of these methods. If no detection methods are specified the function will return targets which were detected by any method. --- @function [parent=#Controller] getDetectedTargets --- @param self --- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN --- @return #list<#DetectedTarget> array of DetectedTarget - ---- Know a target. --- @function [parent=#Controller] knowTarget --- @param self --- @param Wrapper.Object#Object object The target. --- @param #boolean type Target type is known. --- @param #boolean distance Distance to target is known. - - -Controller = {} --#Controller \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCSGroup.lua b/Moose Development/Moose/Dcs/DCSGroup.lua deleted file mode 100644 index 04f7818bf..000000000 --- a/Moose Development/Moose/Dcs/DCSGroup.lua +++ /dev/null @@ -1,83 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSGroup - ---- Represents group of Units. --- @type Group --- @field #ID ID Identifier of a group. It is assigned to a group by Mission Editor automatically. --- @field #Group.Category Category Enum contains identifiers of group types. - ---- Enum contains identifiers of group types. --- @type Group.Category --- @field AIRPLANE --- @field HELICOPTER --- @field GROUND --- @field SHIP - --- Static Functions - ---- Returns group by the name assigned to the group in Mission Editor. --- @function [parent=#Group] getByName --- @param #string name --- @return #Group - --- Member Functions - ---- returns true if the group exist or false otherwise. --- @function [parent=#Group] isExist --- @param #Group self --- @return #boolean - ---- Destroys the group and all of its units. --- @function [parent=#Group] destroy --- @param #Group self - ---- Returns category of the group. --- @function [parent=#Group] getCategory --- @param #Group self --- @return #Group.Category - ---TODO check coalition.side - ---- Returns the coalition of the group. --- @function [parent=#Group] getCoalition --- @param #Group self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side - ---- Returns the group's name. This is the same name assigned to the group in Mission Editor. --- @function [parent=#Group] getName --- @param #Group self --- @return #string - ---- Returns the group identifier. --- @function [parent=#Group] getID --- @param #Group self --- @return #ID - ---- Returns the unit with number unitNumber. If the unit is not exists the function will return nil. --- @function [parent=#Group] getUnit --- @param #Group self --- @param #number unitNumber --- @return Dcs.DCSWrapper.Unit#Unit - ---- Returns current size of the group. If some of the units will be destroyed, As units are destroyed the size of the group will be changed. --- @function [parent=#Group] getSize --- @param #Group self --- @return #number - ---- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function. --- @function [parent=#Group] getInitialSize --- @param #Group self --- @return #number - ---- Returns array of the units present in the group now. Destroyed units will not be enlisted at all. --- @function [parent=#Group] getUnits --- @param #Group self --- @return #list array of Units - ---- Returns controller of the group. --- @function [parent=#Group] getController --- @param #Group self --- @return Controller#Controller - -Group = {} --#Group - diff --git a/Moose Development/Moose/Dcs/DCSObject.lua b/Moose Development/Moose/Dcs/DCSObject.lua deleted file mode 100644 index 281e2781a..000000000 --- a/Moose Development/Moose/Dcs/DCSObject.lua +++ /dev/null @@ -1,73 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSObject - ---- @type Object --- @field #Object.Category Category --- @field #Object.Desc Desc - ---- @type Object.Category --- @field UNIT --- @field WEAPON --- @field STATIC --- @field SCENERY --- @field BASE - ---- @type Object.Desc --- @extends #Desc --- @field #number life initial life level --- @field #Box3 box bounding box of collision geometry - ---- @function [parent=#Object] isExist --- @param #Object self --- @return #boolean - ---- @function [parent=#Object] destroy --- @param #Object self - ---- @function [parent=#Object] getCategory --- @param #Object self --- @return #Object.Category - ---- Returns type name of the Object. --- @function [parent=#Object] getTypeName --- @param #Object self --- @return #string - ---- Returns object descriptor. --- @function [parent=#Object] getDesc --- @param #Object self --- @return #Object.Desc - ---- Returns true if the object belongs to the category. --- @function [parent=#Object] hasAttribute --- @param #Object self --- @param #AttributeName attributeName Attribute name to check. --- @return #boolean - ---- Returns name of the object. This is the name that is assigned to the object in the Mission Editor. --- @function [parent=#Object] getName --- @param #Object self --- @return #string - ---- Returns object coordinates for current time. --- @function [parent=#Object] getPoint --- @param #Object self --- @return #Vec3 - ---- Returns object position for current time. --- @function [parent=#Object] getPosition --- @param #Object self --- @return #Position3 - ---- Returns the unit's velocity vector. --- @function [parent=#Object] getVelocity --- @param #Object self --- @return #Vec3 - ---- Returns true if the unit is in air. --- @function [parent=#Object] inAir --- @param #Object self --- @return #boolean - -Object = {} --#Object - diff --git a/Moose Development/Moose/Dcs/DCSStaticObject.lua b/Moose Development/Moose/Dcs/DCSStaticObject.lua deleted file mode 100644 index 5dc220412..000000000 --- a/Moose Development/Moose/Dcs/DCSStaticObject.lua +++ /dev/null @@ -1,34 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSStaticObject - - -------------------------------------------------------------------------------- --- @module StaticObject --- @extends CoalitionWrapper.Object#CoalitionObject - ---- Represents static object added in the Mission Editor. --- @type StaticObject --- @field #StaticObject.ID ID Identifier of a StaticObject. It assigned to an StaticObject by the Mission Editor automatically. --- @field #StaticObject.Desc Desc Descriptor of StaticObject and Unit are equal. StaticObject is just a passive variant of Unit. - ---- StaticObject descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. --- @type StaticObject.Desc --- @extends Wrapper.Unit#Unit.Desc - ---- Returns static object by its name. If no static object found nil will be returned. --- @function [parent=#StaticObject] getByName --- @param #string name Name of static object to find. --- @return #StaticObject - ---- returns identifier of the static object. --- @function [parent=#StaticObject] getID --- @param #StaticObject self --- @return #StaticObject.ID - ---- Returns descriptor of the StaticObject. --- @function [parent=#StaticObject] getDesc --- @param #StaticObject self --- @return #StaticObject.Desc - - -StaticObject = {} --#StaticObject diff --git a/Moose Development/Moose/Dcs/DCSTask.lua b/Moose Development/Moose/Dcs/DCSTask.lua deleted file mode 100644 index 4b4cd277a..000000000 --- a/Moose Development/Moose/Dcs/DCSTask.lua +++ /dev/null @@ -1,15 +0,0 @@ ---- @module DCSTask - - ---- A task descriptor (internal structure for DCS World) --- @type Task --- @field #string id --- @field #Task.param param - ---- @type Task.param - ---- List of @{#Task} --- @type TaskArray --- @list <#Task> - -env.info( "Task defined" ) diff --git a/Moose Development/Moose/Dcs/DCSTime.lua b/Moose Development/Moose/Dcs/DCSTime.lua deleted file mode 100644 index d70d337ec..000000000 --- a/Moose Development/Moose/Dcs/DCSTime.lua +++ /dev/null @@ -1,8 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSTime - ---- @type ModelTime --- @extends #number - ---- @type Time --- @extends #number \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCSTypes.lua b/Moose Development/Moose/Dcs/DCSTypes.lua deleted file mode 100644 index 176725302..000000000 --- a/Moose Development/Moose/Dcs/DCSTypes.lua +++ /dev/null @@ -1,246 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSTypes - - - ---- Time is given in seconds. --- @type Time --- @extends #number - ---- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. --- @type ModelTime --- @extends #number - ---- Mission time is a model time plus time of the mission start. --- @type MissionTime --- @extends #number - - ---- Distance is given in meters. --- @type Distance --- @extends #number - ---- Angle is given in radians. --- @type Angle --- @extends #number - ---- Azimuth is an angle of rotation around world axis y counter-clockwise. --- @type Azimuth --- @extends #number - ---- Mass is given in kilograms. --- @type Mass --- @extends #number - ---- Vec3 type is a 3D-vector. --- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. --- @type Vec3 --- @field #Distance x is directed to the north --- @field #Distance z is directed to the east --- @field #Distance y is directed up - ---- Vec2 is a 2D-vector for the ground plane as a reference plane. --- @type Vec2 --- @field #Distance x Vec2.x = Vec3.x --- @field #Distance y Vec2.y = Vec3.z - ---- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: --- @type Position3 --- @field #Vec3 p --- @field #Vec3 x --- @field #Vec3 y --- @field #Vec3 z - ---- 3-dimensional box. --- @type Box3 --- @field #Vec3 min --- @field #Vec3 max - ---- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". --- @type TypeName --- @extends #string - ---- AttributeName = string --- Each object type may have attributes. --- Attributes are enlisted in ./Scripts/Database/db_attributes.Lua. --- To know what attributes the object type has, look for the unit type script in sub-directories planes/, helicopter/s, vehicles, navy/ of ./Scripts/Database/ directory. --- @type AttributeName --- @extends #string - ---- List of @{#AttributeName} --- @type AttributeNameArray --- @list <#AttributeName> - ---- @type AI --- @field #AI.Skill Skill --- @field #AI.Task Task --- @field #AI.Option Option - ---- @type AI.Skill --- @field AVERAGE --- @field GOOD --- @field HIGH --- @field EXCELLENT --- @field PLAYER --- @field CLIENT - ---- @type AI.Task --- @field #AI.Task.WeaponExpend WeaponExpend --- @field #AI.Task.OrbitPattern OrbitPattern --- @field #AI.Task.Designation Designation --- @field #AI.Task.WaypointType WaypointType --- @field #AI.Task.TurnMethod TurnMethod --- @field #AI.Task.AltitudeType AltitudeType --- @field #AI.Task.VehicleFormation VehicleFormation - ---- @type AI.Task.WeaponExpend --- @field ONE --- @field TWO --- @field FOUR --- @field QUARTER --- @field HALF --- @field ALL - ---- @type AI.Task.OrbitPattern --- @field CIRCLE --- @field RACE_TRACK - ---- @type AI.Task.Designation --- @field NO --- @field AUTO --- @field WP --- @field IR_POINTER --- @field LASER - ---- @type AI.Task.WaypointType --- @field TAKEOFF --- @field TAKEOFF_PARKING --- @field TURNING_POINT --- @field LAND - ---- @type AI.Task.TurnMethod --- @field FLY_OVER_POINT --- @field FIN_POINT - ---- @type AI.Task.AltitudeType --- @field BARO --- @field RADIO - ---- @type AI.Task.VehicleFormation --- @field OFF_ROAD --- @field ON_ROAD --- @field RANK --- @field CONE --- @field DIAMOND --- @field VEE --- @field ECHELON_LEFT --- @field ECHELON_RIGHT - ---- @type AI.Option --- @field #AI.Option.Air Air --- @field #AI.Option.Ground Ground --- @field #AI.Option.Naval Naval - ---- @type AI.Option.Air --- @field #AI.Option.Air.id id --- @field #AI.Option.Air.val val - ---- @type AI.Option.Ground --- @field #AI.Option.Ground.id id --- @field #AI.Option.Ground.val val - ---- @type AI.Option.Naval --- @field #AI.Option.Naval.id id --- @field #AI.Option.Naval.val val - ---TODO: work on formation ---- @type AI.Option.Air.id --- @field NO_OPTION --- @field ROE --- @field REACTION_ON_THREAT --- @field RADAR_USING --- @field FLARE_USING --- @field FORMATION --- @field RTB_ON_BINGO --- @field SILENCE - ---- @type AI.Option.Air.val --- @field #AI.Option.Air.val.ROE ROE --- @field #AI.Option.Air.val.REACTION_ON_THREAT REACTION_ON_THREAT --- @field #AI.Option.Air.val.RADAR_USING RADAR_USING --- @field #AI.Option.Air.val.FLARE_USING FLARE_USING - ---- @type AI.Option.Air.val.ROE --- @field WEAPON_FREE --- @field OPEN_FIRE_WEAPON_FREE --- @field OPEN_FIRE --- @field RETURN_FIRE --- @field WEAPON_HOLD - ---- @type AI.Option.Air.val.REACTION_ON_THREAT --- @field NO_REACTION --- @field PASSIVE_DEFENCE --- @field EVADE_FIRE --- @field BYPASS_AND_ESCAPE --- @field ALLOW_ABORT_MISSION - ---- @type AI.Option.Air.val.RADAR_USING --- @field NEVER --- @field FOR_ATTACK_ONLY --- @field FOR_SEARCH_IF_REQUIRED --- @field FOR_CONTINUOUS_SEARCH - ---- @type AI.Option.Air.val.FLARE_USING --- @field NEVER --- @field AGAINST_FIRED_MISSILE --- @field WHEN_FLYING_IN_SAM_WEZ --- @field WHEN_FLYING_NEAR_ENEMIES - ---- @type AI.Option.Ground.id --- @field NO_OPTION --- @field ROE @{#AI.Option.Ground.val.ROE} --- @field DISPERSE_ON_ATTACK true or false --- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE} - ---- @type AI.Option.Ground.val --- @field #AI.Option.Ground.val.ROE ROE --- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE - ---- @type AI.Option.Ground.val.ROE --- @field OPEN_FIRE --- @field RETURN_FIRE --- @field WEAPON_HOLD - ---- @type AI.Option.Ground.val.ALARM_STATE --- @field AUTO --- @field GREEN --- @field RED - ---- @type AI.Option.Naval.id --- @field NO_OPTION --- @field ROE - ---- @type AI.Option.Naval.val --- @field #AI.Option.Naval.val.ROE ROE - ---- @type AI.Option.Naval.val.ROE --- @field OPEN_FIRE --- @field RETURN_FIRE --- @field WEAPON_HOLD - -AI = {} --#AI - - ---- @type Desc --- @field #TypeName typeName type name --- @field #string displayName localized display name --- @field #table attributes object type attributes - ---- A distance type --- @type Distance - ---- An angle type --- @type Angle - -env.info( 'AI types created' ) - diff --git a/Moose Development/Moose/Dcs/DCSUnit.lua b/Moose Development/Moose/Dcs/DCSUnit.lua deleted file mode 100644 index e5e86aba2..000000000 --- a/Moose Development/Moose/Dcs/DCSUnit.lua +++ /dev/null @@ -1,241 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSUnit - ---- @type Unit --- @extends Dcs.DCSCoalitionWrapper.Object#CoalitionObject --- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. --- @field #Unit.Category Category --- @field #Unit.RefuelingSystem RefuelingSystem --- @field #Unit.SensorType SensorType --- @field #Unit.OpticType OpticType --- @field #Unit.RadarType RadarType --- @field #Unit.Desc Desc --- @field #Unit.DescAircraft DescAircraft --- @field #Unit.DescAirplane DescAirplane --- @field #Unit.DescHelicopter DescHelicopter --- @field #Unit.DescVehicle DescVehicle --- @field #Unit.DescShip DescShip --- @field #Unit.AmmoItem AmmoItem --- @field #list<#Unit.AmmoItem> Ammo --- @field #Unit.Sensor Sensor --- @field #Unit.Optic Optic --- @field #Unit.Radar Radar --- @field #Unit.IRST IRST - - ---- Enum that stores unit categories. --- @type Unit.Category --- @field AIRPLANE --- @field HELICOPTER --- @field GROUND_UNIT --- @field SHIP --- @field STRUCTURE - ---- Enum that stores aircraft refueling system types. --- @type Unit.RefuelingSystem --- @field BOOM_AND_RECEPTACLE --- @field PROBE_AND_DROGUE - ---- Enum that stores sensor types. --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - ---- Enum that stores types of optic sensors. --- @type Unit.OpticType --- @field TV TV-sensor --- @field LLTV Low-level TV-sensor --- @field IR Infra-Red optic sensor - ---- Enum that stores radar types. --- @type Unit.RadarType --- @field AS air search radar --- @field SS surface/land search radar - - ---- A unit descriptor. --- @type Unit.Desc --- @extends Wrapper.Object#Object.Desc --- @field #Unit.Category category Unit Category --- @field #Mass massEmpty mass of empty unit --- @field #number speedMax istance / Time, --maximal velocity - ---- An aircraft descriptor. --- @type Unit.DescAircraft --- @extends Wrapper.Unit#Unit.Desc --- @field #Mass fuelMassMax maximal inner fuel mass --- @field #Distance range Operational range --- @field #Distance Hmax Ceiling --- @field #number VyMax #Distance / #Time, --maximal climb rate --- @field #number NyMin minimal safe acceleration --- @field #number NyMax maximal safe acceleration --- @field #Unit.RefuelingSystem tankerType refueling system type - ---- An airplane descriptor. --- @type Unit.DescAirplane --- @extends Wrapper.Unit#Unit.DescAircraft --- @field #number speedMax0 Distance / Time maximal TAS at ground level --- @field #number speedMax10K Distance / Time maximal TAS at altitude of 10 km - ---- A helicopter descriptor. --- @type Unit.DescHelicopter --- @extends Wrapper.Unit#Unit.DescAircraft --- @field #Distance HmaxStat static ceiling - ---- A vehicle descriptor. --- @type Unit.DescVehicle --- @extends Wrapper.Unit#Unit.Desc --- @field #Angle maxSlopeAngle maximal slope angle --- @field #boolean riverCrossing can the vehicle cross a rivers - ---- A ship descriptor. --- @type Unit.DescShip --- @extends #Unit.Desc - ---- ammunition item: "type-count" pair. --- @type Unit.AmmoItem --- @field #Weapon.Desc desc ammunition descriptor --- @field #number count ammunition count - ---- A unit sensor. --- @type Unit.Sensor --- @field #TypeName typeName --- @field #Unit.SensorType type - ---- An optic sensor. --- @type Unit.Optic --- @extends Wrapper.Unit#Unit.Sensor --- @field #Unit.OpticType opticType - ---- A radar. --- @type Unit.Radar --- @extends Wrapper.Unit#Unit.Sensor --- @field #Distance detectionDistanceRBM detection distance for RCS=1m^2 in real-beam mapping mode, nil if radar doesn't support surface/land search --- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM --- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search - ---- @type Unit.Radar.detectionDistanceAir --- @field #Unit.Radar.detectionDistanceAir.upperHemisphere upperHemisphere --- @field #Unit.Radar.detectionDistanceAir.lowerHemisphere lowerHemisphere - ---- @type Unit.Radar.detectionDistanceAir.upperHemisphere --- @field #Distance headOn --- @field #Distance tailOn - ---- @type Unit.Radar.detectionDistanceAir.lowerHemisphere --- @field #Distance headOn --- @field #Distance tailOn - ---- An IRST. --- @type Wrapper.Unit#Unit.IRST --- @extends Unit.Sensor --- @field #Distance detectionDistanceIdle detection of tail-on target with heat signature = 1 in upper hemisphere, engines are in idle --- @field #Distance detectionDistanceMaximal ..., engines are in maximal mode --- @field #Distance detectionDistanceAfterburner ..., engines are in afterburner mode - ---- An RWR. --- @type Unit.RWR --- @extends Wrapper.Unit#Unit.Sensor - ---- table that stores all unit sensors. --- TODO @type Sensors --- - - ---- Returns unit object by the name assigned to the unit in Mission Editor. If there is unit with such name or the unit is destroyed the function will return nil. The function provides access to non-activated units too. --- @function [parent=#Unit] getByName --- @param #string name --- @return #Unit - ---- Returns if the unit is activated. --- @function [parent=#Unit] isActive --- @param #Unit self --- @return #boolean - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @function [parent=#Unit] getPlayerName --- @param #Unit self --- @return #string - ---- returns the unit's unique identifier. --- @function [parent=#Unit] getID --- @param #Unit self --- @return #Unit.ID - - ---- Returns the unit's number in the group. The number is the same number the unit has in ME. It may not be changed during the mission. If any unit in the group is destroyed, the numbers of another units will not be changed. --- @function [parent=#Unit] getNumber --- @param #Unit self --- @return #number - ---- Returns controller of the unit if it exist and nil otherwise --- @function [parent=#Unit] getController --- @param #Unit self --- @return #Controller - ---- Returns the unit's group if it exist and nil otherwise --- @function [parent=#Unit] getGroup --- @param #Unit self --- @return Dcs.DCSWrapper.Group#Group - ---- Returns the unit's callsign - the localized string. --- @function [parent=#Unit] getCallsign --- @param #Unit self --- @return #string - ---- Returns the unit's health. Dead units has health <= 1.0 --- @function [parent=#Unit] getLife --- @param #Unit self --- @return #number - ---- returns the unit's initial health. --- @function [parent=#Unit] getLife0 --- @param #Unit self --- @return #number - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @function [parent=#Unit] getFuel --- @param #Unit self --- @return #number - ---- Returns the unit ammunition. --- @function [parent=#Unit] getAmmo --- @param #Unit self --- @return #Unit.Ammo - ---- Returns the unit sensors. --- @function [parent=#Unit] getSensors --- @param #Unit self --- @return #Unit.Sensors - ---- Returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @function [parent=#Unit] hasSensors --- @param #Unit self --- @param #Unit.SensorType sensorType (= nil) Sensor type. --- @param ... Additional parameters. --- @return #boolean --- @usage --- If sensorType is Unit.SensorType.OPTIC, additional parameters are optic sensor types. Following example checks if the unit has LLTV or IR optics: --- unit:hasSensors(Unit.SensorType.OPTIC, Unit.OpticType.LLTV, Unit.OpticType.IR) --- If sensorType is Unit.SensorType.RADAR, additional parameters are radar types. Following example checks if the unit has air search radars: --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) --- If no additional parameters are specified the function returns true if the unit has at least one sensor of specified type. --- If sensor type is not specified the function returns true if the unit has at least one sensor of any type. --- - ---- returns two values: --- First value indicates if at least one of the unit's radar(s) is on. --- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @function [parent=#Unit] getRadar --- @param #Unit self --- @return #boolean, Wrapper.Object#Object - ---- Returns unit descriptor. Descriptor type depends on unit category. --- @function [parent=#Unit] getDesc --- @param #Unit self --- @return #Unit.Desc - - -Unit = {} --#Unit diff --git a/Moose Development/Moose/Dcs/DCSVec3.lua b/Moose Development/Moose/Dcs/DCSVec3.lua deleted file mode 100644 index 6de2c20aa..000000000 --- a/Moose Development/Moose/Dcs/DCSVec3.lua +++ /dev/null @@ -1,11 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSVec3 - - - ---- --- @type Vec3 --- @field #number x --- @field #number y --- @field #number z -Vec3 = {} \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCSZone.lua b/Moose Development/Moose/Dcs/DCSZone.lua deleted file mode 100644 index 4b65bd76e..000000000 --- a/Moose Development/Moose/Dcs/DCSZone.lua +++ /dev/null @@ -1,10 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSZone - - - ---- --- @type Zone --- @field DCSVec3#Vec3 point --- @field #number radius -Zone = {} diff --git a/Moose Development/Moose/Dcs/DCScoalition.lua b/Moose Development/Moose/Dcs/DCScoalition.lua deleted file mode 100644 index 4ec5f6f10..000000000 --- a/Moose Development/Moose/Dcs/DCScoalition.lua +++ /dev/null @@ -1,14 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCScoalition - ---- @type coalition --- @field #coalition.side side - ---- @type coalition.side --- @field NEUTRAL --- @field RED --- @field BLUE - ---- @function [parent=#coalition] getCountryCoalition --- @param #number countryId --- @return #number coalitionId diff --git a/Moose Development/Moose/Dcs/DCScountry.lua b/Moose Development/Moose/Dcs/DCScountry.lua deleted file mode 100644 index e390894cd..000000000 --- a/Moose Development/Moose/Dcs/DCScountry.lua +++ /dev/null @@ -1,27 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCScountry - ---- @type country --- @field #country.id id -country = country -- #country - ---- @type country.id --- @field RUSSIA --- @field UKRAINE --- @field USA --- @field TURKEY --- @field UK --- @field FRANCE --- @field GERMANY --- @field CANADA --- @field SPAIN --- @field THE_NETHERLANDS --- @field BELGIUM --- @field NORWAY --- @field DENMARK --- @field ISRAEL --- @field GEORGIA --- @field INSURGENTS --- @field ABKHAZIA --- @field SOUTH_OSETIA --- @field ITALY diff --git a/Moose Development/Moose/Dcs/DCSenv.lua b/Moose Development/Moose/Dcs/DCSenv.lua deleted file mode 100644 index c6fe98776..000000000 --- a/Moose Development/Moose/Dcs/DCSenv.lua +++ /dev/null @@ -1,27 +0,0 @@ -------------------------------------------------------------------------------- --- @module env - ---- @type env - ---- Add message to simulator log with caption "INFO". Message box is optional. --- @function [parent=#env] info --- @field #string message message string to add to log. --- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - ---- Add message to simulator log with caption "WARNING". Message box is optional. --- @function [parent=#env] warning --- @field #string message message string to add to log. --- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - ---- Add message to simulator log with caption "ERROR". Message box is optional. --- @function [parent=#env] error --- @field #string message message string to add to log. --- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - ---- Enables/disables appearance of message box each time lua error occurs. --- @function [parent=#env] setErrorMessageBoxEnabled --- @field #boolean on if true message box appearance is enabled. - - - -env = {} --#env diff --git a/Moose Development/Moose/Dcs/DCSland.lua b/Moose Development/Moose/Dcs/DCSland.lua deleted file mode 100644 index 6391142af..000000000 --- a/Moose Development/Moose/Dcs/DCSland.lua +++ /dev/null @@ -1,26 +0,0 @@ -------------------------------------------------------------------------------- --- @module land - ---- @type land --- @field #land.SurfaceType SurfaceType - - ---- @type land.SurfaceType --- @field LAND --- @field SHALLOW_WATER --- @field WATER --- @field ROAD --- @field RUNWAY - ---- Returns altitude MSL of the point. --- @function [parent=#land] getHeight --- @param #Vec2 point point on the ground. --- @return Dcs.DCSTypes#Distance - ---- returns surface type at the given point. --- @function [parent=#land] getSurfaceType --- @param #Vec2 point Point on the land. --- @return #land.SurfaceType - - -land = {} --#land \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCStimer.lua b/Moose Development/Moose/Dcs/DCStimer.lua deleted file mode 100644 index 2d0f4a16c..000000000 --- a/Moose Development/Moose/Dcs/DCStimer.lua +++ /dev/null @@ -1,45 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCStimer - ---- @type timer - - ---- Returns model time in seconds. --- @function [parent=#timer] getTime --- @return #Time - ---- Returns mission time in seconds. --- @function [parent=#timer] getAbsTime --- @return #Time - ---- Returns mission start time in seconds. --- @function [parent=#timer] getTime0 --- @return #Time - ---- Schedules function to call at desired model time. --- Time function FunctionToCall(any argument, Time time) --- --- ... --- --- return ... --- --- end --- --- Must return model time of next call or nil. Note that the DCS scheduler calls the function in protected mode and any Lua errors in the called function will be trapped and not reported. If the function triggers a Lua error then it will be terminated and not scheduled to run again. --- @function [parent=#timer] scheduleFunction --- @param #FunctionToCall functionToCall Lua-function to call. Must have prototype of FunctionToCall. --- @param functionArgument Function argument of any type to pass to functionToCall. --- @param #Time time Model time of the function call. --- @return functionId - ---- Re-schedules function to call at another model time. --- @function [parent=#timer] setFunctionTime --- @param functionId Lua-function to call. Must have prototype of FunctionToCall. --- @param #Time time Model time of the function call. - - ---- Removes the function from schedule. --- @function [parent=#timer] removeFunction --- @param functionId Function identifier to remove from schedule - -timer = {} --#timer diff --git a/Moose Development/Moose/Dcs/DCStrigger.lua b/Moose Development/Moose/Dcs/DCStrigger.lua deleted file mode 100644 index 7bf5360be..000000000 --- a/Moose Development/Moose/Dcs/DCStrigger.lua +++ /dev/null @@ -1,8 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCStrigger - - -trigger = {} --#timer - - - \ No newline at end of file diff --git a/Moose Development/Moose/Dcs/DCSworld.lua b/Moose Development/Moose/Dcs/DCSworld.lua deleted file mode 100644 index 5bcb203d2..000000000 --- a/Moose Development/Moose/Dcs/DCSworld.lua +++ /dev/null @@ -1,35 +0,0 @@ -------------------------------------------------------------------------------- --- @module DCSWorld - ---- @type world --- @field #world.event event - - ---- @type world.event --- @field S_EVENT_INVALID --- @field S_EVENT_SHOT --- @field S_EVENT_HIT --- @field S_EVENT_TAKEOFF --- @field S_EVENT_LAND --- @field S_EVENT_CRASH --- @field S_EVENT_EJECTION --- @field S_EVENT_REFUELING --- @field S_EVENT_DEAD --- @field S_EVENT_PILOT_DEAD --- @field S_EVENT_BASE_CAPTURED --- @field S_EVENT_MISSION_START --- @field S_EVENT_MISSION_END --- @field S_EVENT_TOOK_CONTROL --- @field S_EVENT_REFUELING_STOP --- @field S_EVENT_BIRTH --- @field S_EVENT_HUMAN_FAILURE --- @field S_EVENT_ENGINE_STARTUP --- @field S_EVENT_ENGINE_SHUTDOWN --- @field S_EVENT_PLAYER_ENTER_UNIT --- @field S_EVENT_PLAYER_LEAVE_UNIT --- @field S_EVENT_PLAYER_COMMENT --- @field S_EVENT_SHOOTING_START --- @field S_EVENT_SHOOTING_END --- @field S_EVENT_MAX - -world = {} --#world \ No newline at end of file diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index b27fa27f4..af4ac91bf 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -11,8 +11,8 @@ -- -- === -- --- @module ATC_Ground - +-- @module Functional.ATC_Ground +-- @image Air_Traffic_Control_Ground_Operations.JPG --- @type ATC_GROUND -- @field Core.Set#SET_CLIENT SetClient @@ -417,7 +417,7 @@ end -- # Airbases monitored -- -- The following airbases are monitored at the Caucasus region. --- Use the @{Airbase#AIRBASE.Caucasus} enumeration to select the airbases to be monitored. +-- Use the @{Wrapper.Airbase#AIRBASE.Caucasus} enumeration to select the airbases to be monitored. -- -- * `AIRBASE.Caucasus.Anapa_Vityazevo` -- * `AIRBASE.Caucasus.Batumi` @@ -1021,7 +1021,7 @@ end -- # Airbases monitored -- -- The following airbases are monitored at the Nevada region. --- Use the @{Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored. +-- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored. -- -- * `AIRBASE.Nevada.Beatty_Airport` -- * `AIRBASE.Nevada.Boulder_City_Airport` @@ -1561,7 +1561,7 @@ end -- # Airbases monitored -- -- The following airbases are monitored at the Normandy region. --- Use the @{Airbase#AIRBASE.Normandy} enumeration to select the airbases to be monitored. +-- Use the @{Wrapper.Airbase#AIRBASE.Normandy} enumeration to select the airbases to be monitored. -- -- * `AIRBASE.Normandy.Azeville` -- * `AIRBASE.Normandy.Bazenville` diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua new file mode 100644 index 000000000..fa5d346d9 --- /dev/null +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -0,0 +1,4582 @@ +--- **Functional** - (R2.4) Control artillery units. +-- +-- === +-- +-- The ARTY class can be used to easily assign and manage targets for artillery units using an advanced queueing system. +-- +-- ## Features: +-- +-- * Multiple targets can be assigned. No restriction on number of targets. +-- * Targets can be given a priority. Engagement of targets is executed a according to their priority. +-- * Engagements can be scheduled, i.e. will be executed at a certain time of the day. +-- * Multiple relocations of the group can be assigned and scheduled via queueing system. +-- * Special weapon types can be selected for each attack, e.g. cruise missiles for Naval units. +-- * Automatic rearming once the artillery is out of ammo (optional). +-- * Automatic relocation after each firing engagement to prevent counter strikes (optional). +-- * Automatic relocation movements to get the battery within firing range (optional). +-- * Simulation of tactical nuclear shells. +-- * New targets can be added during the mission, e.g. when they are detected by recon units. +-- * Targets and relocations can be assigned by placing markers on the F10 map. +-- * Finite state machine implementation. Mission designer can interact when certain events occur. +-- +-- ==== +-- +-- # Demo Missions +-- +-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS) +-- +-- ==== +-- +-- # YouTube Channel +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- +-- === +-- +-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** +-- +-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536) +-- +-- ==== +-- @module Functional.Arty +-- @image Artillery.JPG + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- ARTY class +-- @type ARTY +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players. +-- @field #table targets All targets assigned. +-- @field #table moves All moves assigned. +-- @field #table currentTarget Holds the current target, if there is one assigned. +-- @field #table currentMove Holds the current commanded move, if there is one assigned. +-- @field #number Nammo0 Initial amount total ammunition (shells+rockets+missiles) of the whole group. +-- @field #number Nshells0 Initial amount of shells of the whole group. +-- @field #number Nrockets0 Initial amount of rockets of the whole group. +-- @field #number Nmissiles0 Initial amount of missiles of the whole group. +-- @field #number Nukes0 Initial amount of tactical nukes of the whole group. Default is 0. +-- @field #number StatusInterval Update interval in seconds between status updates. Default 10 seconds. +-- @field #number WaitForShotTime Max time in seconds to wait until fist shot event occurs after target is assigned. If time is passed without shot, the target is deleted. Default is 300 seconds. +-- @field #table DCSdesc DCS descriptors of the ARTY group. +-- @field #string Type Type of the ARTY group. +-- @field #string DisplayName Extended type name of the ARTY group. +-- @field #number IniGroupStrength Inital number of units in the ARTY group. +-- @field #boolean IsArtillery If true, ARTY group has attribute "Artillery". This is automatically derived from the DCS descriptor table. +-- @field #boolean ismobile If true, ARTY group can move. +-- @field #string groupname Name of the ARTY group as defined in the mission editor. +-- @field #string alias Name of the ARTY group. +-- @field #table clusters Table of names of clusters the group belongs to. Can be used to address all groups within the cluster simultaniously. +-- @field #number SpeedMax Maximum speed of ARTY group in km/h. This is determined from the DCS descriptor table. +-- @field #number Speed Default speed in km/h the ARTY group moves at. Maximum speed possible is 80% of maximum speed the group can do. +-- @field #number RearmingDistance Safe distance in meters between ARTY group and rearming group or place at which rearming is possible. Default 100 m. +-- @field Wrapper.Group#GROUP RearmingGroup Unit designated to rearm the ARTY group. +-- @field #number RearmingGroupSpeed Speed in km/h the rearming unit moves at. Default is 50% of the max speed possible of the group. +-- @field #boolean RearmingGroupOnRoad If true, rearming group will move to ARTY group or rearming place using mainly roads. Default false. +-- @field Core.Point#COORDINATE RearmingGroupCoord Initial coordinates of the rearming unit. After rearming complete, the unit will return to this position. +-- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there. +-- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false. +-- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group. +-- @field #boolean report Arty group sends messages about their current state or target to its coaliton. +-- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells. +-- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets. +-- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles. +-- @field #number Nshots Number of shots fired on current target. +-- @field #number minrange Minimum firing range in kilometers. Targets closer than this distance are not engaged. Default 0.1 km. +-- @field #number maxrange Maximum firing range in kilometers. Targets further away than this distance are not engaged. Default 10000 km. +-- @field #number nukewarhead Explosion strength of tactical nuclear warhead in kg TNT. Default 75000. +-- @field #number Nukes Number of nuclear shells, the group has available. Default is same number as normal shells. Note that if normal shells are empty, firing nukes is also not possible any more. +-- @field #number nukerange Demolition range of tactical nuclear explostions. +-- @field #boolean nukefire Ignite additional fires and smoke for nuclear explosions Default true. +-- @field #number nukefires Number of nuclear fires and subexplosions. +-- @field #boolean relocateafterfire Group will relocate after each firing task. Default false. +-- @field #number relocateRmin Minimum distance in meters the group will look for places to relocate. +-- @field #number relocateRmax Maximum distance in meters the group will look for places to relocate. +-- @field #boolean markallow If true, Players are allowed to assign targets and moves for ARTY group by placing markers on the F10 map. Default is false. +-- @field #number markkey Authorization key. Only player who know this key can assign targets and moves via markers on the F10 map. Default no authorization required. +-- @field #boolean markreadonly Marks for targets are readonly and cannot be removed by players. Default is false. +-- @field #boolean autorelocate ARTY group will automatically move to within the max/min firing range. +-- @field #number autorelocatemaxdist Max distance [m] the ARTY group will travel to get within firing range. Default 50000 m = 50 km. +-- @field #boolean autorelocateonroad ARTY group will use mainly road to automatically get within firing range. Default is false. +-- @extends Core.Fsm#FSM_CONTROLLABLE + +--- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can +-- interact with the process at certain events or states. +-- +-- A new ARTY object can be created with the @{#ARTY.New}(*group*) contructor. +-- The parameter *group* has to be a MOOSE Group object and defines ARTY group. +-- +-- The ARTY FSM process can be started by the @{#ARTY.Start}() command. +-- +-- ## The ARTY Process +-- +-- ![Process](..\Presentations\ARTY\ARTY_Process.png) +-- +-- ### Blue Branch +-- After the FMS process is started the ARTY group will be in the state **CombatReady**. Once a target is assigned the **OpenFire** event will be triggered and the group starts +-- firing. At this point the group in in the state **Firing**. +-- When the defined number of shots has been fired on the current target the event **CeaseFire** is triggered. The group will stop firing and go back to the state **CombatReady**. +-- If another target is defined (or multiple engagements of the same target), the cycle starts anew. +-- +-- ### Violet Branch +-- When the ARTY group runs out of ammunition, the event **Winchester** is triggered and the group enters the state **OutOfAmmo**. +-- In this state, the group is unable to engage further targets. +-- +-- ### Red Branch +-- With the @{#ARTY.SetRearmingGroup}(*group*) command, a special group can be defined to rearm the ARTY group. If this unit has been assigned and the group has entered the state +-- **OutOfAmmo** the event **Rearm** is triggered followed by a transition to the state **Rearming**. +-- If the rearming group is less than 100 meters away from the ARTY group, the rearming process starts. If the rearming group is more than 100 meters away from the ARTY unit, the +-- rearming group is routed to a point 20 to 100 m from the ARTY group. +-- +-- Once the rearming is complete, the **Rearmed** event is triggered and the group enters the state **CombatReady**. At this point targeted can be engaged again. +-- +-- ### Green Branch +-- The ARTY group can be ordered to change its position via the @{#ARTY.AssignMoveCoord}() function as described below. When the group receives the command to move +-- the event **Move** is triggered and the state changes to **Moving**. When the unit arrives to its destination the event **Arrived** is triggered and the group +-- becomes **CombatReady** again. +-- +-- Note, that the ARTY group will not open fire while it is in state **Moving**. This property differentiates artillery from tanks. +-- +-- ### Yellow Branch +-- When a new target is assigned via the @{#ARTY.AssignTargetCoord}() function (see below), the **NewTarget** event is triggered. +-- +-- ## Assigning Targets +-- Assigning targets is a central point of the ARTY class. Multiple targets can be assigned simultanioulsly and are put into a queue. +-- Of course, targets can be added at any time during the mission. For example, once they are detected by a reconnaissance unit. +-- +-- In order to add a target, the function @{#ARTY.AssignTargetCoord}(*coord*, *prio*, *radius*, *nshells*, *maxengage*, *time*, *weapontype*, *name*) has to be used. +-- Only the first parameter *coord* is mandatory while all remaining parameters are all optional. +-- +-- ### Parameters: +-- +-- * *coord*: Coordinates of the target, given as @{Core.Point#COORDINATE} object. +-- * *prio*: Priority of the target. This a number between 1 (high prio) and 100 (low prio). Targets with higher priority are engaged before targets with lower priority. +-- * *radius*: Radius in meters which defines the area the ARTY group will attempt to be hitting. Default is 100 meters. +-- * *nshells*: Number of shots (shells, rockets, missiles) fired by the group at each engagement of a target. Default is 5. +-- * *maxengage*: Number of times a target is engaged. +-- * *time*: Time of day the engagement is schedule in the format "hh:mm:ss" for hh=hours, mm=minutes, ss=seconds. +-- For example "10:15:35". In the case the attack will be executed at a quarter past ten in the morning at the day the mission started. +-- If the engagement should start on the following day the format can be specified as "10:15:35+1", where the +1 denots the following day. +-- This is useful for longer running missions or if the mission starts at 23:00 hours and the attack should be scheduled at 01:00 hours on the following day. +-- Of course, later days are also possible by appending "+2", "+3", etc. +-- **Note** that the time has to be given as a string. So the enclosing quotation marks "" are important. +-- * *weapontype*: Specified the weapon type that should be used for this attack if the ARTY group has multiple weapons to engage the target. +-- For example, this is useful for naval units which carry a bigger arsenal (cannons and missiles). Default is Auto, i.e. DCS logic selects the appropriate weapon type. +-- *name*: A special name can be defined for this target. Default name are the coordinates of the target in LL DMS format. If a name is already given for another target +-- or the same target should be attacked two or more times with different parameters a suffix "#01", "#02", "#03" is automatically appended to the specified name. +-- +-- ## Target Queue +-- In case multiple targets have been defined, it is important to understand how the target queue works. +-- +-- Here, the essential parameters are the priority *prio*, the number of engagements *maxengage* and the scheduled *time* as described above. +-- +-- For example, we have assigned two targets one with *prio*=10 and the other with *prio*=50 and both targets should be engaged three times (*maxengage*=3). +-- Let's first consider the case that none of the targets is scheduled to be executed at a certain time (*time*=nil). +-- The ARTY group will first engage the target with higher priority (*prio*=10). After the engagement is finished, the target with lower priority is attacked. +-- This is because the target with lower prio has been attacked one time less. After the attack on the lower priority task is finished and both targets +-- have been engaged equally often, the target with the higher priority is engaged again. This coninues until a target has engaged three times. +-- Once the maximum number of engagements is reached, the target is deleted from the queue. +-- +-- In other words, the queue is first sorted with respect to the number of engagements and targets with the same number of engagements are sorted with +-- respect to their priority. +-- +-- ### Timed Engagements +-- +-- As mentioned above, targets can be engaged at a specific time of the day via the *time* parameter. +-- +-- If the *time* parameter is specified for a target, the first engagement of that target will happen at that time of the day and not before. +-- This also applies when multiple engagements are requested via the *maxengage* parameter. The first attack will not happen before the specifed time. +-- When that timed attack is finished, the *time* parameter is deleted and the remaining engagements are carried out in the same manner as for untimed targets (described above). +-- +-- Of course, it can happen that a scheduled task should be executed at a time, when another target is already under attack. +-- If the priority of the target is higher than the priority of the current target, then the current attack is cancelled and the engagement of the target with the higher +-- priority is started. +-- +-- By contrast, if the current target has a higher priority than the target scheduled at that time, the current attack is finished before the scheduled attack is started. +-- +-- ## Determining the Amount of Ammo +-- +-- In order to determin when a unit is out of ammo and possible initiate the rearming process it is necessary to know which types of weapons have to be counted. +-- For most artillery unit types, this is simple because they only have one type of weapon and hence ammunition. +-- +-- However, there are more complex scenarios. For example, naval units carry a big arsenal of different ammunition types ranging from various cannon shell types +-- over surface-to-air missiles to cruise missiles. Obviously, not all of these ammo types can be employed for artillery tasks. +-- +-- Unfortunately, there is no easy way to count only those ammo types useable as artillery. Therefore, to keep the implementation general the user +-- can specify the names of the ammo types by the following functions: +-- +-- * @{#ARTY.SetShellTypes}(*tableofnames*): Defines the ammo types for unguided cannons, e.g. *tableofnames*={"weapons.shells"}, i.e. **all** types of shells are counted. +-- * @{#ARTY.SetRocketTypes}(*tableofnames*): Defines the ammo types of unguided rockets, e.g. *tableofnames*={"weapons.nurs"}, i.e. **all** types of rockets are counted. +-- * @{#ARTY.SetMissileTypes}(*tableofnames*): Defines the ammo types of guided missiles, e.g. is *tableofnames*={"weapons.missiles"}, i.e. **all** types of missiles are counted. +-- +-- **Note** that the default parameters "weapons.shells", "weapons.nurs", "weapons.missiles" **should in priciple** capture all the corresponding ammo types. +-- However, the logic searches for the string "weapon.missies" in the ammo type. Especially for missiles, this string is often not contained in the ammo type descriptor. +-- +-- One way to determin which types of ammo the unit carries, one can use the debug mode of the arty class via @{#ARTY.SetDebugON}(). +-- In debug mode, the all ammo types of the group are printed to the monitor as message and can be found in the DCS.log file. +-- +-- ## Empoying Selected Weapons +-- +-- If an ARTY group carries multiple weapons, which can be used for artillery task, a certain weapon type can be selected to attack the target. +-- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function. +-- +-- The enumerator @{#ARTY.WeaponType} has been defined to select a certain weapon type. Supported values are: +-- +-- * @{#ARTY.WeaponType}.Auto: Automatic weapon selection by the DCS logic. This is the default setting. +-- * @{#ARTY.WeaponType}.Cannon: Only cannons are used during the attack. Corresponding ammo type are shells and can be defined by @{#ARTY.SetShellTypes}. +-- * @{#ARTY.WeaponType}.Rockets: Only unguided are used during the attack. Corresponding ammo type are rockets/nurs and can be defined by @{#ARTY.SetRocketTypes}. +-- * @{#ARTY.WeaponType}.CruiseMissile: Only cruise missiles are used during the attack. Corresponding ammo type are missiles and can be defined by @{#ARTY.SetMissileTypes}. +-- * @{#ARTY.WeaponType}.TacticalNukes: Use tactical nuclear shells. This works only with units that have shells and is described below. +-- +-- ## Assigning Moves +-- The ARTY group can be commanded to move. This is done by the @{#ARTY.AssignMoveCoord}(*coord*,*time*,*speed*,*onroad*,*cancel*,*name*) function. +-- With this multiple timed moves of the group can be scheduled easily. By default, these moves will only be executed if the group is state **CombatReady**. +-- +-- ### Parameters +-- +-- * *coord*: Coordinates where the group should move to given as @{Core.Point#COORDINATE} object. +-- * *time*: The time when the move should be executed. This has to be given as a string in the format "hh:mm:ss" (hh=hours, mm=minutes, ss=seconds). +-- * *speed*: Speed of the group in km/h. +-- * *onroad*: If this parameter is set to true, the group uses mainly roads to get to the commanded coordinates. +-- * *cancel*: If set to true, any current engagement of targets is cancelled at the time the move should be executed. +-- * *name*: Can be used to set a user defined name of the move. By default the name is created from the LL DMS coordinates. +-- +-- ## Automatic Rearming +-- +-- If an ARTY group runs out of ammunition, it can be rearmed automatically. +-- +-- ### Rearming Group +-- The first way to activate the automatic rearming is to define a rearming group with the function @{#ARTY.SetRearmingGroup}(*group*). For the blue side, this +-- could be a M181 transport truck and for the red side an Ural-375 truck. +-- +-- Once the ARTY group is out of ammo and the **Rearm** event is triggered, the defined rearming truck will drive to the ARTY group. +-- So the rearming truck does not have to be placed nearby the artillery group. When the rearming is complete, the rearming truck will drive back to its original position. +-- +-- ### Rearming Place +-- The second alternative is to define a rearming place, e.g. a FRAP, airport or any other warehouse. This is done with the function @{#ARTY.SetRearmingPlace}(*coord*). +-- The parameter *coord* specifies the coordinate of the rearming place which should not be further away then 100 meters from the warehouse. +-- +-- When the **Rearm** event is triggered, the ARTY group will move to the rearming place. Of course, the group must be mobil. So for a mortar this rearming procedure would not work. +-- +-- After the rearming is complete, the ARTY group will move back to its original position and resume normal operations. +-- +-- ### Rearming Group **and** Rearming Place +-- If both a rearming group *and* a rearming place are specified like described above, both the ARTY group and the rearming truck will move to the rearming place and meet there. +-- +-- After the rearming is complete, both groups will move back to their original positions. +-- +-- ## Tactical Nukes +-- +-- ARTY groups that can fire shells can also be used to fire tactical nukes. This is achieved by setting the weapon type to **ARTY.WeaponType.TacticalNukes** in the +-- @{#ARTY.AssignTargetCoord}() function. +-- +-- By default, they group does not have any nukes available. To give the group the ability the function @{#ARTY.SetTacNukeShells}(*n*) can be used. +-- This supplies the group with *n* nuclear shells, where *n* is restricted to the number of conventional shells the group can carry. +-- Note that the group must always have convenctional shells left in order to fire a nuclear shell. +-- +-- The default explostion strength is 0.075 kilo tons TNT. The can be changed with the @{#ARTY.SetTacNukeWarhead}(*strength*), where *strength* is given in kilo tons TNT. +-- +-- +-- ## Assignments via Markers on F10 Map +-- +-- Targets and relocations can be assigned by players via placing a mark on the F10 map. The marker text must contain certain keywords. +-- +-- This feature can be turned on with the @{#ARTY.SetMarkAssignmentsOn}(*key*, *readonly*). The parameter *key* is optional. When set, it can be used as PIN, i.e. only +-- players who know the correct key are able to assign and cancel targets or relocations. Default behavior is that all players belonging to the same coalition as the +-- ARTY group are able to assign targets and moves without a key. +-- +-- ### Target Assignments +-- A new target can be assigned by writing **arty engage** in the marker text. +-- This is followed by a **comma separated list** of (optional) keywords and parameters. +-- First, it is important to address the ARTY group or groups that should engage. This can be done in numrous ways. The keywords are *battery*, *alias*, *cluster*. +-- It is also possible to address all ARTY groups by the keyword *everyone* or *allbatteries*. These two can be used synonymously. +-- **Note that**, if no battery is assigned nothing will happen. +-- +-- * *everyone* or *allbatteries* The target is assigned to all batteries. +-- * *battery* Name of the ARTY group that the target is assigned to. Note that **the name is case sensitive** and has to be given in quotation marks. Default is all ARTY groups of the right coalition. +-- * *alias* Alias of the ARTY group that the target is assigned to. The alias is **case sensitive** and needs to be in quotation marks. +-- * *cluster* The cluster of ARTY groups that is addessed. Clusters can be defined by the function @{#ARTY.AddToCluster}(*clusters*). Names are **case sensitive** and need to be in quotation marks. +-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. +-- * *time* Time for which which the engagement is schedules, e.g. 08:42. Default is as soon as possible. +-- * *prio* Priority of the engagement as number between 1 (high prio) and 100 (low prio). Default is 50, i.e. medium priority. +-- * *shots* Number of shots (shells, rockets or missiles) fired at each engagement. Default is 5. +-- * *maxengage* Number of times the target is engaged. Default is 1. +-- * *radius* Scattering radius of the fired shots in meters. Default is 100 m. +-- * *weapon* Type of weapon to be used. Valid parameters are *cannon*, *rocket*, *missile*, *nuke*. Default is automatic selection. +-- * *lldms* Specify the coordinates in Lat/Long degrees, minutes and seconds format. The actual location of the marker is unimportant here. The group will engage the coordinates given in the lldms keyword. +-- Format is DD:MM:SS[N,S] DD:MM:SS[W,E]. See example below. This can be useful when coordinates in this format are obtained from elsewhere. +-- * *readonly* The marker is readonly and cannot be deleted by users. Hence, assignment cannot be cancelled by removing the marker. +-- +-- Here are examples of valid marker texts: +-- arty engage, battery "Blue Paladin Alpha" +-- arty engage, everyone +-- arty engage, allbatteries +-- arty engage, alias "Bob", weapon missiles +-- arty engage, cluster "All Mortas" +-- arty engage, cluster "Northern Batteries" "Southern Batteries" +-- arty engage, cluster "Northern Batteries", cluster "Southern Batteries" +-- arty engage, shots 20, prio 10, time 08:15, weapon cannons +-- arty engage, battery "Blue Paladin 1" "Blue MRLS 1", shots 10, time 10:15 +-- arty engage, battery "Blue MRLS 1", key 666 +-- arty engage, battery "Paladin Alpha", weapon nukes, shots 1, time 20:15 +-- arty engage, lldms 41:51:00N 41:47:58E +-- +-- Note that the keywords and parameters are *case insensitve*. Only exception are the battery, alias and cluster names. +-- These must be exactly the same as the names of the goups defined in the mission editor or the aliases and cluster names defined in the script. +-- +-- ### Relocation Assignments +-- +-- Markers can also be used to relocate the group with the keyphrase **arty move**. This is done in a similar way as assigning targets. Here, the (optional) keywords and parameters are: +-- +-- * *time* Time for which which the relocation/move is schedules, e.g. 08:42. Default is as soon as possible. +-- * *speed* The speed in km/h the group will drive at. Default is 70% of its max possible speed. +-- * *on road* Group will use mainly roads. Default is off, i.e. it will go in a straight line from its current position to the assigned coordinate. +-- * *canceltarget* Group will cancel all running firing engagements and immidiately start to move. Default is that group will wait until is current assignment is over. +-- * *battery* Name of the ARTY group that the relocation is assigned to. +-- * *alias* Alias of the ARTY group that the target is assigned to. The alias is **case sensitive** and needs to be in quotation marks. +-- * *cluster* The cluster of ARTY groups that is addessed. Clusters can be defined by the function @{#ARTY.AddToCluster}(*clusters*). Names are **case sensitive** and need to be in quotation marks. +-- * *key* A number to authorize the target assignment. Only specifing the correct number will trigger an engagement. +-- * *lldms* Specify the coordinates in Lat/Long degrees, minutes and seconds format. The actual location of the marker is unimportant. The group will move to the coordinates given in the lldms keyword. +-- Format is DD:MM:SS[N,S] DD:MM:SS[W,E]. See example below. +-- * *readonly* Marker cannot be deleted by users any more. Hence, assignment cannot be cancelled by removing the marker. +-- +-- Here are some examples: +-- arty move, battery "Blue Paladin" +-- arty move, battery "Blue MRLS", canceltarget, speed 10, on road +-- arty move, cluster "mobile", lldms 41:51:00N 41:47:58E +-- arty move, alias "Bob", weapon missiles +-- arty move, cluster "All Howitzer" +-- arty move, cluster "Northern Batteries" "Southern Batteries" +-- arty move, cluster "Northern Batteries", cluster "Southern Batteries" +-- arty move, everyone +-- +-- ### Requests +-- +-- Marks can also be to send requests to the ARTY group. This is done by the keyword **arty request**, which can have the keywords +-- +-- * *target* All assigned targets are reported. +-- * *move* All assigned relocation moves are reported. +-- * *ammo* Current ammunition status is reported. +-- +-- For example +-- arty request, everyone, ammo +-- arty request, battery "Paladin Bravo", targets +-- arty request, cluster "All Mortars", move +-- +-- The actual location of the marker is irrelevant for these requests. +-- +-- ### Cancel +-- +-- Current actions can be cancelled by the keyword **arty cancel**. Actions that can be cancelled are current engagements, relocations and rearming assignments. +-- +-- For example +-- arty cancel, target, battery "Paladin Bravo" +-- arty cancel, move +-- arty cancel, rearming, battery "MRLS Charly" +-- +-- +-- ## Fine Tuning +-- +-- The mission designer has a few options to tailor the ARTY object according to his needs. +-- +-- * @{#ARTY.SetAutoRelocateToFiringRange}(*maxdist*, *onroad*) lets the ARTY group automatically move to within firing range if a current target is outside the min/max firing range. The +-- optional parameter *maxdist* is the maximum distance im km the group will move. If the distance is greater no relocation is performed. Default is 50 km. +-- * @{#ARTY.SetAutoRelocateAfterEngagement}(*rmax*, *rmin*) will cause the ARTY group to change its position after each firing assignment. +-- Optional parameters *rmax*, *rmin* define the max/min distance for relocation of the group. Default distance is randomly between 300 and 800 m. +-- * @{#ARTY.AddToCluster}(*clusters*) Can be used to add the ARTY group to one or more clusters. All groups in a cluster can be addressed simultaniously with one marker command. +-- * @{#ARTY.RemoveAllTargets}() removes all targets from the target queue. +-- * @{#ARTY.RemoveTarget}(*name*) deletes the target with *name* from the target queue. +-- * @{#ARTY.SetMaxFiringRange}(*range*) defines the maximum firing range. Targets further away than this distance are not engaged. +-- * @{#ARTY.SetMinFiringRange}(*range*) defines the minimum firing range. Targets closer than this distance are not engaged. +-- * @{#ARTY.SetRearmingGroup}(*group*) sets the group responsible for rearming of the ARTY group once it is out of ammo. +-- * @{#ARTY.SetReportON}() and @{#ARTY.SetReportOFF}() can be used to enable/disable status reports of the ARTY group send to all coalition members. +-- * @{#ARTY.SetWaitForShotTime}(*waittime*) sets the time after which a target is deleted from the queue if no shooting event occured after the target engagement started. +-- Default is 300 seconds. Note that this can for example happen, when the assigned target is out of range. +-- * @{#ARTY.SetDebugON}() and @{#ARTY.SetDebugOFF}() can be used to enable/disable the debug mode. +-- +-- ## Examples +-- +-- ### Assigning Multiple Targets +-- This basic example illustrates how to assign multiple targets and defining a rearming group. +-- -- Creat a new ARTY object from a Paladin group. +-- paladin=ARTY:New(GROUP:FindByName("Blue Paladin")) +-- +-- -- Define a rearming group. This is a Transport M818 truck. +-- paladin:SetRearmingGroup(GROUP:FindByName("Blue Ammo Truck")) +-- +-- -- Set the max firing range. A Paladin unit has a range of 20 km. +-- paladin:SetMaxFiringRange(20) +-- +-- -- Low priorty (90) target, will be engage last. Target is engaged two times. At each engagement five shots are fired. +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 3"):GetCoordinate(), 90, nil, 5, 2) +-- -- Medium priorty (nil=50) target, will be engage second. Target is engaged two times. At each engagement ten shots are fired. +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), nil, nil, 10, 2) +-- -- High priorty (10) target, will be engage first. Target is engaged three times. At each engagement twenty shots are fired. +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 2"):GetCoordinate(), 10, nil, 20, 3) +-- +-- -- Start ARTY process. +-- paladin:Start() +-- **Note** +-- +-- * If a parameter should be set to its default value, it has to be set to *nil* if other non-default parameters follow. Parameters at the end can simply be skiped. +-- * In this example, the target coordinates are taken from groups placed in the mission edit using the COORDINATE:GetCoordinate() function. +-- +-- ### Scheduled Engagements +-- -- Mission starts at 8 o'clock. +-- -- Assign two scheduled targets. +-- +-- -- Create ARTY object from Paladin group. +-- paladin=ARTY:New(GROUP:FindByName("Blue Paladin")) +-- +-- -- Assign target coordinates. Priority=50 (medium), radius=100 m, use 5 shells per engagement, engage 1 time at two past 8 o'clock. +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), 50, 100, 5, 1, "08:02:00", ARTY.WeaponType.Auto, "Target 1") +-- +-- -- Assign target coordinates. Priority=10 (high), radius=300 m, use 10 shells per engagement, engage 1 time at seven past 8 o'clock. +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 2"):GetCoordinate(), 10, 300, 10, 1, "08:07:00", ARTY.WeaponType.Auto, "Target 2") +-- +-- -- Start ARTY process. +-- paladin:Start() +-- +-- ### Specific Weapons +-- This example demonstrates how to use specific weapons during an engagement. +-- -- Define the Normandy as ARTY object. +-- normandy=ARTY:New(GROUP:FindByName("Normandy")) +-- +-- -- Add target: prio=50, radius=300 m, number of missiles=20, number of engagements=1, start time=08:05 hours, only use cruise missiles for this attack. +-- normandy:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), 20, 300, 50, 1, "08:01:00", ARTY.WeaponType.CruiseMissile) +-- +-- -- Add target: prio=50, radius=300 m, number of shells=100, number of engagements=1, start time=08:15 hours, only use cannons during this attack. +-- normandy:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), 50, 300, 100, 1, "08:15:00", ARTY.WeaponType.Cannon) +-- +-- -- Define shells that are counted to check whether the ship is out of ammo. +-- -- Note that this is necessary because the Normandy has a lot of other shell type weapons which cannot be used to engage ground targets in an artillery style manner. +-- normandy:SetShellTypes({"MK45_127"}) +-- +-- -- Define missile types that are counted. +-- normandy:SetMissileTypes({"BGM"}) +-- +-- -- Start ARTY process. +-- normandy:Start() +-- +-- +-- @field #ARTY +ARTY={ + ClassName="ARTY", + Debug=false, + targets={}, + moves={}, + currentTarget=nil, + currentMove=nil, + Nammo0=0, + Nshells0=0, + Nrockets0=0, + Nmissiles0=0, + Nukes0=0, + StatusInterval=10, + WaitForShotTime=300, + DCSdesc=nil, + Type=nil, + DisplayName=nil, + groupname=nil, + alias=nil, + clusters={}, + ismobile=true, + IniGroupStrength=0, + IsArtillery=nil, + RearmingDistance=100, + RearmingGroup=nil, + RearmingGroupSpeed=nil, + RearmingGroupOnRoad=false, + RearmingGroupCoord=nil, + RearmingPlaceCoord=nil, + RearmingArtyOnRoad=false, + InitialCoord=nil, + report=true, + ammoshells={}, + ammorockets={}, + ammomissiles={}, + Nshots=0, + minrange=300, + maxrange=1000000, + nukewarhead=75000, + Nukes=nil, + nukefire=false, + nukefires=nil, + nukerange=nil, + relocateafterfire=false, + relocateRmin=300, + relocateRmax=800, + markallow=false, + markkey=nil, + markreadonly=false, + autorelocate=false, + autorelocatemaxdist=50000, + autorelocateonroad=false, +} + +--- Weapong type ID. http://wiki.hoggit.us/view/DCS_enum_weapon_flag +-- @list WeaponType +ARTY.WeaponType={ + Auto=1073741822, + Cannon=805306368, + Rockets=30720, + --UnguidedAny=805339120, + --GuidedMissile=268402688, + CruiseMissile=2097152, + --AntiShipMissile=65536, + TacticalNukes=666, +} + +--- Database of common artillery unit properties. +-- @list db +ARTY.db={ + ["2B11 mortar"] = { -- type "2B11 mortar" + minrange = 500, -- correct? + maxrange = 7000, -- 7 km + reloadtime = 30, -- 30 sec + }, + ["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika" + minrange = 300, -- correct? + maxrange = 15000, -- 15 km + reloadtime = nil, -- unknown + }, + ["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta" + minrange = 300, -- correct? + maxrange = 23500, -- 23.5 km + reloadtime = nil, -- unknown + }, + ["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia" + minrange = 300, -- correct? + maxrange = 17000, -- 17 km + reloadtime = nil, -- unknown + }, + ["SPH 2S9 Nona"] = { --type "SAU 2-C9" + minrange = 500, -- correct? + maxrange = 7000, -- 7 km + reloadtime = nil, -- unknown + }, + ["SPH M109 Paladin"] = { -- type "M-109", alias "M109" + minrange = 300, -- correct? + maxrange = 22000, -- 22 km + reloadtime = nil, -- unknown + }, + ["SpGH Dana"] = { -- type "SpGH_Dana" + minrange = 300, -- correct? + maxrange = 18700, -- 18.7 km + reloadtime = nil, -- unknown + }, + ["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" + minrange = 5000, -- 5 km + maxrange = 19000, -- 19 km + reloadtime = 420, -- 7 min + }, + ["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" + minrange = 11500, -- 11.5 km + maxrange = 35800, -- 35.8 km + reloadtime = 840, -- 14 min + }, + ["MLRS 9A52 Smerch"] = { -- type "Smerch" + minrange = 20000, -- 20 km + maxrange = 70000, -- 70 km + reloadtime = 2160, -- 36 min + }, + ["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" + minrange = 10000, -- 10 km + maxrange = 32000, -- 32 km + reloadtime = 540, -- 9 min + }, +} + +--- Some ID to identify who we are in output of the DCS.log file. +-- @field #string id +ARTY.id="ARTY | " + +--- Arty script version. +-- @field #string version +ARTY.version="1.0.2" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO list: +-- DONE: Delete targets from queue user function. +-- DONE: Delete entire target queue user function. +-- DONE: Add weapon types. Done but needs improvements. +-- DONE: Add user defined rearm weapon types. +-- DONE: Check if target is in range. Maybe this requires a data base with the ranges of all arty units. +-- DONE: Make ARTY move to rearming position. +-- DONE: Check that right rearming vehicle is specified. Blue M818, Red Ural-375. Are there more? +-- DONE: Check if ARTY group is still alive. +-- DONE: Handle dead events. +-- DONE: Abort firing task if no shooting event occured with 5(?) minutes. Something went wrong then. Min/max range for example. +-- DONE: Improve assigned time for engagement. Next day? +-- DONE: Improve documentation. +-- DONE: Add pseudo user transitions. OnAfter... +-- DONE: Make reaming unit a group. +-- DONE: Write documenation. +-- DONE: Add command move to make arty group move. +-- DONE: remove schedulers for status event. +-- DONE: Improve handling of special weapons. When winchester if using selected weapons? +-- TODO: Handle rearming for ships. How? +-- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. +-- TODO: Add set commands via markers. E.g. set rearming place. +-- DONE: Test stationary types like mortas ==> rearming etc. +-- TODO: Add hit event and make the arty group relocate. +-- TODO: Add illumination and smoke. + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Creates a new ARTY object. +-- @param #ARTY self +-- @param Wrapper.Group#GROUP group The GROUP object for which artillery tasks should be assigned. +-- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name. +-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group. +function ARTY:New(group, alias) + BASE:F2(group) + + -- Inherits from FSM_CONTROLLABLE + local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #ARTY + + -- Check that group is present. + if group then + self:T(ARTY.id..string.format("ARTY script version %s. Added group %s.", ARTY.version, group:GetName())) + else + self:E(ARTY.id.."ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)") + return nil + end + + -- Check that we actually have a GROUND group. + if group:IsGround()==false and group:IsShip()==false then + self:E(ARTY.id..string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!", group:GetName())) + return nil + end + + -- Set the controllable for the FSM. + self:SetControllable(group) + + -- Set the group name + self.groupname=group:GetName() + + -- Set an alias name. + if alias~=nil then + self.alias=tostring(alias) + else + self.alias=self.groupname + end + + -- Set the initial coordinates of the ARTY group. + self.InitialCoord=group:GetCoordinate() + + -- Get DCS descriptors of group. + local DCSgroup=Group.getByName(group:GetName()) + local DCSunit=DCSgroup:getUnit(1) + self.DCSdesc=DCSunit:getDesc() + + -- DCS descriptors. + self:T3(ARTY.id.."DCS descriptors for group "..group:GetName()) + for id,desc in pairs(self.DCSdesc) do + self:T3({id=id, desc=desc}) + end + + -- Maximum speed in km/h. + self.SpeedMax=group:GetSpeedMax() + + -- Group is mobile or not (e.g. mortars). + if self.SpeedMax>1 then + self.ismobile=true + else + self.ismobile=false + end + + -- Set speed to 0.7 of maximum. + self.Speed=self.SpeedMax * 0.7 + + -- Displayed name (similar to type name below) + self.DisplayName=self.DCSdesc.displayName + + -- Is this infantry or not. + self.IsArtillery=DCSunit:hasAttribute("Artillery") + + -- Type of group. + self.Type=group:GetTypeName() + + -- Initial group strength. + self.IniGroupStrength=#group:GetUnits() + + --------------- + -- Transitions: + --------------- + + -- Entry. + self:AddTransition("*", "Start", "CombatReady") + + -- Blue branch. + self:AddTransition("CombatReady", "OpenFire", "Firing") + self:AddTransition("Firing", "CeaseFire", "CombatReady") + + -- Violett branch. + self:AddTransition("CombatReady", "Winchester", "OutOfAmmo") + + -- Red branch. + self:AddTransition({"CombatReady", "OutOfAmmo"}, "Rearm", "Rearming") + self:AddTransition("Rearming", "Rearmed", "Rearmed") + + -- Green branch. + self:AddTransition("*", "Move", "Moving") + self:AddTransition("Moving", "Arrived", "Arrived") + + -- Yellow branch. + self:AddTransition("*", "NewTarget", "*") + + -- Not in diagram. + self:AddTransition("*", "CombatReady", "CombatReady") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "NewMove", "*") + self:AddTransition("*", "Dead", "*") + + -- Unknown transitons. To be checked if adding these causes problems. + self:AddTransition("Rearming", "Arrived", "Rearming") + self:AddTransition("Rearming", "Move", "Rearming") + + + --- User function for OnAfter "NewTarget" event. + -- @function [parent=#ARTY] OnAfterNewTarget + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfter "OpenFire" event. + -- @function [parent=#ARTY] OnAfterOpenFire + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfter "CeaseFire" event. + -- @function [parent=#ARTY] OnAfterCeaseFire + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table target Array holding the target info. + + --- User function for OnAfer "NewMove" event. + -- @function [parent=#ARTY] OnAfterNewMove + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table move Array holding the move info. + + --- User function for OnAfer "Move" event. + -- @function [parent=#ARTY] OnAfterMove + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table move Array holding the move info. + + --- User function for OnAfer "Arrived" event. + -- @function [parent=#ARTY] OnAfterArrvied + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Winchester" event. + -- @function [parent=#ARTY] OnAfterWinchester + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Rearm" event. + -- @function [parent=#ARTY] OnAfterRearm + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Rearmed" event. + -- @function [parent=#ARTY] OnAfterRearmed + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Start" event. + -- @function [parent=#ARTY] OnAfterStart + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Status" event. + -- @function [parent=#ARTY] OnAfterStatus + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnAfter "Dead" event. + -- @function [parent=#ARTY] OnAfterDead + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- User function for OnEnter "CombatReady" state. + -- @function [parent=#ARTY] OnEnterCombatReady + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Firing" state. + -- @function [parent=#ARTY] OnEnterFiring + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "OutOfAmmo" state. + -- @function [parent=#ARTY] OnEnterOutOfAmmo + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Rearming" state. + -- @function [parent=#ARTY] OnEnterRearming + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Rearmed" state. + -- @function [parent=#ARTY] OnEnterRearmed + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- User function for OnEnter "Moving" state. + -- @function [parent=#ARTY] OnEnterMoving + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Function to start the ARTY FSM process. + -- @function [parent=#ARTY] Start + -- @param #ARTY self + + --- Function to start the ARTY FSM process after a delay. + -- @function [parent=#ARTY] __Start + -- @param #ARTY self + -- @param #number Delay before start in seconds. + + --- Function to update the status of the ARTY group and tigger FSM events. Triggers the FSM event "Status". + -- @function [parent=#ARTY] Status + -- @param #ARTY self + + --- Function to update the status of the ARTY group and tigger FSM events after a delay. Triggers the FSM event "Status". + -- @function [parent=#ARTY] __Status + -- @param #ARTY self + -- @param #number Delay in seconds. + + --- Function called when a unit of the ARTY group died. Triggers the FSM event "Dead". + -- @function [parent=#ARTY] Dead + -- @param #ARTY self + + --- Function called when a unit of the ARTY group died after a delay. Triggers the FSM event "Dead". + -- @function [parent=#ARTY] __Dead + -- @param #ARTY self + -- @param #number Delay in seconds. + + --- Add a new target for the ARTY group. Triggers the FSM event "NewTarget". + -- @function [parent=#ARTY] NewTarget + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Add a new target for the ARTY group with a delay. Triggers the FSM event "NewTarget". + -- @function [parent=#ARTY] __NewTarget + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Add a new relocation move for the ARTY group. Triggers the FSM event "NewMove". + -- @function [parent=#ARTY] NewMove + -- @param #ARTY self + -- @param #table move Array holding the relocation move data. + + --- Add a new relocation for the ARTY group after a delay. Triggers the FSM event "NewMove". + -- @function [parent=#ARTY] __NewMove + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table move Array holding the relocation move data. + + --- Order ARTY group to open fire on a target. Triggers the FSM event "OpenFire". + -- @function [parent=#ARTY] OpenFire + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Order ARTY group to open fire on a target with a delay. Triggers the FSM event "Move". + -- @function [parent=#ARTY] __OpenFire + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Order ARTY group to cease firing on a target. Triggers the FSM event "CeaseFire". + -- @function [parent=#ARTY] CeaseFire + -- @param #ARTY self + -- @param #table target Array holding the target data. + + --- Order ARTY group to cease firing on a target after a delay. Triggers the FSM event "CeaseFire". + -- @function [parent=#ARTY] __CeaseFire + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table target Array holding the target data. + + --- Order ARTY group to move to another location. Triggers the FSM event "Move". + -- @function [parent=#ARTY] Move + -- @param #ARTY self + -- @param #table move Array holding the relocation move data. + + --- Order ARTY group to move to another location after a delay. Triggers the FSM event "Move". + -- @function [parent=#ARTY] __Move + -- @param #ARTY self + -- @param #number delay Delay in seconds. + -- @param #table move Array holding the relocation move data. + + --- Tell ARTY group it has arrived at its destination. Triggers the FSM event "Arrived". + -- @function [parent=#ARTY] Arrived + -- @param #ARTY self + + --- Tell ARTY group it has arrived at its destination after a delay. Triggers the FSM event "Arrived". + -- @function [parent=#ARTY] __Arrived + -- @param #ARTY self + -- @param #number delay Delay in seconds. + + --- Tell ARTY group it is combat ready. Triggers the FSM event "CombatReady". + -- @function [parent=#ARTY] CombatReady + -- @param #ARTY self + + --- Tell ARTY group it is combat ready after a delay. Triggers the FSM event "CombatReady". + -- @function [parent=#ARTY] __CombatReady + -- @param #ARTY self + -- @param #number delay Delay in seconds. + + --- Tell ARTY group it is out of ammo. Triggers the FSM event "Winchester". + -- @function [parent=#ARTY] Winchester + -- @param #ARTY self + + --- Tell ARTY group it is out of ammo after a delay. Triggers the FSM event "Winchester". + -- @function [parent=#ARTY] __Winchester + -- @param #ARTY self + -- @param #number delay Delay in seconds. + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Assign target coordinates to the ARTY group. Only the first parameter, i.e. the coordinate of the target is mandatory. The remaining parameters are optional and can be used to fine tune the engagement. +-- @param #ARTY self +-- @param Core.Point#COORDINATE coord Coordinates of the target. +-- @param #number prio (Optional) Priority of target. Number between 1 (high) and 100 (low). Default 50. +-- @param #number radius (Optional) Radius. Default is 100 m. +-- @param #number nshells (Optional) How many shells (or rockets) are fired on target per engagement. Default 5. +-- @param #number maxengage (Optional) How many times a target is engaged. Default 1. +-- @param #string time (Optional) Day time at which the target should be engaged. Passed as a string in format "08:13:45". Current task will be canceled. +-- @param #number weapontype (Optional) Type of weapon to be used to attack this target. Default ARTY.WeaponType.Auto, i.e. the DCS logic automatically determins the appropriate weapon. +-- @param #string name (Optional) Name of the target. Default is LL DMS coordinate of the target. If the name was already given, the numbering "#01", "#02",... is appended automatically. +-- @param #boolean unique (Optional) Target is unique. If the target name is already known, the target is rejected. Default false. +-- @return #string Name of the target. Can be used for further reference, e.g. deleting the target from the list. +-- @usage paladin=ARTY:New(GROUP:FindByName("Blue Paladin")) +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), 10, 300, 10, 1, "08:02:00", ARTY.WeaponType.Auto, "Target 1") +-- paladin:Start() +function ARTY:AssignTargetCoord(coord, prio, radius, nshells, maxengage, time, weapontype, name, unique) + self:F({coord=coord, prio=prio, radius=radius, nshells=nshells, maxengage=maxengage, time=time, weapontype=weapontype, name=name, unique=unique}) + + -- Set default values. + nshells=nshells or 5 + radius=radius or 100 + maxengage=maxengage or 1 + prio=prio or 50 + prio=math.max( 1, prio) + prio=math.min(100, prio) + if unique==nil then + unique=false + end + weapontype=weapontype or ARTY.WeaponType.Auto + + -- Check if we have a coordinate object. + local text=nil + if coord:IsInstanceOf("GROUP") then + text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a GROUP. Converting to COORDINATE..." + coord=coord:GetCoordinate() + elseif coord:IsInstanceOf("UNIT") then + text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a UNIT. Converting to COORDINATE..." + coord=coord:GetCoordinate() + elseif coord:IsInstanceOf("POSITIONABLE") then + text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a POSITIONABLE. Converting to COORDINATE..." + coord=coord:GetCoordinate() + elseif coord:IsInstanceOf("COORDINATE") then + -- Nothing to do here. + else + text="ERROR: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter!" + MESSAGE:New(text, 30):ToAll() + self:E(ARTY.id..text) + return nil + end + if text~=nil then + self:E(ARTY.id..text) + end + + -- Name of the target. + local _name=name or coord:ToStringLLDMS() + local _unique=true + + -- Check if the name has already been used for another target. If so, the function returns a new unique name. + _name,_unique=self:_CheckName(self.targets, _name, not unique) + + -- Target name should be unique and is not. + if unique==true and _unique==false then + self:T(ARTY.id..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!", self.groupname, _name)) + return nil + end + + -- Time in seconds. + local _time=self:_ClockToSeconds(time) + + -- Prepare target array. + local _target={name=_name, coord=coord, radius=radius, nshells=nshells, engaged=0, underfire=false, prio=prio, maxengage=maxengage, time=_time, weapontype=weapontype} + + -- Add to table. + table.insert(self.targets, _target) + + -- Trigger new target event. + self:__NewTarget(1, _target) + + return _name +end + +--- Assign coordinate to where the ARTY group should move. +-- @param #ARTY self +-- @param Core.Point#COORDINATE coord Coordinates of the new position. +-- @param #string time (Optional) Day time at which the group should start moving. Passed as a string in format "08:13:45". Default is now. +-- @param #number speed (Optinal) Speed in km/h the group should move at. Default 70% of max posible speed of group. +-- @param #boolean onroad (Optional) If true, group will mainly use roads. Default off, i.e. go directly towards the specified coordinate. +-- @param #boolean cancel (Optional) If true, cancel any running attack when move should begin. Default is false. +-- @param #string name (Optional) Name of the coordinate. Default is LL DMS string of the coordinate. If the name was already given, the numbering "#01", "#02",... is appended automatically. +-- @param #boolean unique (Optional) Move is unique. If the move name is already known, the move is rejected. Default false. +-- @return #string Name of the move. Can be used for further reference, e.g. deleting the move from the list. +function ARTY:AssignMoveCoord(coord, time, speed, onroad, cancel, name, unique) + self:F({coord=coord, time=time, speed=speed, onroad=onroad, cancel=cancel, name=name, unique=unique}) + + -- Reject move if the group is immobile. + if not self.ismobile then + self:T(ARTY.id..string.format("%s: group is immobile. Rejecting move request!", self.groupname)) + return nil + end + + -- Default + if unique==nil then + unique=false + end + + -- Name of the target. + local _name=name or coord:ToStringLLDMS() + local _unique=true + + -- Check if the name has already been used for another target. If so, the function returns a new unique name. + _name,_unique=self:_CheckName(self.moves, _name, not unique) + + -- Move name should be unique and is not. + if unique==true and _unique==false then + self:T(ARTY.id..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!", self.groupname, _name)) + return nil + end + + -- Default is current time if no time was specified. + time=time or self:_SecondsToClock(timer.getAbsTime()) + + -- Set speed. + if speed then + -- Make sure, given speed is less than max physiaclly possible speed of group. + speed=math.min(speed, self.SpeedMax) + elseif self.Speed then + speed=self.Speed + else + speed=self.SpeedMax*0.7 + end + + -- Default is off road. + if onroad==nil then + onroad=false + end + + -- Default is not to cancel a running attack. + if cancel==nil then + cancel=false + end + + -- Time in seconds. + local _time=self:_ClockToSeconds(time) + + -- Prepare move array. + local _move={name=_name, coord=coord, time=_time, speed=speed, onroad=onroad, cancel=cancel} + + -- Add to table. + table.insert(self.moves, _move) + + return _name +end + +--- Set alias, i.e. the name the group will use when sending messages. +-- @param #ARTY self +-- @param #string alias The alias for the group. +function ARTY:SetAlias(alias) + self:F({alias=alias}) + self.alias=tostring(alias) +end + +--- Add ARTY group to one or more clusters. Enables addressing all ARTY groups within a cluster simultaniously via marker assignments. +-- @param #ARTY self +-- @param #table clusters Table of cluster names the group should belong to. +function ARTY:AddToCluster(clusters) + self:F({clusters=clusters}) + + -- Convert input to table. + local names + if type(clusters)=="table" then + names=clusters + elseif type(clusters)=="string" then + names={clusters} + else + -- error message + self:E(ARTY.id.."ERROR: Input parameter must be a string or a table in ARTY:AddToCluster()!") + return + end + + -- Add names to cluster array. + for _,cluster in pairs(names) do + table.insert(self.clusters, cluster) + end +end + +--- Set minimum firing range. Targets closer than this distance are not engaged. +-- @param #ARTY self +-- @param #number range Min range in kilometers. Default is 0.1 km. +function ARTY:SetMinFiringRange(range) + self:F({range=range}) + self.minrange=range*1000 or 100 +end + +--- Set maximum firing range. Targets further away than this distance are not engaged. +-- @param #ARTY self +-- @param #number range Max range in kilometers. Default is 1000 km. +function ARTY:SetMaxFiringRange(range) + self:F({range=range}) + self.maxrange=range*1000 or 1000*1000 +end + +--- Set time interval between status updates. During the status check, new events are triggered. +-- @param #ARTY self +-- @param #number interval Time interval in seconds. Default 10 seconds. +function ARTY:SetStatusInterval(interval) + self:F({interval=interval}) + self.StatusInterval=interval or 10 +end + +--- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed. +-- @param #ARTY self +-- @param #number waittime Time in seconds. Default 300 seconds. +function ARTY:SetWaitForShotTime(waittime) + self:F({waittime=waittime}) + self.WaitForShotTime=waittime or 300 +end + +--- Define the safe distance between ARTY group and rearming unit or rearming place at which rearming process is possible. +-- @param #ARTY self +-- @param #number distance Safe distance in meters. Default is 100 m. +function ARTY:SetRearmingDistance(distance) + self:F({distance=distance}) + self.RearmingDistance=distance or 100 +end + +--- Assign a group, which is responsible for rearming the ARTY group. If the group is too far away from the ARTY group it will be guided towards the ARTY group. +-- @param #ARTY self +-- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. For the blue coalition, this is often a unarmed M818 transport whilst for red an unarmed Ural-375 transport can be used. +function ARTY:SetRearmingGroup(group) + self:F({group=group}) + self.RearmingGroup=group +end + +--- Set the speed the rearming group moves at towards the ARTY group or the rearming place. +-- @param #ARTY self +-- @param #number speed Speed in km/h. +function ARTY:SetRearmingGroupSpeed(speed) + self:F({speed=speed}) + self.RearmingGroupSpeed=speed +end + +--- Define if rearming group uses mainly roads to drive to the ARTY group or rearming place. +-- @param #ARTY self +-- @param #boolean onroad If true, rearming group uses mainly roads. If false, it drives directly to the ARTY group or rearming place. +function ARTY:SetRearmingGroupOnRoad(onroad) + self:F({onroad=onroad}) + if onroad==nil then + onroad=true + end + self.RearmingGroupOnRoad=onroad +end + +--- Define if ARTY group uses mainly roads to drive to the rearming place. +-- @param #ARTY self +-- @param #boolean onroad If true, ARTY group uses mainly roads. If false, it drives directly to the rearming place. +function ARTY:SetRearmingArtyOnRoad(onroad) + self:F({onroad=onroad}) + if onroad==nil then + onroad=true + end + self.RearmingArtyOnRoad=onroad +end + +--- Defines the rearming place of the ARTY group. If the place is too far away from the ARTY group it will be routed to the place. +-- @param #ARTY self +-- @param Core.Point#COORDINATE coord Coordinates of the rearming place. +function ARTY:SetRearmingPlace(coord) + self:F({coord=coord}) + self.RearmingPlaceCoord=coord +end + +--- Set automatic relocation of ARTY group if a target is assigned which is out of range. The unit will drive automatically towards or away from the target to be in max/min firing range. +-- @param #ARTY self +-- @param #number maxdistance (Optional) The maximum distance in km the group will travel to get within firing range. Default is 50 km. No automatic relocation is performed if targets are assigned which are further away. +-- @param #boolean onroad (Optional) If true, ARTY group uses roads whenever possible. Default false, i.e. group will move in a straight line to the assigned coordinate. +function ARTY:SetAutoRelocateToFiringRange(maxdistance, onroad) + self:F({distance=maxdistance, onroad=onroad}) + self.autorelocate=true + self.autorelocatemaxdist=maxdistance or 50 + self.autorelocatemaxdist=self.autorelocatemaxdist*1000 + if onroad==nil then + onroad=false + end + self.autorelocateonroad=onroad +end + +--- Set relocate after firing. Group will find a new location after each engagement. Default is off +-- @param #ARTY self +-- @param #number rmax (Optional) Max distance in meters, the group will move to relocate. Default is 800 m. +-- @param #number rmin (Optional) Min distance in meters, the group will move to relocate. Default is 300 m. +function ARTY:SetAutoRelocateAfterEngagement(rmax, rmin) + self.relocateafterfire=true + self.relocateRmax=rmax or 800 + self.relocateRmin=rmin or 300 + + -- Ensure that Rmin<=Rmax + self.relocateRmin=math.min(self.relocateRmin, self.relocateRmax) +end + +--- Report messages of ARTY group turned on. This is the default. +-- @param #ARTY self +function ARTY:SetReportON() + self.report=true +end + +--- Report messages of ARTY group turned off. Default is on. +-- @param #ARTY self +function ARTY:SetReportOFF() + self.report=false +end + +--- Turn debug mode on. Information is printed to screen. +-- @param #ARTY self +function ARTY:SetDebugON() + self.Debug=true +end + +--- Turn debug mode off. This is the default setting. +-- @param #ARTY self +function ARTY:SetDebugOFF() + self.Debug=false +end + +--- Delete a target from target list. If the target is currently engaged, it is cancelled. +-- @param #ARTY self +-- @param #string name Name of the target. +function ARTY:RemoveTarget(name) + self:F2(name) + + -- Get target ID from namd + local id=self:_GetTargetIndexByName(name) + + if id then + + -- Remove target from table. + self:T(ARTY.id..string.format("Group %s: Removing target %s (id=%d).", self.groupname, name, id)) + table.remove(self.targets, id) + + -- Delete marker belonging to this engagement. + if self.markallow then + local batteryname,markTargetID, markMoveID=self:_GetMarkIDfromName(name) + if batteryname==self.groupname and markTargetID~=nil then + COORDINATE:RemoveMark(markTargetID) + end + end + + end + self:T(ARTY.id..string.format("Group %s: Number of targets = %d.", self.groupname, #self.targets)) +end + +--- Delete a move from move list. +-- @param #ARTY self +-- @param #string name Name of the target. +function ARTY:RemoveMove(name) + self:F2(name) + + -- Get move ID from name. + local id=self:_GetMoveIndexByName(name) + + if id then + + -- Remove move from table. + self:T(ARTY.id..string.format("Group %s: Removing move %s (id=%d).", self.groupname, name, id)) + table.remove(self.moves, id) + + -- Delete marker belonging to this relocation move. + if self.markallow then + local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) + if batteryname==self.groupname and markMoveID~=nil then + COORDINATE:RemoveMark(markMoveID) + end + end + + end + self:T(ARTY.id..string.format("Group %s: Number of moves = %d.", self.groupname, #self.moves)) +end + +--- Delete ALL targets from current target list. +-- @param #ARTY self +function ARTY:RemoveAllTargets() + self:F2() + for _,target in pairs(self.targets) do + self:RemoveTarget(target.name) + end +end + +--- Define shell types that are counted to determine the ammo amount the ARTY group has. +-- @param #ARTY self +-- @param #table tableofnames Table of shell type names. +function ARTY:SetShellTypes(tableofnames) + self:F2(tableofnames) + self.ammoshells={} + for _,_type in pairs(tableofnames) do + table.insert(self.ammoshells, _type) + end +end + +--- Define rocket types that are counted to determine the ammo amount the ARTY group has. +-- @param #ARTY self +-- @param #table tableofnames Table of rocket type names. +function ARTY:SetRocketTypes(tableofnames) + self:F2(tableofnames) + self.ammorockets={} + for _,_type in pairs(tableofnames) do + table.insert(self.ammorockets, _type) + end +end + +--- Define missile types that are counted to determine the ammo amount the ARTY group has. +-- @param #ARTY self +-- @param #table tableofnames Table of rocket type names. +function ARTY:SetMissileTypes(tableofnames) + self:F2(tableofnames) + self.ammomissiles={} + for _,_type in pairs(tableofnames) do + table.insert(self.ammomissiles, _type) + end +end + +--- Set number of tactical nuclear warheads available to the group. +-- Note that it can be max the number of normal shells. Also if all normal shells are empty, firing nuclear shells is also not possible any more until group gets rearmed. +-- @param #ARTY self +-- @param #number n Number of warheads for the whole group. +function ARTY:SetTacNukeShells(n) + self.Nukes=n +end + +--- Set nuclear warhead explosion strength. +-- @param #ARTY self +-- @param #number strength Explosion strength in kilo tons TNT. Default is 0.075 kt. +function ARTY:SetTacNukeWarhead(strength) + self.nukewarhead=strength or 0.075 + self.nukewarhead=self.nukewarhead*1000*1000 -- convert to kg TNT. +end + +--- Set nuclear fires and extra demolition explosions. +-- @param #ARTY self +-- @param #number nfires (Optional) Number of big smoke and fire objects created in the demolition zone. +-- @param #number demolitionrange (Optional) Demolition range in meters. +function ARTY:SetTacNukeFires(nfires, range) + self.nukefire=true + self.nukefires=nfires + self.nukerange=range +end + +--- Enable assigning targets and moves by placing markers on the F10 map. +-- @param #ARTY self +-- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. +-- @param #boolean readonly (Optional) Marks are readonly and cannot be removed by players. This also means that targets cannot be cancelled by removing the mark. Default false. +function ARTY:SetMarkAssignmentsOn(key, readonly) + self.markkey=key + self.markallow=true + if readonly==nil then + self.markreadonly=false + end +end + +--- Disable assigning targets by placing markers on the F10 map. +-- @param #ARTY self +function ARTY:SetMarkTargetsOff() + self.markallow=false + self.markkey=nil +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Start Event +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterStart(Controllable, From, Event, To) + self:_EventFromTo("onafterStart", Event, From, To) + + -- Debug output. + local text=string.format("Started ARTY version %s for group %s.", ARTY.version, Controllable:GetName()) + self:E(ARTY.id..text) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + + -- Get Ammo. + self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0=self:GetAmmo(self.Debug) + + -- Init nuclear explosion parameters if they were not set by user. + if self.nukerange==nil then + self.nukerange=1500/75000*self.nukewarhead -- linear dependence + end + if self.nukefires==nil then + self.nukefires=20/1000/1000*self.nukerange*self.nukerange + end + if self.Nukes~=nil then + self.Nukes0=math.min(self.Nukes, self.Nshells0) + else + self.Nukes=0 + self.Nukes0=0 + end + + -- Check if we have and arty type that is in the DB. + local _dbproperties=self:_CheckDB(self.DisplayName) + self:T({dbproperties=_dbproperties}) + if _dbproperties~=nil then + for property,value in pairs(_dbproperties) do + self:T({property=property, value=value}) + self[property]=value + end + end + + -- Some mobility consitency checks if group cannot move. + if not self.ismobile then + self.RearmingPlaceCoord=nil + self.relocateafterfire=false + self.autorelocate=false + self.RearmingGroupSpeed=20 + end + + -- Set Rearming group speed if not specified by user + if self.RearmingGroup then + + -- Get max speed of rearming group. + local speedmax=self.RearmingGroup:GetSpeedMax() + self:T(ARTY.id..string.format("%s, rearming group %s max speed = %.1f km/h.", self.groupname, self.RearmingGroup:GetName(), speedmax)) + + if self.RearmingGroupSpeed==nil then + -- Set rearming group speed to 50% of max possible speed. + self.RearmingGroupSpeed=speedmax*0.5 + else + -- Ensure that speed is <= max speed. + self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed, self.RearmingGroup:GetSpeedMax()) + end + else + -- Just to have a reasonable number for output format below. + self.RearmingGroupSpeed=23 + end + + local text=string.format("\n******************************************************\n") + text=text..string.format("Arty group = %s\n", self.groupname) + text=text..string.format("Arty alias = %s\n", self.alias) + text=text..string.format("Artillery attribute = %s\n", tostring(self.IsArtillery)) + text=text..string.format("Type = %s\n", self.Type) + text=text..string.format("Display Name = %s\n", self.DisplayName) + text=text..string.format("Number of units = %d\n", self.IniGroupStrength) + text=text..string.format("Speed max = %d km/h\n", self.SpeedMax) + text=text..string.format("Speed default = %d km/h\n", self.Speed) + text=text..string.format("Is mobile = %s\n", tostring(self.ismobile)) + text=text..string.format("Min range = %.1f km\n", self.minrange/1000) + text=text..string.format("Max range = %.1f km\n", self.maxrange/1000) + text=text..string.format("Total ammo count = %d\n", self.Nammo0) + text=text..string.format("Number of shells = %d\n", self.Nshells0) + text=text..string.format("Number of rockets = %d\n", self.Nrockets0) + text=text..string.format("Number of missiles = %d\n", self.Nmissiles0) + text=text..string.format("Number of nukes = %d\n", self.Nukes0) + text=text..string.format("Nuclear warhead = %d tons TNT\n", self.nukewarhead/1000) + text=text..string.format("Nuclear demolition = %d m\n", self.nukerange) + text=text..string.format("Nuclear fires = %d (active=%s)\n", self.nukefires, tostring(self.nukefire)) + if self.RearmingGroup or self.RearmingPlaceCoord then + text=text..string.format("Rearming safe dist. = %d m\n", self.RearmingDistance) + end + if self.RearmingGroup then + text=text..string.format("Rearming group = %s\n", self.RearmingGroup:GetName()) + text=text..string.format("Rearming group speed= %d km/h\n", self.RearmingGroupSpeed) + text=text..string.format("Rearming group roads= %s\n", tostring(self.RearmingGroupOnRoad)) + end + if self.RearmingPlaceCoord then + local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord) + text=text..string.format("Rearming coord dist = %d m\n", dist) + text=text..string.format("Rearming ARTY roads = %s\n", tostring(self.RearmingArtyOnRoad)) + end + text=text..string.format("Relocate after fire = %s\n", tostring(self.relocateafterfire)) + text=text..string.format("Relocate min dist. = %d m\n", self.relocateRmin) + text=text..string.format("Relocate max dist. = %d m\n", self.relocateRmax) + text=text..string.format("Auto move in range = %s\n", tostring(self.autorelocate)) + text=text..string.format("Auto move dist. max = %.1f km\n", self.autorelocatemaxdist/1000) + text=text..string.format("Auto move on road = %s\n", tostring(self.autorelocateonroad)) + text=text..string.format("Marker assignments = %s\n", tostring(self.markallow)) + text=text..string.format("Marker auth. key = %s\n", tostring(self.markkey)) + text=text..string.format("Marker readonly = %s\n", tostring(self.markreadonly)) + text=text..string.format("Clusters:\n") + for _,cluster in pairs(self.clusters) do + text=text..string.format("- %s\n", tostring(cluster)) + end + text=text..string.format("******************************************************\n") + text=text..string.format("Targets:\n") + for _, target in pairs(self.targets) do + text=text..string.format("- %s\n", self:_TargetInfo(target)) + local possible=self:_CheckWeaponTypePossible(target) + if not possible then + self:E(ARTY.id..string.format("WARNING: Selected weapon type %s is not possible", self:_WeaponTypeName(target.weapontype))) + end + if self.Debug then + local zone=ZONE_RADIUS:New(target.name, target.coord:GetVec2(), target.radius) + zone:BoundZone(180, coalition.side.NEUTRAL) + end + end + text=text..string.format("Moves:\n") + for i=1,#self.moves do + text=text..string.format("- %s\n", self:_MoveInfo(self.moves[i])) + end + text=text..string.format("******************************************************\n") + text=text..string.format("Shell types:\n") + for _,_type in pairs(self.ammoshells) do + text=text..string.format("- %s\n", _type) + end + text=text..string.format("Rocket types:\n") + for _,_type in pairs(self.ammorockets) do + text=text..string.format("- %s\n", _type) + end + text=text..string.format("Missile types:\n") + for _,_type in pairs(self.ammomissiles) do + text=text..string.format("- %s\n", _type) + end + text=text..string.format("******************************************************") + if self.Debug then + self:E(ARTY.id..text) + else + self:T(ARTY.id..text) + end + + -- Set default ROE to weapon hold. + self.Controllable:OptionROEHoldFire() + + -- Add event handler. + self:HandleEvent(EVENTS.Shot, self._OnEventShot) + self:HandleEvent(EVENTS.Dead, self._OnEventDead) + --self:HandleEvent(EVENTS.MarkAdded, self._OnEventMarkAdded) + + -- Add DCS event handler - necessary for S_EVENT_MARK_* events. So we only start it, if this was requested. + if self.markallow then + world.addEventHandler(self) + end + + -- Start checking status. + self:__Status(self.StatusInterval) +end + +--- Check the DB for properties of the specified artillery unit type. +-- @param #ARTY self +-- @return #table Properties of the requested artillery type. Returns nil if no matching DB entry could be found. +function ARTY:_CheckDB(displayname) + for _type,_properties in pairs(ARTY.db) do + self:T({type=_type, properties=_properties}) + if _type==displayname then + self:T({type=_type, properties=_properties}) + return _properties + end + end + return nil +end + +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #ARTY self +-- @param #boolean display (Optional) If true, send message to coalition. Default false. +function ARTY:_StatusReport(display) + + -- Set default. + if display==nil then + display=false + end + + -- Get Ammo. + local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() + local Nnukes=self.Nukes + + local Tnow=timer.getTime() + local Clock=self:_SecondsToClock(timer.getAbsTime()) + + local text=string.format("\n******************* STATUS ***************************\n") + text=text..string.format("ARTY group = %s\n", self.groupname) + text=text..string.format("Clock = %s\n", Clock) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Total ammo count = %d\n", Nammo) + text=text..string.format("Number of shells = %d\n", Nshells) + text=text..string.format("Number of rockets = %d\n", Nrockets) + text=text..string.format("Number of missiles = %d\n", Nmissiles) + text=text..string.format("Number of nukes = %d\n", Nnukes) + if self.currentTarget then + text=text..string.format("Current Target = %s\n", tostring(self.currentTarget.name)) + text=text..string.format("Curr. Tgt assigned = %d\n", Tnow-self.currentTarget.Tassigned) + else + text=text..string.format("Current Target = %s\n", "none") + end + text=text..string.format("Nshots curr. Target = %d\n", self.Nshots) + text=text..string.format("Targets:\n") + for i=1,#self.targets do + text=text..string.format("- %s\n", self:_TargetInfo(self.targets[i])) + end + if self.currentMove then + text=text..string.format("Current Move = %s\n", tostring(self.currentMove.name)) + else + text=text..string.format("Current Move = %s\n", "none") + end + text=text..string.format("Moves:\n") + for i=1,#self.moves do + text=text..string.format("- %s\n", self:_MoveInfo(self.moves[i])) + end + text=text..string.format("******************************************************") + env.info(ARTY.id..text) + MESSAGE:New(text, 20):Clear():ToCoalitionIf(self.Controllable:GetCoalition(), display) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Handling +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Eventhandler for shot event. +-- @param #ARTY self +-- @param Core.Event#EVENTDATA EventData +function ARTY:_OnEventShot(EventData) + self:F(EventData) + + -- Weapon data. + local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName + local _weaponStrArray = self:_split(_weapon,"%.") + local _weaponName = _weaponStrArray[#_weaponStrArray] + + -- Debug info. + self:T3(ARTY.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName) + self:T3(ARTY.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName) + self:T3(ARTY.id.."EVENT SHOT: Weapon type = ".._weapon) + self:T3(ARTY.id.."EVENT SHOT: Weapon name = ".._weaponName) + + local group = EventData.IniGroup --Wrapper.Group#GROUP + + if group and group:IsAlive() then + + if EventData.IniGroupName == self.groupname then + + if self.currentTarget then + + -- Increase number of shots fired by this group on this target. + self.Nshots=self.Nshots+1 + + -- Debug output. + local text=string.format("%s, fired shot %d of %d with weapon %s on target %s.", self.alias, self.Nshots, self.currentTarget.nshells, _weaponName, self.currentTarget.name) + self:T(ARTY.id..text) + MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug) + + -- Last known position of the weapon fired. + local _lastpos={x=0, y=0, z=0} + + --- Track the position of the weapon if it is supposed to model a tac nuke. + -- @param #table _weapon + local function _TrackWeapon(_weapon) + + -- When the pcall status returns false the weapon has hit. + local _status,_currpos = pcall( + function() + return _weapon:getPoint() + end) + + self:T(ARTY.id..string.format("ARTY %s: Weapon still in air: %s", self.groupname, tostring(_status))) + + if _status then + + -- Update last position. + _lastpos={x=_currpos.x, y=_currpos.y, z=_currpos.z} + + -- Check again in 0.05 seconds. + --return timer.getTime() + self.dtBombtrack + return timer.getTime() + 0.05 + + else + + local _impactcoord=COORDINATE:NewFromVec3(_lastpos) + + -- Create a "nuclear" explosion and blast at the impact point. + SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0) + + end + + end + + -- Start track the shell if we want to model a tactical nuke. + if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0 then + self:T(ARTY.id..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname)) + timer.scheduleFunction(_TrackWeapon, EventData.weapon, timer.getTime() + 2.0) + end + + -- Get current ammo. + local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() + + -- Decrease available nukes because we just fired one. + if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then + self.Nukes=self.Nukes-1 + end + + -- Check if we are completely out of ammo. + local _outofammo=false + if _nammo==0 then + self:T(ARTY.id..string.format("Group %s completely out of ammo.", self.groupname)) + _outofammo=true + end + + -- Check if we are out of ammo of the weapon type used for this target. + -- Note that should not happen because we only open fire with the available number of shots. + local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget}) + + -- Weapon type name for current target. + local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype) + self:T(ARTY.id..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.groupname, _nammo, _nshells, _nrockets, _nmissiles)) + self:T(ARTY.id..string.format("Group %s uses weapontype %s for current target.", self.groupname, _weapontype)) + + -- Default switches for cease fire and relocation. + local _ceasefire=false + local _relocate=false + + -- Check if number of shots reached max. + if self.Nshots >= self.currentTarget.nshells then + + -- Debug message + local text=string.format("Group %s stop firing on target %s.", self.groupname, self.currentTarget.name) + self:T(ARTY.id..text) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + + -- Cease fire. + _ceasefire=true + + -- Relocate if enabled. + _relocate=self.relocateafterfire + end + + -- Check if we are (partly) out of ammo. + if _outofammo or _partlyoutofammo then + _ceasefire=true + end + + -- Relocate position. + if _relocate then + self:_Relocate() + end + + -- Cease fire on current target. + if _ceasefire then + self:CeaseFire(self.currentTarget) + end + + -- Group is out of ammo (or partly and can rearm) ==> Winchester (==> Rearm). + if _outofammo or (_partlyoutofammo and self.RearmingGroup ~=nil) then + --self:Winchester() + --return + end + + else + self:E(ARTY.id..string.format("WARNING: No current target for group %s?!", self.groupname)) + end + end + end +end + + +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #ARTY self +-- @param #table Event +function ARTY:onEvent(Event) + + if Event == nil or Event.idx == nil then + self:T3("Skipping onEvent. Event or Event.idx unknown.") + return true + end + + -- Set battery and coalition. + --local batteryname=self.groupname + --local batterycoalition=self.Controllable:GetCoalition() + + self:T2(string.format("Event captured = %s", tostring(self.groupname))) + self:T2(string.format("Event id = %s", tostring(Event.id))) + self:T2(string.format("Event time = %s", tostring(Event.time))) + self:T2(string.format("Event idx = %s", tostring(Event.idx))) + self:T2(string.format("Event coalition = %s", tostring(Event.coalition))) + self:T2(string.format("Event group id = %s", tostring(Event.groupID))) + self:T2(string.format("Event text = %s", tostring(Event.text))) + if Event.initiator~=nil then + local _unitname=Event.initiator:getName() + self:T2(string.format("Event ini unit name = %s", tostring(_unitname))) + end + + if Event.id==world.event.S_EVENT_MARK_ADDED then + self:T2({event="S_EVENT_MARK_ADDED", battery=self.groupname, vec3=Event.pos}) + + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + self:T({event="S_EVENT_MARK_CHANGE", battery=self.groupname, vec3=Event.pos}) + + -- Handle event. + self:_OnEventMarkChange(Event) + + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + self:T2({event="S_EVENT_MARK_REMOVED", battery=self.groupname, vec3=Event.pos}) + + -- Hande event. + self:_OnEventMarkRemove(Event) + end + +end + +--- Function called when a F10 map mark was removed. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkRemove(Event) + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + --local batteryname=self.groupname + + if Event.text~=nil and Event.text:find("BATTERY") then + + -- Init defaults. + local _cancelmove=false + local _canceltarget=false + local _name="" + local _id=nil + + -- Check for key phrases of relocation or engagements in marker text. If not, return. + if Event.text:find("Marked Relocation") then + _cancelmove=true + _name=self:_MarkMoveName(Event.idx) + _id=self:_GetMoveIndexByName(_name) + elseif Event.text:find("Marked Target") then + _canceltarget=true + _name=self:_MarkTargetName(Event.idx) + _id=self:_GetTargetIndexByName(_name) + else + return + end + + -- Check if there is a task which matches. + if _id==nil then + return + end + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Authentify key + local _validkey=self:_MarkerKeyAuthentification(Event.text) + + -- Check if we have the right coalition. + if _validkey then + + -- This should be the unique name of the target or move. + if _cancelmove then + if self.currentMove and self.currentMove.name==_name then + -- We do clear tasks here because in Arrived() it can cause a CTD if the group did actually arrive! + self.Controllable:ClearTasks() + -- Current move is removed here. In contrast to RemoveTarget() there are is no maxengage parameter. + self:Arrived() + else + -- Remove move from queue + self:RemoveMove(_name) + end + elseif _canceltarget then + if self.currentTarget and self.currentTarget.name==_name then + -- Cease fire. + self:CeaseFire(self.currentTarget) + -- We still need to remove the target, because there might be more planned engagements (maxengage>1). + self:RemoveTarget(_name) + else + -- Remove target from queue + self:RemoveTarget(_name) + end + end + + end + end + end +end + +--- Function called when a F10 map mark was changed. This happens when a user enters text. +-- @param #ARTY self +-- @param #table Event Event data. +function ARTY:_OnEventMarkChange(Event) + + -- Check if marker has a text and the "arty" keyword. + if Event.text~=nil and Event.text:lower():find("arty") then + + -- Get battery coalition and name. + local batterycoalition=self.Controllable:GetCoalition() + local batteryname=self.groupname + + -- Check if the coalition is the same or an authorization key has been defined. + if (batterycoalition==Event.coalition and self.markkey==nil) or self.markkey~=nil then + + -- Evaluate marker text and extract parameters. + local _assign=self:_Markertext(Event.text) + + -- Check if ENGAGE or MOVE or REQUEST keywords were found. + if _assign==nil or not (_assign.engage or _assign.move or _assign.request or _assign.cancel) then + self:T(ARTY.id..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST or CANCEL in mark text! Command will not be executed. Text:\n%s", self.groupname, Event.text)) + return + end + + -- Check if job is assigned to this ARTY group. Default is for all ARTY groups. + local _assigned=false + + -- If any array is filled something has been assigned. + if _assign.everyone then + -- Everyone was addressed. + _assigned=true + else --#_assign.battery>0 or #_assign.aliases>0 or #_assign.cluster>0 then + + -- Loop over batteries. + for _,bat in pairs(_assign.battery) do + if self.groupname==bat then + _assigned=true + end + end + + -- Loop over aliases. + for _,alias in pairs(_assign.aliases) do + if self.alias==alias then + _assigned=true + end + end + + -- Loop over clusters. + for _,bat in pairs(_assign.cluster) do + for _,cluster in pairs(self.clusters) do + if cluster==bat then + _assigned=true + end + end + end + + end + + -- We were not addressed. + if not _assigned then + self:T3(ARTY.id..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s", self.groupname, Event.text)) + return + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=self:_MarkerKeyAuthentification(Event.text) + + -- Handle requests and return. + if _assign.request and _validkey then + if _assign.requestammo then + self:_MarkRequestAmmo() + end + if _assign.requestmoves then + self:_MarkRequestMoves() + end + if _assign.requesttargets then + self:_MarkRequestTargets() + end + if _assign.requeststatus then + self:_MarkRequestStatus() + end + if _assign.requestrearming then + self:Rearm() + end + -- Requests Done ==> End of story! + return + end + + -- Cancel current target and return. + if _assign.cancel and _validkey then + if _assign.cancelmove and self.currentMove then + self.Controllable:ClearTasks() + self:Arrived() + elseif _assign.canceltarget and self.currentTarget then + self.currentTarget.engaged=self.currentTarget.engaged+1 + self:CeaseFire(self.currentTarget) + elseif _assign.cancelrearm and self:is("Rearming") then + local nammo=self:GetAmmo() + if nammo>0 then + self:Rearmed() + else + self:Winchester() + end + end + -- Cancels Done ==> End of story! + return + end + + -- Handle engagements and relocations. + if _validkey then + + -- Convert (wrong x-->z, z-->x) vec3 + -- TODO: This needs to be "fixed", once DCS gives the correct numbers for x and z. + -- local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z} + local vec3={y=Event.pos.y, x=Event.pos.z, z=Event.pos.x} + + -- Get coordinate from vec3. + local _coord=COORDINATE:NewFromVec3(vec3) + + -- Remove old mark because it might contain confidential data such as the key. + -- Also I don't know who can see the mark which was created. + _coord:RemoveMark(Event.idx) + + -- Coordinate was given in text, e.g. as lat, long. + if _assign.coord then + _coord=_assign.coord + end + + -- Anticipate marker ID. + -- WARNING: Make sure, no marks are set until the COORDINATE:MarkToCoalition() is called or the target/move name will be wrong and target cannot be removed by deleting its marker. + local _id=UTILS._MarkID+1 + + if _assign.move then + + -- Create a new name. This determins the string we search when deleting a move! + local _name=self:_MarkMoveName(_id) + + local text=string.format("%s, received new relocation assignment.", self.alias) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a relocation of the arty group. + local _movename=self:AssignMoveCoord(_coord, _assign.time, _assign.speed, _assign.onroad, _assign.movecanceltarget,_name, true) + + if _movename~=nil then + local _mid=self:_GetMoveIndexByName(_movename) + local _move=self.moves[_mid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_move.time)) + local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.", clock, _move.speed, tostring(_move.onroad)) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(100) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + else + local text=string.format("%s, relocation not possible.", self.alias) + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + else + + -- Create a new name. + local _name=self:_MarkTargetName(_id) + + local text=string.format("%s, received new target assignment.", self.alias) + text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) + if _assign.time then + text=text..string.format("\nTime %s",_assign.time) + end + if _assign.prio then + text=text..string.format("\nPrio %d",_assign.prio) + end + if _assign.radius then + text=text..string.format("\nRadius %d m",_assign.radius) + end + if _assign.nshells then + text=text..string.format("\nShots %d",_assign.nshells) + end + if _assign.maxengage then + text=text..string.format("\nEngagements %d",_assign.maxengage) + end + if _assign.weapontype then + text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) + end + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + + -- Assign a new firing engagement. + -- Note, we set unique=true so this target gets only added once. + local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype, _name, true) + + if _targetname~=nil then + local _tid=self:_GetTargetIndexByName(_targetname) + local _target=self.targets[_tid] + + -- Create new target name. + local clock=tostring(self:_SecondsToClock(_target.time)) + local weapon=self:_WeaponTypeName(_target.weapontype) + local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s", _target.prio, _target.radius, _target.nshells, _target.maxengage, weapon, clock) + + -- Create a new mark. This will trigger the mark added event. + local _randomcoord=_coord:GetRandomCoordinateInRadius(250) + _randomcoord:MarkToCoalition(_markertext, batterycoalition, self.markreadonly or _assign.readonly) + end + end + end + + end + end + +end + +--- Event handler for event Dead. +-- @param #ARTY self +-- @param Core.Event#EVENTDATA EventData +function ARTY:_OnEventDead(EventData) + self:F(EventData) + + -- Name of controllable. + local _name=self.groupname + + -- Check for correct group. + if EventData.IniGroupName==_name then + + -- Dead Unit. + self:T2(string.format("%s: Captured dead event for unit %s.", _name, EventData.IniUnitName)) + + -- FSM Dead event. We give one second for update of data base. + self:__Dead(1) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events and States +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Status" event. Report status of group. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterStatus(Controllable, From, Event, To) + self:_EventFromTo("onafterStatus", Event, From, To) + + -- Debug current status info. + if self.Debug then + self:_StatusReport() + end + + -- Group on the move. + if self:is("Moving") then + self:T2(ARTY.id..string.format("%s: Moving", Controllable:GetName())) + end + + -- Group is rearming. + if self:is("Rearming") then + local _rearmed=self:_CheckRearmed() + if _rearmed then + self:T2(ARTY.id..string.format("%s: Rearming ==> Rearmed", Controllable:GetName())) + self:Rearmed() + end + end + + -- Group finished rearming. + if self:is("Rearmed") then + local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) + self:T2(ARTY.id..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m", Controllable:GetName(), distance)) + -- Check that ARTY group is back and set it to combat ready. + if distance <= self.RearmingDistance then + self:T2(ARTY.id..string.format("%s: Rearmed ==> CombatReady", Controllable:GetName())) + self:CombatReady() + end + end + + -- Group arrived at destination. + if self:is("Arrived") then + self:T2(ARTY.id..string.format("%s: Arrived ==> CombatReady", Controllable:GetName())) + self:CombatReady() + end + + -- Group is firing on target. + if self:is("Firing") then + -- Check that firing started after ~5 min. If not, target is removed. + self:_CheckShootingStarted() + end + + -- Check if targets are in range and update target.inrange value. + self:_CheckTargetsInRange() + + -- Check if selected weapon type for target is possible at all. E.g. request rockets for Paladin. + local notpossible={} + for i=1,#self.targets do + local _target=self.targets[i] + local possible=self:_CheckWeaponTypePossible(_target) + if not possible then + table.insert(notpossible, _target.name) + end + end + for _,targetname in pairs(notpossible) do + self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.groupname, targetname)) + self:RemoveTarget(targetname) + end + + -- Get a valid timed target if it is due to be attacked. + local _timedTarget=self:_CheckTimedTargets() + + -- Get a valid normal target (one that is not timed). + local _normalTarget=self:_CheckNormalTargets() + + -- Get a commaned move to another location. + local _move=self:_CheckMoves() + + if _move then + + -- Command to move. + self:Move(_move) + + elseif _timedTarget then + + -- Cease fire on current target first. + if self.currentTarget then + self:CeaseFire(self.currentTarget) + end + + -- Open fire on timed target. + self:OpenFire(_timedTarget) + + elseif _normalTarget then + + -- Open fire on normal target. + self:OpenFire(_normalTarget) + + end + + -- Get ammo. + local nammo, nshells, nrockets, nmissiles=self:GetAmmo() + + -- Check if we have a target in the queue for which weapons are still available. + local gotsome=false + if #self.targets>0 then + for i=1,#self.targets do + local _target=self.targets[i] + if self:_CheckWeaponTypeAvailable(_target)>0 then + gotsome=true + end + end + else + -- No targets in the queue. + gotsome=true + end + + -- No ammo available. Either completely blank or only queued targets for ammo which is out. + if (nammo==0 or not gotsome) and not (self:is("Moving") or self:is("Rearming") or self:is("OutOfAmmo")) then + self:Winchester() + end + + -- Group is out of ammo. + if self:is("OutOfAmmo") then + self:T2(ARTY.id..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming", Controllable:GetName())) + self:Rearm() + end + + -- Call status again in ~10 sec. + self:__Status(self.StatusInterval) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Enter "CombatReady" state. Route the group back if necessary. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onenterCombatReady(Controllable, From, Event, To) + self:_EventFromTo("onenterCombatReady", Event, From, To) + -- Debug info + self:T3(ARTY.id..string.format("onenterComabReady, from=%s, event=%s, to=%s", From, Event, To)) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "OpenFire" event. Checks if group already has a target. Checks for valid min/max range and removes the target if necessary. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table target Array holding the target info. +-- @return #boolean If true, proceed to onafterOpenfire. +function ARTY:onbeforeOpenFire(Controllable, From, Event, To, target) + self:_EventFromTo("onbeforeOpenFire", Event, From, To) + + -- Check that group has no current target already. + if self.currentTarget then + -- This should not happen. Some earlier check failed. + self:E(ARTY.id..string.format("ERROR: Group %s already has a target %s!", self.groupname, self.currentTarget.name)) + -- Deny transition. + return false + end + + -- Check if target is in range. + if not self:_TargetInRange(target) then + -- This should not happen. Some earlier check failed. + self:E(ARTY.id..string.format("ERROR: Group %s, target %s is out of range!", self.groupname, self.currentTarget.name)) + -- Deny transition. + return false + end + + -- Get the number of available shells, rockets or missiles requested for this target. + local nfire=self:_CheckWeaponTypeAvailable(target) + + -- Adjust if less than requested ammo is left. + target.nshells=math.min(target.nshells, nfire) + + -- No ammo left ==> deny transition. + if target.nshells<1 then + local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.") + return false + end + + return true +end + +--- After "OpenFire" event. Sets the current target and starts the fire at point task. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table target Array holding the target info. +function ARTY:onafterOpenFire(Controllable, From, Event, To, target) + self:_EventFromTo("onafterOpenFire", Event, From, To) + + -- Get target array index. + local id=self:_GetTargetIndexByName(target.name) + + -- Target is now under fire and has been engaged once more. + if id then + -- Set under fire flag. + self.targets[id].underfire=true + -- Set current target. + self.currentTarget=target + -- Set time the target was assigned. + self.currentTarget.Tassigned=timer.getTime() + end + + -- Distance to target + local range=Controllable:GetCoordinate():Get2DDistance(target.coord) + + -- Get ammo. + local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() + local nfire=Nammo + local _type="shots" + if target.weapontype==ARTY.WeaponType.Auto then + nfire=Nammo + _type="shots" + elseif target.weapontype==ARTY.WeaponType.Cannon then + nfire=Nshells + _type="shells" + elseif target.weapontype==ARTY.WeaponType.TacticalNukes then + nfire=self.Nukes + _type="nuclear shells" + elseif target.weapontype==ARTY.WeaponType.Rockets then + nfire=Nrockets + _type="rockets" + elseif target.weapontype==ARTY.WeaponType.CruiseMissile then + nfire=Nmissiles + _type="cruise missiles" + end + + -- Adjust if less than requested ammo is left. + target.nshells=math.min(target.nshells, nfire) + + -- Send message. + local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.", Controllable:GetName(), target.name, target.nshells, _type, range/1000) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report) + + --if self.Debug then + -- local _coord=target.coord --Core.Point#COORDINATE + -- local text=string.format("ARTY %s, Target %s, n=%d, weapon=%s", self.Controllable:GetName(), target.name, target.nshells, self:_WeaponTypeName(target.weapontype)) + -- _coord:MarkToAll(text) + --end + + -- Start firing. + self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "CeaseFire" event. Clears task of the group and removes the target if max engagement was reached. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table target Array holding the target info. +function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) + self:_EventFromTo("onafterCeaseFire", Event, From, To) + + if target then + + -- Send message. + local text=string.format("%s, ceasing fire on target %s.", Controllable:GetName(), target.name) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report) + + -- Get target array index. + local id=self:_GetTargetIndexByName(target.name) + + -- We have a target. + if id then + -- Target was actually engaged. (Could happen that engagement was aborted while group was still aiming.) + if self.Nshots>0 then + self.targets[id].engaged=self.targets[id].engaged+1 + -- Clear the attack time. + self.targets[id].time=nil + end + -- Target is not under fire any more. + self.targets[id].underfire=false + end + + -- If number of engagements has been reached, the target is removed. + if target.engaged >= target.maxengage then + self:RemoveTarget(target.name) + end + + -- Set ROE to weapon hold. + self.Controllable:OptionROEHoldFire() + + -- Clear tasks. + self.Controllable:ClearTasks() + + else + self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.groupname) + end + + -- Set number of shots to zero. + self.Nshots=0 + + -- ARTY group has no current target any more. + self.currentTarget=nil + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Winchester" event. Group is out of ammo. Trigger "Rearm" event. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterWinchester(Controllable, From, Event, To) + self:_EventFromTo("onafterWinchester", Event, From, To) + + -- Send message. + local text=string.format("%s, winchester!", Controllable:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Rearm" event. Check if a unit to rearm the ARTY group has been defined. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean If true, proceed to onafterRearm. +function ARTY:onbeforeRearm(Controllable, From, Event, To) + self:_EventFromTo("onbeforeRearm", Event, From, To) + + local _rearmed=self:_CheckRearmed() + if _rearmed then + self:T(ARTY.id..string.format("%s, group is already armed to the teeth. Rearming request denied!", self.groupname)) + return false + else + self:T(ARTY.id..string.format("%s, group might be rearmed.", self.groupname)) + end + + -- Check if a reaming unit or rearming place was specified. + if self.RearmingGroup and self.RearmingGroup:IsAlive() then + return true + elseif self.RearmingPlaceCoord then + return true + else + return false + end + +end + +--- After "Rearm" event. Send message if reporting is on. Route rearming unit to ARTY group. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterRearm(Controllable, From, Event, To) + self:_EventFromTo("onafterRearm", Event, From, To) + + -- Coordinate of ARTY unit. + local coordARTY=self.Controllable:GetCoordinate() + + -- Remember current coordinates so that we find our way back home. + self.InitialCoord=coordARTY + + -- Coordinate of rearming group. + local coordRARM=nil + if self.RearmingGroup then + -- Coordinate of the rearming unit. + coordRARM=self.RearmingGroup:GetCoordinate() + -- Remember the coordinates of the rearming unit. After rearming it will go back to this position. + self.RearmingGroupCoord=coordRARM + end + + if self.RearmingGroup and self.RearmingPlaceCoord and self.SpeedMax>0 then + + -- CASE 1: Rearming unit and ARTY group meet at rearming place. + + -- Send message. + local text=string.format("%s, %s, request rearming at rearming place.", Controllable:GetName(), self.RearmingGroup:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + + -- Distances. + local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) + local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord) + + -- Route ARTY group to rearming place. + if dA > self.RearmingDistance then + local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2) + self:AssignMoveCoord(_tocoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE TO REARMING PLACE", true) + end + + -- Route Rearming group to rearming place. + if dR > self.RearmingDistance then + local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord, self.RearmingDistance/4, self.RearmingDistance/2) + self:_Move(self.RearmingGroup, ToCoord, self.RearmingGroupSpeed, self.RearmingGroupOnRoad) + end + + elseif self.RearmingGroup then + + -- CASE 2: Rearming unit drives to ARTY group. + + -- Send message. + local text=string.format("%s, %s, request rearming.", Controllable:GetName(), self.RearmingGroup:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + + -- Distance between ARTY group and rearming unit. + local distance=coordARTY:Get2DDistance(coordRARM) + + -- If distance is larger than ~100 m, the Rearming unit is routed to the ARTY group. + if distance > self.RearmingDistance then + + -- Route rearming group to ARTY group. + self:_Move(self.RearmingGroup, self:_VicinityCoord(coordARTY), self.RearmingGroupSpeed, self.RearmingGroupOnRoad) + end + + elseif self.RearmingPlaceCoord then + + -- CASE 3: ARTY drives to rearming place. + + -- Send message. + local text=string.format("%s, moving to rearming place.", Controllable:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + + -- Distance. + local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) + + -- Route ARTY group to rearming place. + if dA > self.RearmingDistance then + local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) + self:AssignMoveCoord(_tocoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE TO REARMING PLACE", true) + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Rearmed" event. Send ARTY and rearming group back to their inital positions. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterRearmed(Controllable, From, Event, To) + self:_EventFromTo("onafterRearmed", Event, From, To) + + -- Send message. + local text=string.format("%s, rearming complete.", Controllable:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + + -- "Rearm" tactical nukes as well. + self.Nukes=self.Nukes0 + + -- Route ARTY group back to where it came from (if distance is > 100 m). + local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) + if dist > self.RearmingDistance then + self:AssignMoveCoord(self.InitialCoord, nil, nil, self.RearmingArtyOnRoad, false, "REARMING MOVE REARMING COMPLETE", true) + end + + -- Route unit back to where it came from (if distance is > 100 m). + if self.RearmingGroup and self.RearmingGroup:IsAlive() then + local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord) + if d > self.RearmingDistance then + self:_Move(self.RearmingGroup, self.RearmingGroupCoord, self.RearmingGroupSpeed, self.RearmingGroupOnRoad) + else + -- Clear tasks. + self.RearmingGroup:ClearTasks() + end + end + +end + +--- Check if ARTY group is rearmed, i.e. has its full amount of ammo. +-- @param #ARTY self +-- @return #boolean True if rearming is complete, false otherwise. +function ARTY:_CheckRearmed() + self:F2() + + -- Get current ammo. + local nammo,nshells,nrockets,nmissiles=self:GetAmmo() + + -- Number of units still alive. + local units=self.Controllable:GetUnits() + local nunits=0 + if units then + nunits=#units + end + + -- Full Ammo count. + local FullAmmo=self.Nammo0 * nunits / self.IniGroupStrength + + -- Rearming status in per cent. + local _rearmpc=nammo/FullAmmo*100 + + -- Send message if rearming > 1% complete + if _rearmpc>1 then + local text=string.format("%s, rearming %d %% complete.", self.alias, _rearmpc) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + end + + -- Return if ammo is full. + -- TODO: Strangely, I got the case that a Paladin got one more shell than it can max carry, i.e. 40 not 39 when rearming when it still had some ammo left. Need to report. + if nammo>=FullAmmo then + return true + else + return false + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Move" event. Check if a unit to rearm the ARTY group has been defined. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table move Table containing the move parameters. +-- @param Core.Point#COORDINATE ToCoord Coordinate to which the ARTY group should move. +-- @param #boolean OnRoad If true group should move on road mainly. +-- @return #boolean If true, proceed to onafterMove. +function ARTY:onbeforeMove(Controllable, From, Event, To, move) + self:_EventFromTo("onbeforeMove", Event, From, To) + + -- Check if group can actually move... + if not self.ismobile then + return false + end + + -- Check if group is engaging. + if self.currentTarget then + if move.cancel then + -- Cancel current target. + self:CeaseFire(self.currentTarget) + else + -- We should not cancel. + return false + end + end + + return true +end + +--- After "Move" event. Route group to given coordinate. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table move Table containing the move parameters. +function ARTY:onafterMove(Controllable, From, Event, To, move) + self:_EventFromTo("onafterMove", Event, From, To) + + -- Set alarm state to green and ROE to weapon hold. + self.Controllable:OptionAlarmStateGreen() + self.Controllable:OptionROEHoldFire() + + -- Take care of max speed. + local _Speed=math.min(move.speed, self.SpeedMax) + + -- Smoke coordinate + if self.Debug then + move.coord:SmokeRed() + end + + -- Set current move. + self.currentMove=move + + -- Route group to coodinate. + self:_Move(self.Controllable, move.coord, move.speed, move.onroad) + +end + +--- After "Arrived" event. Group has reached its destination. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterArrived(Controllable, From, Event, To) + self:_EventFromTo("onafterArrived", Event, From, To) + + -- Set alarm state to auto. + self.Controllable:OptionAlarmStateAuto() + + -- WARNING: calling ClearTasks() here causes CTD of DCS when move is over. Dont know why? combotask? + --self.Controllable:ClearTasks() + + -- Send message + local text=string.format("%s, arrived at destination.", Controllable:GetName()) + self:T(ARTY.id..text) + MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + + -- Remove executed move from queue. + if self.currentMove then + self:RemoveMove(self.currentMove.name) + self.currentMove=nil + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "NewTarget" event. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table target Array holding the target parameters. +function ARTY:onafterNewTarget(Controllable, From, Event, To, target) + self:_EventFromTo("onafterNewTarget", Event, From, To) + + -- Debug message. + local text=string.format("Adding new target %s.", target.name) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + self:T(ARTY.id..text) +end + +--- After "NewMove" event. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table move Array holding the move parameters. +function ARTY:onafterNewMove(Controllable, From, Event, To, move) + self:_EventFromTo("onafterNewTarget", Event, From, To) + + -- Debug message. + local text=string.format("Adding new move %s.", move.name) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + self:T(ARTY.id..text) +end + + +--- After "Dead" event, when a unit has died. When all units of a group are dead trigger "Stop" event. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterDead(Controllable, From, Event, To) + self:_EventFromTo("onafterDead", Event, From, To) + + -- Number of units left in the group. + local units=self.Controllable:GetUnits() + local nunits=0 + if units~=nil then + nunits=#units + end + + -- Message. + local text=string.format("%s, one of our units just died! %d units left.", self.groupname, nunits) + MESSAGE:New(text, 5):ToAllIf(self.Debug) + self:T(ARTY.id..text) + + -- Go to stop state. + if nunits==0 then + self:Stop() + end + +end + +--- After "Stop" event. Unhandle events and cease fire on current target. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterStop(Controllable, From, Event, To) + self:_EventFromTo("onafterStop", Event, From, To) + + -- Debug info. + self:T(ARTY.id..string.format("Stopping ARTY FSM for group %s.", Controllable:GetName())) + + -- Cease Fire on current target. + if self.currentTarget then + self:CeaseFire(self.currentTarget) + end + + -- Remove all targets. + --self:RemoveAllTargets() + + -- Unhandle event. + self:UnHandleEvent(EVENTS.Shot) + self:UnHandleEvent(EVENTS.Dead) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set task for firing at a coordinate. +-- @param #ARTY self +-- @param Core.Point#COORDINATE coord Coordinates to fire upon. +-- @param #number radius Radius around coordinate. +-- @param #number nshells Number of shells to fire. +-- @param #number weapontype Type of weapon to use. +function ARTY:_FireAtCoord(coord, radius, nshells, weapontype) + self:F({coord=coord, radius=radius, nshells=nshells}) + + -- Controllable. + local group=self.Controllable --Wrapper.Group#GROUP + + -- Tactical nukes are actually cannon shells. + if weapontype==ARTY.WeaponType.TacticalNukes then + weapontype=ARTY.WeaponType.Cannon + end + + -- Set ROE to weapon free. + group:OptionROEOpenFire() + + -- Get Vec2 + local vec2=coord:GetVec2() + + -- Get task. + local fire=group:TaskFireAtPoint(vec2, radius, nshells, weapontype) + + -- Execute task. + group:SetTask(fire) +end + +--- Model a nuclear blast/destruction by creating fires and destroy scenery. +-- @param #ARTY self +-- @param Core.Point#COORDINATE _coord Coordinate of the impact point (center of the blast). +function ARTY:_NuclearBlast(_coord) + + local S0=self.nukewarhead + local R0=self.nukerange + + -- Number of fires + local N0=self.nukefires + + -- Create an explosion at the last known position. + _coord:Explosion(S0) + + -- Huge fire at direct impact point. + --if self.nukefire then + _coord:BigSmokeAndFireHuge() + --end + + -- Create a table of fire coordinates within the demolition zone. + local _fires={} + for i=1,N0 do + local _fire=_coord:GetRandomCoordinateInRadius(R0) + local _dist=_fire:Get2DDistance(_coord) + table.insert(_fires, {distance=_dist, coord=_fire}) + end + + -- Sort scenery wrt to distance from impact point. + local _sort = function(a,b) return a.distance < b.distance end + table.sort(_fires,_sort) + + local function _explosion(R) + -- At R=R0 ==> explosion strength is 1% of S0 at impact point. + local alpha=math.log(100) + local strength=S0*math.exp(-alpha*R/R0) + self:T2(ARTY.id..string.format("Nuclear explosion strength s(%.1f m) = %.5f (s/s0=%.1f %%), alpha=%.3f", R, strength, strength/S0*100, alpha)) + return strength + end + + local function ignite(_fires) + for _,fire in pairs(_fires) do + local _fire=fire.coord --Core.Point#COORDINATE + + -- Get distance to impact and calc exponential explosion strength. + local R=_fire:Get2DDistance(_coord) + local S=_explosion(R) + self:T2(ARTY.id..string.format("Explosion r=%.1f, s=%.3f", R, S)) + + -- Get a random Big Smoke and fire object. + local _preset=math.random(0,7) + local _density=S/S0 --math.random()+0.1 + + _fire:BigSmokeAndFire(_preset,_density) + _fire:Explosion(S) + + end + end + + if self.nukefire==true then + ignite(_fires) + end + +--[[ + local ZoneNuke=ZONE_RADIUS:New("Nukezone", _coord:GetVec2(), 2000) + + -- Scan for Scenery objects. + ZoneNuke:Scan(Object.Category.SCENERY) + + -- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group. + local scenery={} + + for SceneryTypeName, SceneryData in pairs(ZoneNuke:GetScannedScenery()) do + for SceneryName, SceneryObject in pairs(SceneryData) do + + local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY + + -- Position of the scenery object. + local spos=SceneryObject:GetCoordinate() + + -- Distance from group to impact point. + local distance= spos:Get2DDistance(_coord) + + -- Place markers on every possible scenery object. + if self.Debug then + local MarkerID=spos:MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(), SceneryObject:GetTypeName())) + local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS()) + self:T2(SUPPRESSION.id..text) + end + + -- Add to table. + table.insert(scenery, {object=SceneryObject, distance=distance}) + + --SceneryObject:Destroy() + end + end + + -- Sort scenery wrt to distance from impact point. +-- local _sort = function(a,b) return a.distance < b.distance end +-- table.sort(scenery,_sort) + +-- for _,object in pairs(scenery) do +-- local sobject=object -- Wrapper.Scenery#SCENERY +-- sobject:Destroy() +-- end + +]] + +end + +--- Route group to a certain point. +-- @param #ARTY self +-- @param Wrapper.Group#GROUP group Group to route. +-- @param Core.Point#COORDINATE ToCoord Coordinate where we want to go. +-- @param #number Speed (Optional) Speed in km/h. Default is 70% of max speed the group can do. +-- @param #boolean OnRoad If true, use (mainly) roads. +function ARTY:_Move(group, ToCoord, Speed, OnRoad) + self:F2({group=group:GetName(), Speed=Speed, OnRoad=OnRoad}) + + -- Clear all tasks. + group:ClearTasks() + group:OptionAlarmStateGreen() + group:OptionROEHoldFire() + + -- Set formation. + local formation = "Off Road" + + -- Get max speed of group. + local SpeedMax=group:GetSpeedMax() + + -- Set speed. + Speed=Speed or SpeedMax*0.7 + + -- Make sure, we do not go above max speed possible. + Speed=math.min(Speed, SpeedMax) + + -- Current coordinates of group. + local cpini=group:GetCoordinate() -- Core.Point#COORDINATE + + -- Distance between current and final point. + local dist=cpini:Get2DDistance(ToCoord) + + -- Waypoint and task arrays. + local path={} + local task={} + + -- First waypoint is the current position of the group. + path[#path+1]=cpini:WaypointGround(Speed, formation) + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + -- Route group on road if requested. + if OnRoad then + + -- Get path on road. + local _pathonroad=cpini:GetPathOnRoad(ToCoord) + + -- Check if we actually got a path. There are situations where nil is returned. In that case, we go directly. + if _pathonroad then + + -- Just take the first and last point. + local _first=_pathonroad[1] + local _last=_pathonroad[#_pathonroad] + + if self.Debug then + _first:SmokeGreen() + _last:SmokeGreen() + end + + -- First point on road. + path[#path+1]=_first:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + + -- Last point on road. + path[#path+1]=_last:WaypointGround(Speed, "On Road") + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, false) + end + + end + + -- Last waypoint at ToCoord. + path[#path+1]=ToCoord:WaypointGround(Speed, formation) + task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint", self, #path-1, true) + + --if self.Debug then + -- cpini:SmokeBlue() + -- ToCoord:SmokeBlue() + --end + + -- Init waypoints of the group. + local Waypoints={} + + -- New points are added to the default route. + for i=1,#path do + table.insert(Waypoints, i, path[i]) + end + + -- Set task for all waypoints. + for i=1,#Waypoints do + group:SetTaskWaypoint(Waypoints[i], task[i]) + end + + -- Submit task and route group along waypoints. + group:Route(Waypoints) + +end + +--- Function called when group is passing a waypoint. +-- @param Wrapper.Group#GROUP group Group for which waypoint passing should be monitored. +-- @param #ARTY arty ARTY object. +-- @param #number i Waypoint number that has been reached. +-- @param #boolean final True if it is the final waypoint. +function ARTY._PassingWaypoint(group, arty, i, final) + + -- Debug message. + local text=string.format("%s, passing waypoint %d.", group:GetName(), i) + if final then + text=string.format("%s, arrived at destination.", group:GetName()) + end + arty:T(ARTY.id..text) + + --[[ + if final then + MESSAGE:New(text, 10):ToCoalitionIf(group:GetCoalition(), arty.Debug or arty.report) + else + MESSAGE:New(text, 10):ToAllIf(arty.Debug) + end + ]] + + -- Arrived event. + if final and arty.groupname==group:GetName() then + arty:Arrived() + end + +end + +--- Relocate to another position, e.g. after an engagement to avoid couter strikes. +-- @param #ARTY self +function ARTY:_Relocate() + + -- Current position. + local _pos=self.Controllable:GetCoordinate() + + local _new=nil + local _gotit=false + local _n=0 + local _nmax=1000 + repeat + -- Get a random coordinate. + _new=_pos:GetRandomCoordinateInRadius(self.relocateRmax, self.relocateRmin) + local _surface=_new:GetSurfaceType() + + -- Check that new coordinate is not water(-ish). + if _surface~=land.SurfaceType.WATER and _surface~=land.SurfaceType.SHALLOW_WATER then + _gotit=true + end + -- Increase counter. + _n=_n+1 + until _gotit or _n>_nmax + + -- Assign relocation. + if _gotit then + self:AssignMoveCoord(_new, nil, nil, false, false, "RELOCATION MOVE AFTER FIRING") + end +end + +--- Get the number of shells a unit or group currently has. For a group the ammo count of all units is summed up. +-- @param #ARTY self +-- @param #boolean display Display ammo table as message to all. Default false. +-- @return #number Total amount of ammo the whole group has left. +-- @return #number Number of shells the group has left. +-- @return #number Number of rockets the group has left. +-- @return #number Number of missiles the group has left. +function ARTY:GetAmmo(display) + self:F3({display=display}) + + -- Default is display false. + if display==nil then + display=false + end + + -- Init counter. + local nammo=0 + local nshells=0 + local nrockets=0 + local nmissiles=0 + + -- Get all units. + local units=self.Controllable:GetUnits() + if units==nil then + return nammo, nshells, nrockets, nmissiles + end + + for _,unit in pairs(units) do + + if unit and unit:IsAlive() then + + -- Output. + local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName()) + + -- Get ammo table. + local ammotable=unit:GetAmmo() + + if ammotable ~= nil then + + local weapons=#ammotable + + -- Display ammo table + if display then + self:E(ARTY.id..string.format("Number of weapons %d.", weapons)) + self:E({ammotable=ammotable}) + self:E(ARTY.id.."Ammotable:") + for id,bla in pairs(ammotable) do + self:E({id=id, ammo=bla}) + end + end + + -- Loop over all weapons. + for w=1,weapons do + + -- Number of current weapon. + local Nammo=ammotable[w]["count"] + + -- Typename of current weapon + local Tammo=ammotable[w]["desc"]["typeName"] + + local _weaponString = self:_split(Tammo,"%.") + local _weaponName = _weaponString[#_weaponString] + + -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 + local Category=ammotable[w].desc.category + + -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 + local MissileCategory=nil + if Category==Weapon.Category.MISSILE then + MissileCategory=ammotable[w].desc.missileCategory + end + + + -- Check for correct shell type. + local _gotshell=false + if #self.ammoshells>0 then + -- User explicitly specified the valid type(s) of shells. + for _,_type in pairs(self.ammoshells) do + if string.match(Tammo, _type) and Category==Weapon.Category.SHELL then + _gotshell=true + end + end + else + if Category==Weapon.Category.SHELL then + _gotshell=true + end + end + + -- Check for correct rocket type. + local _gotrocket=false + if #self.ammorockets>0 then + for _,_type in pairs(self.ammorockets) do + if string.match(Tammo, _type) and Category==Weapon.Category.ROCKET then + _gotrocket=true + end + end + else + if Category==Weapon.Category.ROCKET then + _gotrocket=true + end + end + + -- Check for correct missile type. + local _gotmissile=false + if #self.ammomissiles>0 then + for _,_type in pairs(self.ammomissiles) do + if string.match(Tammo,_type) and Category==Weapon.Category.MISSILE then + _gotmissile=true + end + end + else + if Category==Weapon.Category.MISSILE then + _gotmissile=true + end + end + + -- We are specifically looking for shells or rockets here. + if _gotshell then + + -- Add up all shells. + nshells=nshells+Nammo + + -- Debug info. + text=text..string.format("- %d shells of type %s\n", Nammo, _weaponName) + + elseif _gotrocket then + + -- Add up all rockets. + nrockets=nrockets+Nammo + + -- Debug info. + text=text..string.format("- %d rockets of type %s\n", Nammo, _weaponName) + + elseif _gotmissile then + + -- Add up all cruise missiles (category 5) + if MissileCategory==Weapon.MissileCategory.CRUISE then + nmissiles=nmissiles+Nammo + end + + -- Debug info. + text=text..string.format("- %d %s missiles of type %s\n", Nammo, self:_MissileCategoryName(MissileCategory), _weaponName) + + else + + -- Debug info. + text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n", Nammo, Tammo, Category, tostring(MissileCategory)) + + end + + end + end + + -- Debug text and send message. + if display then + self:E(ARTY.id..text) + else + self:T3(ARTY.id..text) + end + MESSAGE:New(text, 10):ToAllIf(display) + + end + end + + -- Total amount of ammunition. + nammo=nshells+nrockets+nmissiles + + return nammo, nshells, nrockets, nmissiles +end + +--- Returns a name of a missile category. +-- @param #ARTY self +-- @param #number categorynumber Number of missile category from weapon missile category enumerator. See https://wiki.hoggitworld.com/view/DCS_Class_Weapon +-- @return #string Missile category name. +function ARTY:_MissileCategoryName(categorynumber) + local cat="unknown" + if categorynumber==Weapon.MissileCategory.AAM then + cat="air-to-air" + elseif categorynumber==Weapon.MissileCategory.SAM then + cat="surface-to-air" + elseif categorynumber==Weapon.MissileCategory.BM then + cat="ballistic" + elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then + cat="anti-ship" + elseif categorynumber==Weapon.MissileCategory.CRUISE then + cat="cruise" + elseif categorynumber==Weapon.MissileCategory.OTHER then + cat="other" + end + return cat +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Mark Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text. +-- @return #boolean If true, authentification successful. +function ARTY:_MarkerKeyAuthentification(text) + + -- Set battery and coalition. + --local batteryname=self.groupname + local batterycoalition=self.Controllable:GetCoalition() + + -- Get assignment. + local mykey=nil + if self.markkey~=nil then + + -- keywords are split by "," + local keywords=self:_split(text, ",") + for _,key in pairs(keywords) do + local s=self:_split(key, " ") + local val=s[2] + if key:lower():find("key") then + mykey=tonumber(val) + self:T(ARTY.id..string.format("Authorisation Key=%s.", val)) + end + end + + end + + -- Check if the authorization key is required and if it is valid. + local _validkey=true + + -- Check if group needs authorization. + if self.markkey~=nil then + -- Assume key is incorrect. + _validkey=false + + -- If key was found, check if matches. + if mykey~=nil then + _validkey=self.markkey==mykey + end + self:T2(ARTY.id..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s", self.groupname, tostring(self.markkey), tostring(mykey), tostring(_validkey))) + + -- Send message + local text="" + if mykey==nil then + text=string.format("%s, authorization required but did not receive a key!", self.alias) + elseif _validkey==false then + text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!", self.alias, tostring(mykey)) + elseif _validkey==true then + text=string.format("%s, authentification successful!", self.alias) + end + MESSAGE:New(text, 10):ToCoalitionIf(batterycoalition, self.report or self.Debug) + end + + return _validkey +end + +--- Extract engagement assignments and parameters from mark text. +-- @param #ARTY self +-- @param #string text Marker text to be analyzed. +-- @return #table Table with assignment parameters, e.g. number of shots, radius, time etc. +function ARTY:_Markertext(text) + self:F(text) + + -- Assignment parameters. + local assignment={} + assignment.battery={} + assignment.aliases={} + assignment.cluster={} + assignment.everyone=false + assignment.move=false + assignment.engage=false + assignment.request=false + assignment.cancel=false + assignment.readonly=false + assignment.movecanceltarget=false + assignment.cancelmove=false + assignment.canceltarget=false + assignment.cancelrearm=false + + -- Check for correct keywords. + if text:lower():find("arty engage") or text:lower():find("arty attack") then + assignment.engage=true + elseif text:lower():find("arty move") or text:lower():find("arty relocate") then + assignment.move=true + elseif text:lower():find("arty request") then + assignment.request=true + elseif text:lower():find("arty cancel") then + assignment.cancel=true + else + self:E(ARTY.id..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" keyword specified!') + return nil + end + + -- keywords are split by "," + local keywords=self:_split(text, ",") + self:T({keywords=keywords}) + + for _,keyphrase in pairs(keywords) do + + -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma. + local str=self:_split(keyphrase, " ") + local key=str[1] + local val=str[2] + + -- Debug output. + self:T3(ARTY.id..string.format("%s, keyphrase = %s, key = %s, val = %s", self.groupname, tostring(keyphrase), tostring(key), tostring(val))) + + -- Battery name, i.e. which ARTY group should fire. + if key:lower():find("battery") then + + local v=self:_split(keyphrase, '"') + + for i=2,#v,2 do + table.insert(assignment.battery, v[i]) + self:T2(ARTY.id..string.format("Key Battery=%s.", v[i])) + end + + elseif key:lower():find("alias") then + + local v=self:_split(keyphrase, '"') + + for i=2,#v,2 do + table.insert(assignment.aliases, v[i]) + self:T2(ARTY.id..string.format("Key Aliases=%s.", v[i])) + end + + elseif key:lower():find("cluster") then + + local v=self:_split(keyphrase, '"') + + for i=2,#v,2 do + table.insert(assignment.cluster, v[i]) + self:T2(ARTY.id..string.format("Key Cluster=%s.", v[i])) + end + + elseif keyphrase:lower():find("everyone") or keyphrase:lower():find("all batteries") or keyphrase:lower():find("allbatteries") then + + assignment.everyone=true + self:T(ARTY.id..string.format("Key Everyone=true.")) + + elseif (assignment.engage or assignment.move) and key:lower():find("time") then + + if val:lower():find("now") then + assignment.time=self:_SecondsToClock(timer.getTime0()+2) + else + assignment.time=val + end + self:T2(ARTY.id..string.format("Key Time=%s.", val)) + + elseif assignment.engage and key:lower():find("shot") then + + assignment.nshells=tonumber(val) + self:T(ARTY.id..string.format("Key Shot=%s.", val)) + + elseif assignment.engage and key:lower():find("prio") then + + assignment.prio=tonumber(val) + self:T2(string.format("Key Prio=%s.", val)) + + elseif assignment.engage and key:lower():find("maxengage") then + + assignment.maxengage=tonumber(val) + self:T2(ARTY.id..string.format("Key Maxengage=%s.", val)) + + elseif assignment.engage and key:lower():find("radius") then + + assignment.radius=tonumber(val) + self:T2(ARTY.id..string.format("Key Radius=%s.", val)) + + elseif assignment.engage and key:lower():find("weapon") then + + if val:lower():find("cannon") then + assignment.weapontype=ARTY.WeaponType.Cannon + elseif val:lower():find("rocket") then + assignment.weapontype=ARTY.WeaponType.Rockets + elseif val:lower():find("missile") then + assignment.weapontype=ARTY.WeaponType.CruiseMissile + elseif val:lower():find("nuke") then + assignment.weapontype=ARTY.WeaponType.TacticalNukes + else + assignment.weapontype=ARTY.WeaponType.Auto + end + self:T2(ARTY.id..string.format("Key Weapon=%s.", val)) + + elseif assignment.move and key:lower():find("speed") then + + assignment.speed=tonumber(val) + self:T2(ARTY.id..string.format("Key Speed=%s.", val)) + + elseif assignment.move and (keyphrase:lower():find("on road") or keyphrase:lower():find("onroad") or keyphrase:lower():find("use road")) then + + assignment.onroad=true + self:T2(ARTY.id..string.format("Key Onroad=true.")) + + elseif keyphrase:lower():find("irrevocable") or keyphrase:lower():find("readonly") then + + assignment.readonly=true + self:T2(ARTY.id..string.format("Key Readonly=true.")) + + elseif assignment.move and (keyphrase:lower():find("cancel target") or keyphrase:lower():find("canceltarget")) then + + assignment.movecanceltarget=true + self:T2(ARTY.id..string.format("Key Cancel Target (before move)=true.")) + + elseif assignment.request and keyphrase:lower():find("rearm") then + + assignment.requestrearming=true + self:T2(ARTY.id..string.format("Key Request Rearming=true.")) + + elseif assignment.request and keyphrase:lower():find("ammo") then + + assignment.requestammo=true + self:T2(ARTY.id..string.format("Key Request Ammo=true.")) + + elseif assignment.request and keyphrase:lower():find("target") then + + assignment.requesttargets=true + self:T2(ARTY.id..string.format("Key Request Targets=true.")) + + elseif assignment.request and keyphrase:lower():find("status") then + + assignment.requeststatus=true + self:T2(ARTY.id..string.format("Key Request Status=true.")) + + elseif assignment.request and (keyphrase:lower():find("move") or keyphrase:lower():find("relocation")) then + + assignment.requestmoves=true + self:T2(ARTY.id..string.format("Key Request Moves=true.")) + + elseif assignment.cancel and (keyphrase:lower():find("engagement") or keyphrase:lower():find("attack") or keyphrase:lower():find("target")) then + + assignment.canceltarget=true + self:T2(ARTY.id..string.format("Key Cancel Target=true.")) + + elseif assignment.cancel and (keyphrase:lower():find("move") or keyphrase:lower():find("relocation")) then + + assignment.cancelmove=true + self:T2(ARTY.id..string.format("Key Cancel Move=true.")) + + elseif assignment.cancel and keyphrase:lower():find("rearm") then + + assignment.cancelrearm=true + self:T2(ARTY.id..string.format("Key Cancel Rearm=true.")) + + elseif key:lower():find("lldms") then + + local _flat = "%d+:%d+:%d+%s*[N,S]" + local _flon = "%d+:%d+:%d+%s*[W,E]" + local _lat=keyphrase:match(_flat) + local _lon=keyphrase:match(_flon) + self:T2(ARTY.id..string.format("Key LLDMS: lat=%s, long=%s format=DMS", _lat,_lon)) + + if _lat and _lon then + + -- Convert DMS string to DD numbers format. + local _latitude, _longitude=self:_LLDMS2DD(_lat, _lon) + self:T2(ARTY.id..string.format("Key LLDMS: lat=%.3f, long=%.3f format=DD", _latitude,_longitude)) + + -- Convert LL to coordinate object. + if _latitude and _longitude then + assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude) + end + + end + end + end + + return assignment +end + +--- Request ammo via mark. +-- @param #ARTY self +function ARTY:_MarkRequestAmmo() + self:GetAmmo(true) +end + +--- Request status via mark. +-- @param #ARTY self +function ARTY:_MarkRequestStatus() + self:_StatusReport(true) +end + +--- Request Moves. +-- @param #ARTY self +function ARTY:_MarkRequestMoves() + local text=string.format("%s, relocations:", self.groupname) + if #self.moves>0 then + for _,move in pairs(self.moves) do + if self.currentMove and move.name == self.currentMove.name then + text=text..string.format("\n- %s (current)", self:_MoveInfo(move)) + else + text=text..string.format("\n- %s", self:_MoveInfo(move)) + end + end + else + text=text..string.format("\n- no queued relocations") + end + MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) +end + +--- Request Targets. +-- @param #ARTY self +function ARTY:_MarkRequestTargets() + local text=string.format("%s, targets:", self.groupname) + if #self.targets>0 then + for _,target in pairs(self.targets) do + if self.currentTarget and target.name == self.currentTarget.name then + text=text..string.format("\n- %s (current)", self:_TargetInfo(target)) + else + text=text..string.format("\n- %s", self:_TargetInfo(target)) + end + end + else + text=text..string.format("\n- no queued targets") + end + MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) +end + +--- Create a name for an engagement initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of target engagement. +function ARTY:_MarkTargetName(markerid) + return string.format("BATTERY=%s, Marked Target ID=%d", self.groupname, markerid) +end + +--- Create a name for a relocation move initiated by placing a marker. +-- @param #ARTY self +-- @param #number markerid ID of the placed marker. +-- @return #string Name of relocation move. +function ARTY:_MarkMoveName(markerid) + return string.format("BATTERY=%s, Marked Relocation ID=%d", self.groupname, markerid) +end + +--- Get the marker ID from the assigned task name. +-- @param #ARTY self +-- @param #string name Name of the assignment. +-- @return #string Name of the ARTY group or nil +-- @return #number ID of the marked target or nil. +-- @return #number ID of the marked relocation move or nil +function ARTY:_GetMarkIDfromName(name) + + -- keywords are split by "," + local keywords=self:_split(name, ",") + + local battery=nil + local markTID=nil + local markMID=nil + + for _,key in pairs(keywords) do + + local str=self:_split(key, "=") + local par=str[1] + local val=str[2] + + if par:find("BATTERY") then + battery=val + end + if par:find("Marked Target ID") then + markTID=tonumber(val) + end + if par:find("Marked Relocation ID") then + markMID=tonumber(val) + end + + end + + return battery, markTID, markMID +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Helper Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Sort targets with respect to priority and number of times it was already engaged. +-- @param #ARTY self +function ARTY:_SortTargetQueuePrio() + self:F2() + + -- Sort results table wrt times they have already been engaged. + local function _sort(a, b) + return (a.engaged < b.engaged) or (a.engaged==b.engaged and a.prio < b.prio) + end + table.sort(self.targets, _sort) + + -- Debug output. + self:T3(ARTY.id.."Sorted targets wrt prio and number of engagements:") + for i=1,#self.targets do + local _target=self.targets[i] + self:T3(ARTY.id..string.format("Target %s", self:_TargetInfo(_target))) + end +end + +--- Sort array with respect to time. Array elements must have a .time entry. +-- @param #ARTY self +-- @param #table queue Array to sort. Should have elemnt .time. +function ARTY:_SortQueueTime(queue) + self:F3({queue=queue}) + + -- Sort targets w.r.t attack time. + local function _sort(a, b) + if a.time == nil and b.time == nil then + return false + end + if a.time == nil then + return false + end + if b.time == nil then + return true + end + return a.time < b.time + end + table.sort(queue, _sort) + + -- Debug output. + self:T3(ARTY.id.."Sorted queue wrt time:") + for i=1,#queue do + local _queue=queue[i] + local _time=tostring(_queue.time) + local _clock=tostring(self:_SecondsToClock(_queue.time)) + self:T3(ARTY.id..string.format("%s: time=%s, clock=%s", _queue.name, _time, _clock)) + end + +end + +--- Heading from point a to point b in degrees. +--@param #ARTY self +--@param Core.Point#COORDINATE a Coordinate. +--@param Core.Point#COORDINATE b Coordinate. +--@return #number angle Angle from a to b in degrees. +function ARTY:_GetHeading(a, b) + local dx = b.x-a.x + local dy = b.z-a.z + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + return angle +end + +--- Check all targets whether they are in range. +-- @param #ARTY self +function ARTY:_CheckTargetsInRange() + + for i=1,#self.targets do + local _target=self.targets[i] + + self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange))) + + -- Check if target is in range. + local _inrange,_toofar,_tooclose=self:_TargetInRange(_target) + self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose))) + + -- Init default for assigning moves into range. + local _movetowards=false + local _moveaway=false + + if _target.inrange==nil then + + -- First time the check is performed. We call the function again and send a message. + _target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug) + + -- Send group towards/away from target. + if _toofar then + _movetowards=true + elseif _tooclose then + _moveaway=true + end + + elseif _target.inrange==true then + + -- Target was in range at previous check... + + if _toofar then --...but is now too far away. + _movetowards=true + elseif _tooclose then --...but is now too close. + _moveaway=true + end + + elseif _target.inrange==false then + + -- Target was out of range at previous check. + + if _inrange then + -- Inform coalition that target is now in range. + local text=string.format("%s, target %s is now in range.", self.alias, _target.name) + self:T(ARTY.id..text) + MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + end + + end + + -- Assign a relocation command so that the unit will be in range of the requested target. + if self.autorelocate and (_movetowards or _moveaway) then + + -- Get current position. + local _from=self.Controllable:GetCoordinate() + local _dist=_from:Get2DDistance(_target.coord) + + if _dist<=self.autorelocatemaxdist then + + local _tocoord --Core.Point#COORDINATE + local _name="" + local _safetymargin=500 + + if _movetowards then + + -- Target was in range on previous check but now we are too far away. + local _waytogo=_dist-self.maxrange+_safetymargin + local _heading=self:_GetHeading(_from,_target.coord) + _tocoord=_from:Translate(_waytogo, _heading) + _name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name) + + elseif _moveaway then + + -- Target was in range on previous check but now we are too far away. + local _waytogo=_dist-self.minrange+_safetymargin + local _heading=self:_GetHeading(_target.coord,_from) + _tocoord=_from:Translate(_waytogo, _heading) + _name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name) + + end + + -- Send info message. + MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + + -- Assign relocation move. + self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) + + end + + end + + -- Update value. + _target.inrange=_inrange + + self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange))) + + end +end + +--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. +-- @param #ARTY self +-- @return #table Target which is due to be attacked now or nil if no target could be found. +function ARTY:_CheckNormalTargets() + self:F3() + + -- Sort targets w.r.t. prio and number times engaged already. + self:_SortTargetQueuePrio() + + -- No target engagements if rearming! + if self:is("Rearming") then + return nil + end + + -- Loop over all sorted targets. + for i=1,#self.targets do + local _target=self.targets[i] + + -- Debug info. + self:T3(ARTY.id..string.format("Check NORMAL target %d: %s", i, self:_TargetInfo(_target))) + + -- Check that target no time, is not under fire currently and in range. + if _target.underfire==false and _target.time==nil and _target.maxengage > _target.engaged and self:_TargetInRange(_target) and self:_CheckWeaponTypeAvailable(_target)>0 then + + -- Debug info. + self:T2(ARTY.id..string.format("Found NORMAL target %s", self:_TargetInfo(_target))) + + return _target + end + end + + return nil +end + +--- Check all timed targets and return the target which should be attacked next. +-- @param #ARTY self +-- @return #table Target which is due to be attacked now. +function ARTY:_CheckTimedTargets() + self:F3() + + -- Current time. + local Tnow=timer.getAbsTime() + + -- Sort Targets wrt time. + self:_SortQueueTime(self.targets) + + -- No target engagements if rearming! + if self:is("Rearming") then + return nil + end + + for i=1,#self.targets do + local _target=self.targets[i] + + -- Debug info. + self:T3(ARTY.id..string.format("Check TIMED target %d: %s", i, self:_TargetInfo(_target))) + + -- Check if target has an attack time which has already passed. Also check that target is not under fire already and that it is in range. + if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target) and self:_CheckWeaponTypeAvailable(_target)>0 then + + -- Check if group currently has a target and whether its priorty is lower than the timed target. + if self.currentTarget then + if self.currentTarget.prio > _target.prio then + -- Current target under attack but has lower priority than this target. + self:T2(ARTY.id..string.format("Found TIMED HIGH PRIO target %s.", self:_TargetInfo(_target))) + return _target + end + else + -- No current target. + self:T2(ARTY.id..string.format("Found TIMED target %s.", self:_TargetInfo(_target))) + return _target + end + end + + end + + return nil +end + +--- Check all moves and return the one which should be executed next. +-- @param #ARTY self +-- @return #table Move which is due. +function ARTY:_CheckMoves() + self:F3() + + -- Current time. + local Tnow=timer.getAbsTime() + + -- Sort Targets wrt time. + self:_SortQueueTime(self.moves) + + -- Check if we are currently firing. + local firing=false + if self.currentTarget then + firing=true + end + + -- Loop over all moves in queue. + for i=1,#self.moves do + + -- Shortcut. + local _move=self.moves[i] + + if string.find(_move.name, "REARMING MOVE") and ((self.currentMove and self.currentMove.name~=_move.name) or self.currentMove==nil) then + -- We got an rearming assignment which has priority. + return _move + elseif (Tnow >= _move.time) and (firing==false or _move.cancel) and (not self.currentMove) and (not self:is("Rearming")) then + -- Time for move is reached and maybe current target should be cancelled. + return _move + end + end + + return nil +end + +--- Check whether shooting started within a certain time (~5 min). If not, the current target is considered invalid and removed from the target list. +-- @param #ARTY self +function ARTY:_CheckShootingStarted() + self:F2() + + if self.currentTarget then + + -- Current time. + local Tnow=timer.getTime() + + -- Get name and id of target. + local name=self.currentTarget.name + + -- Time that passed after current target has been assigned. + local dt=Tnow-self.currentTarget.Tassigned + + -- Debug info + if self.Nshots==0 then + self:T(ARTY.id..string.format("%s, waiting for %d seconds for first shot on target %s.", self.groupname, dt, name)) + end + + -- Check if we waited long enough and no shot was fired. + if dt > self.WaitForShotTime and self.Nshots==0 then + + -- Debug info. + self:T(ARTY.id..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name)) + + -- CeaseFire. + self:CeaseFire(self.currentTarget) + + -- Remove target from list. + self:RemoveTarget(name) + + end + end +end + +--- Get the index of a target by its name. +-- @param #ARTY self +-- @param #string name Name of target. +-- @return #number Arrayindex of target. +function ARTY:_GetTargetIndexByName(name) + self:F2(name) + + for i=1,#self.targets do + local targetname=self.targets[i].name + self:T3(ARTY.id..string.format("Have target with name %s. Index = %d", targetname, i)) + if targetname==name then + self:T2(ARTY.id..string.format("Found target with name %s. Index = %d", name, i)) + return i + end + end + + self:T2(ARTY.id..string.format("WARNING: Target with name %s could not be found. (This can happen.)", name)) + return nil +end + +--- Get the index of a move by its name. +-- @param #ARTY self +-- @param #string name Name of move. +-- @return #number Arrayindex of move. +function ARTY:_GetMoveIndexByName(name) + self:F2(name) + + for i=1,#self.moves do + local movename=self.moves[i].name + self:T3(ARTY.id..string.format("Have move with name %s. Index = %d", movename, i)) + if movename==name then + self:T2(ARTY.id..string.format("Found move with name %s. Index = %d", name, i)) + return i + end + end + + self:T2(ARTY.id..string.format("WARNING: Move with name %s could not be found. (This can happen.)", name)) + return nil +end + +--- Check if group is (partly) out of ammo of a special weapon type. +-- @param #ARTY self +-- @param #table targets Table of targets. +-- @return @boolean True if any target requests a weapon type that is empty. +function ARTY:_CheckOutOfAmmo(targets) + + -- Get current ammo. + local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() + + -- Special weapon type requested ==> Check if corresponding ammo is empty. + local _partlyoutofammo=false + + for _,Target in pairs(targets) do + + if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then + + self:T(ARTY.id..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.", self.groupname, Target.name)) + _partlyoutofammo=true + + elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then + + self:T(ARTY.id..string.format("Group %s, cannons requested for target %s but shells empty.", self.groupname, Target.name)) + _partlyoutofammo=true + + elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then + + self:T(ARTY.id..string.format("Group %s, tactical nukes requested for target %s but nukes empty.", self.groupname, Target.name)) + _partlyoutofammo=true + + elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then + + self:T(ARTY.id..string.format("Group %s, rockets requested for target %s but rockets empty.", self.groupname, Target.name)) + _partlyoutofammo=true + + elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then + + self:T(ARTY.id..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.", self.groupname, Target.name)) + _partlyoutofammo=true + + end + + end + + return _partlyoutofammo +end + +--- Check if a selected weapon type is available for this target, i.e. if the current amount of ammo of this weapon type is currently available. +-- @param #ARTY self +-- @param #boolean target Target array data structure. +-- @return #number Amount of shells, rockets or missiles available of the weapon type selected for the target. +function ARTY:_CheckWeaponTypeAvailable(target) + + -- Get current ammo of group. + local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo() + + -- Check if enough ammo is there for the selected weapon type. + local nfire=Nammo + if target.weapontype==ARTY.WeaponType.Auto then + nfire=Nammo + elseif target.weapontype==ARTY.WeaponType.Cannon then + nfire=Nshells + elseif target.weapontype==ARTY.WeaponType.TacticalNukes then + nfire=self.Nukes + elseif target.weapontype==ARTY.WeaponType.Rockets then + nfire=Nrockets + elseif target.weapontype==ARTY.WeaponType.CruiseMissile then + nfire=Nmissiles + end + + return nfire +end +--- Check if a selected weapon type is in principle possible for this group. The current amount of ammo might be zero but the group still can be rearmed at a later point in time. +-- @param #ARTY self +-- @param #boolean target Target array data structure. +-- @return #boolean True if the group can carry this weapon type, false otherwise. +function ARTY:_CheckWeaponTypePossible(target) + + -- Check if enough ammo is there for the selected weapon type. + local possible=false + if target.weapontype==ARTY.WeaponType.Auto then + possible=self.Nammo0>0 + elseif target.weapontype==ARTY.WeaponType.Cannon then + possible=self.Nshells0>0 + elseif target.weapontype==ARTY.WeaponType.TacticalNukes then + possible=self.Nukes0>0 + elseif target.weapontype==ARTY.WeaponType.Rockets then + possible=self.Nrockets0>0 + elseif target.weapontype==ARTY.WeaponType.CruiseMissile then + possible=self.Nmissiles0>0 + end + + return possible +end + +--- Check if a name is unique. If not, a new unique name can be created by adding a running index #01, #02, ... +-- @param #ARTY self +-- @param #table givennames Table with entries of already given names. Must contain a .name item. +-- @param #string name Name to check if it already exists in givennames table. +-- @param #boolean makeunique If true, a new unique name is returned by appending the running index. +-- @return #string Unique name, which is not already given for another target. +function ARTY:_CheckName(givennames, name, makeunique) + self:F2({givennames=givennames, name=name}) + + local newname=name + local counter=1 + local n=1 + local nmax=100 + if makeunique==nil then + makeunique=true + end + + repeat -- until a unique name is found. + + -- We assume the name is unique. + local _unique=true + + -- Loop over all targets already defined. + for _,_target in pairs(givennames) do + + -- Target name. + local _givenname=_target.name + + -- Name is already used by another target. + if _givenname==newname then + + -- Name is already used for another target ==> try again with new name. + _unique=false + + end + + -- Debug info. + self:T3(ARTY.id..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s", n, tostring(_givenname), newname, tostring(_unique), tostring(makeunique))) + end + + -- Create a new name if requested and try again. + if _unique==false and makeunique==true then + + -- Define newname = "name #01" + newname=string.format("%s #%02d", name, counter) + + -- Increase counter. + counter=counter+1 + end + + -- Name is not unique and we don't want to make it unique. + if _unique==false and makeunique==false then + self:T3(ARTY.id..string.format("Name %s is not unique. Return false.", tostring(newname))) + + -- Return + return name, false + end + + -- Increase loop counter. We try max 100 times. + n=n+1 + until (_unique or n==nmax) + + -- Debug output and return new name. + self:T3(ARTY.id..string.format("Original name %s, new name = %s", name, newname)) + return newname, true +end + +--- Check if target is in range. +-- @param #ARTY self +-- @param #table target Target table. +-- @param #boolean message (Optional) If true, send a message to the coalition if the target is not in range. Default is no message is send. +-- @return #boolean True if target is in range, false otherwise. +-- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range. +-- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range. +function ARTY:_TargetInRange(target, message) + self:F3(target) + + -- Default is no message. + if message==nil then + message=false + end + + -- Distance between ARTY group and target. + local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord) + + -- Assume we are in range. + local _inrange=true + local _tooclose=false + local _toofar=false + local text="" + + if _dist < self.minrange then + _inrange=false + _tooclose=true + text=string.format("%s, target is out of range. Distance of %.1f km is below min range of %.1f km.", self.alias, _dist/1000, self.minrange/1000) + elseif _dist > self.maxrange then + _inrange=false + _toofar=true + text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.", self.alias, _dist/1000, self.maxrange/1000) + end + + -- Debug output. + if not _inrange then + self:T(ARTY.id..text) + MESSAGE:New(text, 5):ToCoalitionIf(self.Controllable:GetCoalition(), (self.report and message) or (self.Debug and message)) + end + + -- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range. + if not self.ismobile and _inrange==false then + self:RemoveTarget(target.name) + end + + return _inrange,_toofar,_tooclose +end + +--- Get the weapon type name, which should be used to attack the target. +-- @param #ARTY self +-- @param #number tnumber Number of weapon type ARTY.WeaponType.XXX +-- @return #number tnumber of weapon type. +function ARTY:_WeaponTypeName(tnumber) + self:F2(tnumber) + local name="unknown" + if tnumber==ARTY.WeaponType.Auto then + name="Auto" -- (Cannon, Rockets, Missiles) + elseif tnumber==ARTY.WeaponType.Cannon then + name="Cannons" + elseif tnumber==ARTY.WeaponType.Rockets then + name="Rockets" + elseif tnumber==ARTY.WeaponType.CruiseMissile then + name="Cruise Missiles" + elseif tnumber==ARTY.WeaponType.TacticalNukes then + name="Tactical Nukes" + end + return name +end + +--- Find a random coordinate in the vicinity of another coordinate. +-- @param #ARTY self +-- @param Core.Point#COORDINATE coord Center coordinate. +-- @param #number rmin (Optional) Minimum distance in meters from center coordinate. Default 20 m. +-- @param #number rmax (Optional) Maximum distance in meters from center coordinate. Default 80 m. +-- @return Core.Point#COORDINATE Random coordinate in a certain distance from center coordinate. +function ARTY:_VicinityCoord(coord, rmin, rmax) + self:F2({coord=coord, rmin=rmin, rmax=rmax}) + -- Set default if necessary. + rmin=rmin or 20 + rmax=rmax or 80 + -- Random point withing range. + local vec2=coord:GetRandomVec2InRadius(rmax, rmin) + local pops=COORDINATE:NewFromVec2(vec2) + -- Debug info. + self:T3(ARTY.id..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)", pops:Get2DDistance(coord), rmin, rmax)) + return pops +end + +--- Print event-from-to string to DCS log file. +-- @param #ARTY self +-- @param #string BA Before/after info. +-- @param #string Event Event. +-- @param #string From From state. +-- @param #string To To state. +function ARTY:_EventFromTo(BA, Event, From, To) + local text=string.format("%s: %s EVENT %s: %s --> %s", BA, self.groupname, Event, From, To) + self:T3(ARTY.id..text) +end + +--- Split string. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua +-- @param #ARTY self +-- @param #string str Sting to split. +-- @param #string sep Speparator for split. +-- @return #table Split text. +function ARTY:_split(str, sep) + self:F3({str=str, sep=sep}) + local result = {} + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) + end + return result +end + +--- Returns the target parameters as formatted string. +-- @param #ARTY self +-- @return #string name, prio, radius, nshells, engaged, maxengage, time, weapontype +function ARTY:_TargetInfo(target) + local clock=tostring(self:_SecondsToClock(target.time)) + local weapon=self:_WeaponTypeName(target.weapontype) + local _underfire=tostring(target.underfire) + return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s", + target.name, target.prio, target.radius, target.nshells, target.engaged, target.maxengage, weapon, clock,_underfire) +end + +--- Returns a formatted string with information about all move parameters. +-- @param #ARTY self +-- @param #table move Move table item. +-- @return #string Info string. +function ARTY:_MoveInfo(move) + self:F3(move) + local _clock=self:_SecondsToClock(move.time) + return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s", move.name, _clock, move.speed, tostring(move.onroad), tostring(move.cancel)) +end + +--- Convert Latitude and Lontigude from DMS to DD. +-- @param #ARTY self +-- @param #string l1 Latitude or longitude as string in the format DD:MM:SS N/S/W/E +-- @param #string l2 Latitude or longitude as string in the format DD:MM:SS N/S/W/E +-- @return #number Latitude in decimal degree format. +-- @return #number Longitude in decimal degree format. +function ARTY:_LLDMS2DD(l1,l2) + self:F2(l1,l2) + + -- Make an array of lat and long. + local _latlong={l1,l2} + + local _latitude=nil + local _longitude=nil + + for _,ll in pairs(_latlong) do + + -- Format is expected as "DD:MM:SS" or "D:M:S". + local _format = "%d+:%d+:%d+" + local _ldms=ll:match(_format) + + if _ldms then + + -- Split DMS to degrees, minutes and seconds. + local _dms=self:_split(_ldms, ":") + local _deg=tonumber(_dms[1]) + local _min=tonumber(_dms[2]) + local _sec=tonumber(_dms[3]) + + -- Convert DMS to DD. + local function DMS2DD(d,m,s) + return d+m/60+s/3600 + end + + -- Detect with hemisphere is meant. + if ll:match("N") then + _latitude=DMS2DD(_deg,_min,_sec) + elseif ll:match("S") then + _latitude=-DMS2DD(_deg,_min,_sec) + elseif ll:match("W") then + _longitude=-DMS2DD(_deg,_min,_sec) + elseif ll:match("E") then + _longitude=DMS2DD(_deg,_min,_sec) + end + + -- Debug text. + local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec) + self:T2(ARTY.id..text) + + end + end + + -- Debug text. + local text=string.format("\nLatitude %s", tostring(_latitude)) + text=text..string.format("\nLongitude %s", tostring(_longitude)) + self:T2(ARTY.id..text) + + return _latitude,_longitude +end + +--- Convert time in seconds to hours, minutes and seconds. +-- @param #ARTY self +-- @param #number seconds Time in seconds. +-- @return #string Time in format Hours:minutes:seconds. +function ARTY:_SecondsToClock(seconds) + self:F3({seconds=seconds}) + + if seconds==nil then + return nil + end + + -- Seconds + local seconds = tonumber(seconds) + + -- Seconds of this day. + local _seconds=seconds%(60*60*24) + + if seconds <= 0 then + return nil + else + local hours = string.format("%02.f", math.floor(_seconds/3600)) + local mins = string.format("%02.f", math.floor(_seconds/60 - (hours*60))) + local secs = string.format("%02.f", math.floor(_seconds - hours*3600 - mins *60)) + local days = string.format("%d", seconds/(60*60*24)) + return hours..":"..mins..":"..secs.."+"..days + end +end + +--- Convert clock time from hours, minutes and seconds to seconds. +-- @param #ARTY self +-- @param #string clock String of clock time. E.g., "06:12:35". +function ARTY:_ClockToSeconds(clock) + self:F3({clock=clock}) + + if clock==nil then + return nil + end + + -- Seconds init. + local seconds=0 + + -- Split additional days. + local dsplit=self:_split(clock, "+") + + -- Convert days to seconds. + if #dsplit>1 then + seconds=seconds+tonumber(dsplit[2])*60*60*24 + end + + -- Split hours, minutes, seconds + local tsplit=self:_split(dsplit[1], ":") + + -- Get time in seconds + local i=1 + for _,time in ipairs(tsplit) do + if i==1 then + -- Hours + seconds=seconds+tonumber(time)*60*60 + elseif i==2 then + -- Minutes + seconds=seconds+tonumber(time)*60 + elseif i==3 then + -- Seconds + seconds=seconds+tonumber(time) + end + i=i+1 + end + + self:T3(ARTY.id..string.format("Clock %s = %d seconds", clock, seconds)) + return seconds +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + \ No newline at end of file diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 016bb10ef..63bda13d6 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -7,7 +7,8 @@ -- -- === -- --- @module CleanUp +-- @module Functional.CleanUp +-- @image CleanUp_Airbases.JPG --- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-) -- @field #map<#string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases. @@ -16,11 +17,8 @@ --- @type CLEANUP_AIRBASE -- @extends #CLEANUP_AIRBASE.__ ---- # CLEANUP_AIRBASE, extends @{Base#BASE} +--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat. -- --- ![Banner Image](..\Presentations\CLEANUP_AIRBASE\Dia1.JPG) --- --- The CLEANUP_AIRBASE class keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat. -- Specific airbases need to be provided that need to be guarded. Each airbase registered, will be guarded within a zone of 8 km around the airbase. -- Any unit that fires a missile, or shoots within the zone of an airbase, will be monitored by CLEANUP_AIRBASE. -- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits. @@ -173,7 +171,7 @@ end ---- Destroys a @{Unit} from the simulator, but checks first if it is still existing! +--- Destroys a @{Wrapper.Unit} from the simulator, but checks first if it is still existing! -- @param #CLEANUP_AIRBASE self -- @param Wrapper.Unit#UNIT CleanUpUnit The object to be destroyed. function CLEANUP_AIRBASE.__:DestroyUnit( CleanUpUnit ) @@ -200,7 +198,7 @@ end --- Destroys a missile from the simulator, but checks first if it is still existing! -- @param #CLEANUP_AIRBASE self --- @param Dcs.DCSTypes#Weapon MissileObject +-- @param DCS#Weapon MissileObject function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject ) self:F( { MissileObject } ) @@ -290,9 +288,9 @@ function CLEANUP_AIRBASE.__:OnEventHit( Event ) end end ---- Add the @{DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. +--- Add the @{DCS#Unit} to the CleanUpList for CleanUp. -- @param #CLEANUP_AIRBASE self --- @param Wrapper.Unit#UNIT CleanUpUnit +-- @param DCS#UNIT CleanUpUnit -- @oaram #string CleanUpUnitName function CLEANUP_AIRBASE.__:AddForCleanUp( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) diff --git a/Moose Development/Moose/Functional/Designate.lua b/Moose Development/Moose/Functional/Designate.lua index a633a8801..f23961e12 100644 --- a/Moose Development/Moose/Functional/Designate.lua +++ b/Moose Development/Moose/Functional/Designate.lua @@ -2,7 +2,7 @@ -- -- === -- --- DESIGNATE is orchestrating the designation of potential targets executed by a Recce group, +-- Orchestrate the designation of potential targets executed by a Recce group, -- and communicates these to a dedicated attacking group of players, -- so that following a dynamically generated menu system, -- each detected set of potential targets can be lased or smoked... @@ -28,17 +28,16 @@ -- -- * **FlightControl**: Design & Programming -- --- @module Designate - +-- @module Functional.Designate +-- @image Designation.JPG do -- DESIGNATE --- @type DESIGNATE -- @extends Core.Fsm#FSM_PROCESS - --- # DESIGNATE class, extends @{Fsm#FSM} + --- Manage the designation of detected targets. -- - -- DESIGNATE is managing the designation of detected targets. -- Targets detected by recce will be communicated to a group of attacking players. -- A menu system is made available that allows to: -- @@ -268,7 +267,7 @@ do -- DESIGNATE -- -- ## 6. Designate Menu Location for a Mission -- - -- You can make DESIGNATE work for a @{Mission#MISSION} object. In this way, the designate menu will not appear in the root of the radio menu, but in the menu of the Mission. + -- You can make DESIGNATE work for a @{Tasking.Mission#MISSION} object. In this way, the designate menu will not appear in the root of the radio menu, but in the menu of the Mission. -- Use the method @{#DESIGNATE.SetMission}() to set the @{Mission} object for the designate function. -- -- ## 7. Status Report diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 98f7cb024..33b4c9331 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2,11 +2,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\DETECTION\Dia1.JPG) --- --- === --- --- DETECTION classes facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnassance Units). +-- Facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnassance Units). -- DETECTION uses the in-built detection capabilities of DCS World, but adds new functionalities. -- -- Find the DETECTION classes documentation further in this document in the globals section. @@ -29,7 +25,8 @@ -- -- * FlightControl : Analysis, Design, Programming, Testing -- --- @module Detection +-- @module Functional.Detection +-- @image Detection.JPG ----BASE:TraceClass("DETECTION_BASE") ----BASE:TraceClass("DETECTION_AREAS") @@ -40,16 +37,14 @@ do -- DETECTION_BASE --- @type DETECTION_BASE -- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. + -- @field DCS#Distance DetectionRange The range till which targets are accepted to be detected. -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun -- @extends Core.Fsm#FSM - --- DETECTION_BASE class, extends @{Fsm#FSM} - -- - -- The DETECTION_BASE class defines the core functions to administer detected objects. - -- The DETECTION_BASE class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). + --- Defines the core functions to administer detected objects. + -- The DETECTION_BASE class will detect objects within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s). -- -- ## DETECTION_BASE constructor -- @@ -105,11 +100,11 @@ do -- DETECTION_BASE -- -- Various methods exist how to retrieve the grouped items from a DETECTION_BASE derived class: -- - -- * The method @{Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. - -- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). + -- * The method @{Functional.Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. + -- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). -- Note that this method returns a DetectedItem element from the list, that contains a Set variable and further information -- about the DetectedItem that is set by the DETECTION_BASE derived classes, used to group the DetectedItem. - -- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). + -- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). -- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ). -- -- ## **Visual filters** to fine-tune the probability of the detected objects @@ -146,7 +141,7 @@ do -- DETECTION_BASE -- -- Note that based on this probability factor, not only the detection but also the **type** of the unit will be applied! -- - -- Use the method @{Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. -- -- ### Alpha Angle visual detection probability -- @@ -158,7 +153,7 @@ do -- DETECTION_BASE -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% -- - -- Use the method @{Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. -- -- ### Cloudy Zones detection probability -- @@ -166,7 +161,7 @@ do -- DETECTION_BASE -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission -- zones that reflect cloudy areas where detected units may not be so easily visually detected. -- - -- Use the method @{Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. -- -- Note however, that the more zones are defined to be "cloudy" within a detection, the more performance it will take -- from the DETECTION_BASE to calculate the presence of the detected unit within each zone. @@ -183,7 +178,7 @@ do -- DETECTION_BASE -- ### Detection acceptance of within range limit -- -- A range can be set that will limit a successful detection for a unit. - -- Use the method @{Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. -- @@ -200,7 +195,7 @@ do -- DETECTION_BASE -- ### Detection acceptance if within zone(s). -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s). - -- Use the method @{Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. -- @@ -220,7 +215,7 @@ do -- DETECTION_BASE -- ### Detection rejectance if within zone(s). -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s). - -- Use the method @{Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. -- An example of how to use the method is shown below. -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. @@ -240,7 +235,7 @@ do -- DETECTION_BASE -- -- ## Detection of Friendlies Nearby -- - -- Use the method @{Detection#DETECTION_BASE.SetFriendliesRange}() to set the range what will indicate when friendlies are nearby + -- Use the method @{Functional.Detection#DETECTION_BASE.SetFriendliesRange}() to set the range what will indicate when friendlies are nearby -- a DetectedItem. The default range is 6000 meters. For air detections, it is advisory to use about 30.000 meters. -- -- ## DETECTION_BASE is a Finite State Machine @@ -510,7 +505,6 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. function DETECTION_BASE:onafterDetect(From,Event,To) - self:F( { From, Event, To } ) local DetectDelay = 0.1 self.DetectionCount = 0 @@ -533,7 +527,6 @@ do -- DETECTION_BASE -- @param #string To The To State string. -- @param Wrapper.Group#GROUP DetectionGroup The Group detecting. function DETECTION_BASE:onafterDetectionGroup( From, Event, To, DetectionGroup, DetectionTimeStamp ) - self:F( { From, Event, To } ) self.DetectionRun = self.DetectionRun + 1 @@ -541,7 +534,7 @@ do -- DETECTION_BASE if DetectionGroup:IsAlive() then - self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) + --self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) local DetectionGroupName = DetectionGroup:GetName() local DetectionUnit = DetectionGroup:GetUnit(1) @@ -557,10 +550,10 @@ do -- DETECTION_BASE self.DetectDLINK ) - self:F( DetectedTargets ) + --self:F( DetectedTargets ) for DetectionObjectID, Detection in pairs( DetectedTargets ) do - local DetectedObject = Detection.object -- Dcs.DCSWrapper.Object#Object + local DetectedObject = Detection.object -- DCS#Object if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then @@ -574,7 +567,7 @@ do -- DETECTION_BASE self.DetectDLINK ) - self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) + --self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) -- Only process if the target is visible. Detection also returns invisible units. --if Detection.visible == true then @@ -596,7 +589,7 @@ do -- DETECTION_BASE local DetectedUnitCategory = DetectedObject:getDesc().category - self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } ) + --self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } ) -- Calculate Acceptance @@ -644,7 +637,7 @@ do -- DETECTION_BASE local DistanceProbability = 1 - DistanceProbabilityReversed DistanceProbability = DistanceProbability * 30 / 300 local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, DistanceProbability } ) + --self:T( { Probability, DistanceProbability } ) if Probability > DistanceProbability then DetectionAccepted = false end @@ -660,7 +653,7 @@ do -- DETECTION_BASE AlphaAngleProbability = AlphaAngleProbability * 30 / 300 local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, AlphaAngleProbability } ) + --self:T( { Probability, AlphaAngleProbability } ) if Probability > AlphaAngleProbability then DetectionAccepted = false end @@ -677,7 +670,7 @@ do -- DETECTION_BASE if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, ZoneProbability } ) + --self:T( { Probability, ZoneProbability } ) if Probability > ZoneProbability then DetectionAccepted = false break @@ -702,7 +695,7 @@ do -- DETECTION_BASE self.DetectedObjects[DetectedObjectName].Distance = Distance self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp - self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) + --self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) local DetectedUnit = UNIT:FindByName( DetectedObjectName ) @@ -716,7 +709,7 @@ do -- DETECTION_BASE --end end - self:T2( self.DetectedObjects ) + --self:T2( self.DetectedObjects ) end if HasDetectedObjects then @@ -726,7 +719,6 @@ do -- DETECTION_BASE end if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then - self:T( "--> Create Detection Sets" ) -- First check if all DetectedObjects were detected. -- This is important. When there are DetectedObjects in the list, but were not detected, @@ -766,7 +758,6 @@ do -- DETECTION_BASE local DetectedSet = DetectedItem.Set if DetectedSet:Count() == 0 then - self:F3( { DetectedItemID = DetectedItemID } ) self:RemoveDetectedItem( DetectedItemID ) end @@ -778,8 +769,7 @@ do -- DETECTION_BASE -- @param #string UnitName The UnitName that needs to be forgotten from the DetectionItem Sets. -- @return #DETECTION_BASE function DETECTION_BASE:ForgetDetectedUnit( UnitName ) - self:F2() - + local DetectedItems = self:GetDetectedItems() for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do @@ -796,7 +786,6 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CreateDetectionItems() - self:F2() self:F( "Error, in DETECTION_BASE class..." ) return self @@ -902,7 +891,7 @@ do -- DETECTION_BASE -- DetectionObject:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) -- -- @param #DETECTION_BASE self - -- @param #list FilterCategories The Categories entries + -- @param #list FilterCategories The Categories entries -- @return #DETECTION_BASE self function DETECTION_BASE:FilterCategories( FilterCategories ) self:F2() @@ -1176,18 +1165,17 @@ do -- DETECTION_BASE --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self -- @param DetectedItem - -- @param Dcs.DCSUnit#Unit.Category Category The category of the unit. + -- @param DCS#Unit.Category Category The category of the unit. -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) - - self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) + --self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false end --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self -- @param DetectedItem - -- @param Dcs.DCSUnit#Unit.Category Category The category of the unit. + -- @param DCS#Unit.Category Category The category of the unit. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category ) @@ -1237,7 +1225,7 @@ do -- DETECTION_BASE --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_BASE self function DETECTION_BASE:ReportFriendliesNearBy( TargetData ) - self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) + --self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) local DetectedItem = TargetData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = TargetData.DetectedItem.Set @@ -1260,9 +1248,9 @@ do -- DETECTION_BASE } - --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit + --- @param DCS#Unit FoundDCSUnit -- @param Wrapper.Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup + -- @param Core.Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem @@ -1286,7 +1274,7 @@ do -- DETECTION_BASE if FoundUnitInReportSetGroup == true then -- If the recce was part of the friendlies found, then check if the recce is part of the allowed friendly unit prefixes. for PrefixID, Prefix in pairs( self.FriendlyPrefixes or {} ) do - self:F( { "Friendly Prefix:", Prefix = Prefix } ) + --self:F( { "Friendly Prefix:", Prefix = Prefix } ) -- In case a match is found (so a recce unit name is part of the friendly prefixes), then report that recce to be part of the friendlies. -- This is important if CAP planes (so planes using their own radar) to be scanning for targets as part of the EWR network. -- But CAP planes are also attackers, so they need to be considered friendlies too! @@ -1298,7 +1286,7 @@ do -- DETECTION_BASE end end - self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + --self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then local FriendlyUnit = UNIT:Find( FoundDCSUnit ) @@ -1313,7 +1301,7 @@ do -- DETECTION_BASE local Distance = DetectedUnitCoord:Get2DDistance( FriendlyUnit:GetCoordinate() ) DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} DetectedItem.FriendliesDistance[Distance] = FriendlyUnit - self:T( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) + --self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) return true end @@ -1355,6 +1343,9 @@ do -- DETECTION_BASE end ) end + + self:F( { Friendlies = DetectedItem.FriendliesNearBy, Players = DetectedItem.PlayersNearBy } ) + end end @@ -1599,7 +1590,7 @@ do -- DETECTION_BASE return "" end - --- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. + --- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Core.Set#SET_UNIT DetectedSet @@ -1648,7 +1639,7 @@ do -- DETECTION_BASE do -- Zones - --- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. + --- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @return Core.Zone#ZONE_UNIT DetectedZone @@ -1811,14 +1802,13 @@ end do -- DETECTION_UNITS - --- # DETECTION_UNITS class, extends @{Detection#DETECTION_BASE} + --- Will detect units within the battle zone. -- - -- The DETECTION_UNITS class will detect units within the battle zone. - -- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. + -- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. -- Beware that when the amount of units detected is large, the DetectedItems list will be large also. -- -- @type DETECTION_UNITS - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are detected. + -- @field DCS#Distance DetectionRange The range till which targets are detected. -- @extends #DETECTION_BASE DETECTION_UNITS = { ClassName = "DETECTION_UNITS", @@ -1886,7 +1876,6 @@ do -- DETECTION_UNITS -- @param #DETECTION_UNITS self -- @return #DETECTION_UNITS self function DETECTION_UNITS:CreateDetectionItems() - self:F2( #self.DetectedObjects ) -- Loop the current detected items, and check if each object still exists and is detected. @@ -2062,11 +2051,9 @@ end do -- DETECTION_TYPES - --- # 3) DETECTION_TYPES class, extends @{Detection#DETECTION_BASE} - -- - -- The DETECTION_TYPES class will detect units within the battle zone. + --- Will detect units within the battle zone. -- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. - -- Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. + -- Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. -- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. -- -- @type DETECTION_TYPES @@ -2137,7 +2124,6 @@ do -- DETECTION_TYPES -- @param #DETECTION_TYPES self -- @return #DETECTION_TYPES self function DETECTION_TYPES:CreateDetectionItems() - self:F2( #self.DetectedObjects ) -- Loop the current detected items, and check if each object still exists and is detected. @@ -2272,41 +2258,39 @@ end do -- DETECTION_AREAS - --- # 4) DETECTION_AREAS class, extends @{Detection#DETECTION_BASE} - -- - -- The DETECTION_AREAS class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), - -- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. + --- Detect units within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s), + -- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- - -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Detection#DECTECTION_BASE} and - -- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Detection#DETECTION_AREAS}. + -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and + -- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}. -- - -- Retrieve the DetectedItems[].Set with the method @{Detection#DETECTION_BASE.GetDetectedSet}(). A @{Set#SET_UNIT} object will be returned. + -- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned. -- - -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). - -- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). - -- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. + -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). + -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). + -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. -- -- ## 4.4) Flare or Smoke detected units -- - -- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. + -- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- ## 4.5) Flare or Smoke or Bound detected zones -- -- Use the methods: -- - -- * @{Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color - -- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color - -- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag + -- * @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color + -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color + -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag -- -- the detected zones when a new detection has taken place. -- -- @type DETECTION_AREAS - -- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. - -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. + -- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. + -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends #DETECTION_BASE DETECTION_AREAS = { ClassName = "DETECTION_AREAS", @@ -2317,7 +2301,7 @@ do -- DETECTION_AREAS --- DETECTION_AREAS constructor. -- @param #DETECTION_AREAS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. + -- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @return #DETECTION_AREAS function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) @@ -2525,10 +2509,9 @@ do -- DETECTION_AREAS -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:CreateDetectionItems() - self:F2() - self:T( "Checking Detected Items for new Detected Units ..." ) + self:T2( "Checking Detected Items for new Detected Units ..." ) -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. -- Regroup when needed, split groups when needed. for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do @@ -2537,8 +2520,7 @@ do -- DETECTION_AREAS if DetectedItem then - self:T( { "Detected Item ID:", DetectedItemID } ) - + self:T2( { "Detected Item ID: ", DetectedItemID } ) local DetectedSet = DetectedItem.Set @@ -2667,7 +2649,6 @@ do -- DETECTION_AREAS local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem if DetectedItem then - self:T( "Detection Area #" .. DetectedItem.ID ) local DetectedSet = DetectedItem.Set if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedItem.Zone ) then self:IdentifyDetectedObject( DetectedObject ) diff --git a/Moose Development/Moose/Functional/Escort.lua b/Moose Development/Moose/Functional/Escort.lua index 6fcddbe7c..c76742736 100644 --- a/Moose Development/Moose/Functional/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -2,61 +2,60 @@ -- -- === -- --- @{#ESCORT} class --- === --- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead. +-- Allows you to interact with escorting AI on your flight and take the lead. +-- -- Each escorting group can be commanded with a whole 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. -- --- RADIO MENUs that can be created: --- === +-- # RADIO MENUs that can be created: +-- -- Find a summary below of the current available commands: -- --- Navigation ...: --- --------------- +-- ## Navigation ...: +-- -- Escort group navigation functions: -- -- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you. -- * **"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 ...: --- ------------------ +-- ## Hold position ...: +-- -- Escort group navigation functions: -- -- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. -- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. -- --- Report targets ...: --- ------------------- +-- ## Report targets ...: +-- -- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below). -- -- * **"Report now":** Will report the current detected targets. -- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list. -- * **"Report targets off":** Will stop detecting targets. -- --- Scan targets ...: --- ----------------- +-- ## Scan targets ...: +-- -- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task. -- -- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. -- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. -- --- Attack 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. -- --- Request assistance from ...: --- ---------------------------- +-- ## Request assistance from ...: +-- -- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**. -- This menu item allows to request attack support from other escorts supporting the current client group. -- 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 ...: --- -------- +-- ## ROE ...: +-- -- Sets the Rules of Engagement (ROE) of the escort group when in flight. -- -- * **"Hold Fire":** The escort group will hold fire. @@ -64,8 +63,8 @@ -- * **"Open Fire":** The escort group will open fire on designated targets. -- * **"Weapon Free":** The escort group will engage with any target. -- --- Evasion ...: --- ------------ +-- ## 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. @@ -73,19 +72,19 @@ -- * **"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 ...: --- ------------------- +-- ## 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. -- --- ESCORT construction methods. --- === +-- # ESCORT construction methods. +-- -- Create a new SPAWN object with the @{#ESCORT.New} method: -- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. +-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. -- --- ESCORT initialization methods. --- === +-- # ESCORT initialization methods. +-- -- The following menus are created within the RADIO MENU (F10) of an active unit hosted by a player: -- -- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client. @@ -114,8 +113,8 @@ -- -- -- --- @module Escort --- @author FlightControl +-- @module Functional.Escort +-- @image Escorting.JPG --- ESCORT class -- @type ESCORT @@ -127,8 +126,8 @@ -- @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 Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. +-- @Field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. +-- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. -- @field FunctionalMENU_GROUPDETECTION_BASE Detection ESCORT = { ClassName = "ESCORT", @@ -295,7 +294,7 @@ end --- Defines a menu slot to let the escort Join and Follow you at a certain distance. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. +-- @param DCS#Distance Distance The distance in meters that the escort needs to follow the client. -- @return #ESCORT function ESCORT:MenuFollowAt( Distance ) self:F(Distance) @@ -320,8 +319,8 @@ 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 #ESCORT self --- @param Dcs.DCSTypes#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.DCSTypes#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 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 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 #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -381,8 +380,8 @@ 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 #ESCORT self --- @param Dcs.DCSTypes#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.DCSTypes#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 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 #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -442,8 +441,8 @@ 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 #ESCORT self --- @param Dcs.DCSTypes#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.DCSTypes#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 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 #ESCORT function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) @@ -567,7 +566,7 @@ end -- 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 #ESCORT self --- @param Dcs.DCSTypes#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. +-- @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 #ESCORT function ESCORT:MenuReportTargets( Seconds ) self:F( { Seconds } ) @@ -737,7 +736,7 @@ end -- @param Functional.Escort#ESCORT self -- @param Wrapper.Group#GROUP EscortGroup -- @param Wrapper.Client#CLIENT EscortClient --- @param Dcs.DCSTypes#Distance Distance +-- @param DCS#Distance Distance function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) diff --git a/Moose Development/Moose/Functional/MissileTrainer.lua b/Moose Development/Moose/Functional/MissileTrainer.lua index 7659b3c8a..cc8830a14 100644 --- a/Moose Development/Moose/Functional/MissileTrainer.lua +++ b/Moose Development/Moose/Functional/MissileTrainer.lua @@ -2,8 +2,6 @@ -- -- === -- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} --- === -- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- the class will destroy the missile within a certain range, to avoid damage to your aircraft. -- It suports the following functionality: @@ -77,8 +75,8 @@ -- Danny has shared his ideas and together we made a design. -- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! -- --- @module MissileTrainer --- @author FlightControl +-- @module Functional.MissileTrainer +-- @image Missile_Trainer.JPG --- The MISSILETRAINER class diff --git a/Moose Development/Moose/Functional/Movement.lua b/Moose Development/Moose/Functional/Movement.lua index a5d4d7058..f01dad139 100644 --- a/Moose Development/Moose/Functional/Movement.lua +++ b/Moose Development/Moose/Functional/Movement.lua @@ -7,7 +7,8 @@ -- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if -- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units -- on defined intervals (currently every minute). --- @module Movement +-- @module Functional.Movement +-- @image MOOSE.JPG --- the MOVEMENT class -- @type MOVEMENT diff --git a/Moose Development/Moose/Functional/Protect.lua b/Moose Development/Moose/Functional/Protect.lua deleted file mode 100644 index f8375b867..000000000 --- a/Moose Development/Moose/Functional/Protect.lua +++ /dev/null @@ -1,305 +0,0 @@ ---- **Functional** -- The PROTECT class handles the protection of objects, which can be zones, units, scenery. --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: **MillerTime** --- --- === --- --- @module Protect - ---- @type PROTECT.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-) --- @extends Core.Fsm#FSM - ---- @type PROTECT --- @extends #PROTECT.__ - ---- # PROTECT, extends @{Base#BASE} --- --- @field #PROTECT -PROTECT = { - ClassName = "PROTECT", -} - ---- Get the ProtectZone --- @param #PROTECT self --- @return Core.Zone#ZONE_BASE -function PROTECT:GetProtectZone() - return self.ProtectZone -end - - ---- Get the name of the ProtectZone --- @param #PROTECT self --- @return #string -function PROTECT:GetProtectZoneName() - return self.ProtectZone:GetName() -end - - ---- Set the owning coalition of the zone. --- @param #PROTECT self --- @param DCSCoalition.DCSCoalition#coalition Coalition -function PROTECT:SetCoalition( Coalition ) - self.Coalition = Coalition -end - - ---- Get the owning coalition of the zone. --- @param #PROTECT self --- @return DCSCoalition.DCSCoalition#coalition Coalition. -function PROTECT:GetCoalition() - return self.Coalition -end - - ---- Get the owning coalition name of the zone. --- @param #PROTECT self --- @return #string Coalition name. -function PROTECT:GetCoalitionName() - - if self.Coalition == coalition.side.BLUE then - return "Blue" - end - - if self.Coalition == coalition.side.RED then - return "Red" - end - - if self.Coalition == coalition.side.NEUTRAL then - return "Neutral" - end - - return "" -end - - -function PROTECT:IsGuarded() - - local IsGuarded = self.ProtectZone:IsAllInZoneOfCoalition( self.Coalition ) - self:F( { IsGuarded = IsGuarded } ) - return IsGuarded -end - -function PROTECT:IsCaptured() - - local IsCaptured = self.ProtectZone:IsAllInZoneOfOtherCoalition( self.Coalition ) - self:F( { IsCaptured = IsCaptured } ) - return IsCaptured -end - - -function PROTECT:IsAttacked() - - local IsAttacked = self.ProtectZone:IsSomeInZoneOfCoalition( self.Coalition ) - self:F( { IsAttacked = IsAttacked } ) - return IsAttacked -end - - -function PROTECT:IsEmpty() - - local IsEmpty = self.ProtectZone:IsNoneInZone() - self:F( { IsEmpty = IsEmpty } ) - return IsEmpty -end - - ---- Check if the units are still alive. --- @param #PROTECT self -function PROTECT:AreProtectUnitsAlive() - - local IsAlive = false - - local UnitSet = self.ProtectUnitSet - UnitSet:Flush( self ) - local UnitList = UnitSet:GetSet() - - for UnitID, ProtectUnit in pairs( UnitList ) do - local IsUnitAlive = ProtectUnit:IsAlive() - if IsUnitAlive == true then - IsAlive = true - break - end - end - - return IsAlive -end - ---- Check if the statics are still alive. --- @param #PROTECT self -function PROTECT:AreProtectStaticsAlive() - - local IsAlive = false - - local StaticSet = self.ProtectStaticSet - StaticSet:Flush( self ) - local StaticList = StaticSet:GetSet() - - for UnitID, ProtectStatic in pairs( StaticList ) do - local IsStaticAlive = ProtectStatic:IsAlive() - if IsStaticAlive == true then - IsAlive = true - break - end - end - - return IsAlive -end - - ---- Check if there is a capture unit in the zone. --- @param #PROTECT self -function PROTECT:IsCaptureUnitInZone() - - local CaptureUnitSet = self.CaptureUnitSet - CaptureUnitSet:Flush( self ) - - local IsInZone = self.CaptureUnitSet:IsPartiallyInZone( self.ProtectZone ) - - self:F({IsInZone = IsInZone}) - - return IsInZone -end - ---- Smoke. --- @param #PROTECT self --- @param #SMOKECOLOR.Color SmokeColor -function PROTECT:Smoke( SmokeColor ) - - self.SmokeColor = SmokeColor -end - - ---- Flare. --- @param #PROTECT self --- @param #SMOKECOLOR.Color FlareColor -function PROTECT:Flare( FlareColor ) - self.ProtectZone:FlareZone( FlareColor, math.random( 1, 360 ) ) -end - - ---- Mark. --- @param #PROTECT self -function PROTECT:Mark() - - local Coord = self.ProtectZone:GetCoordinate() - local ZoneName = self:GetProtectZoneName() - local State = self:GetState() - - if self.MarkRed and self.MarkBlue then - self:F( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } ) - Coord:RemoveMark( self.MarkRed ) - Coord:RemoveMark( self.MarkBlue ) - end - - if self.Coalition == coalition.side.BLUE then - self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State ) - self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State ) - else - self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State ) - self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State ) - end -end - - ---- Bound. --- @param #PROTECT self -function PROTECT:onafterStart() - - self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusCoalition, self ) - self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusZone, self ) - self:ScheduleRepeat( 10, 15, 0, nil, self.StatusSmoke, self ) -end - ---- Bound. --- @param #PROTECT self -function PROTECT:onenterGuarded() - - - if self.Coalition == coalition.side.BLUE then - --elf.ProtectZone:BoundZone( 12, country.id.USA ) - else - --self.ProtectZone:BoundZone( 12, country.id.RUSSIA ) - end - - self:Mark() - -end - -function PROTECT:onenterCaptured() - - local NewCoalition = self.ProtectZone:GetCoalition() - self:F( { NewCoalition = NewCoalition } ) - self:SetCoalition( NewCoalition ) - - self:Mark() -end - - -function PROTECT:onenterEmpty() - - self:Mark() -end - - -function PROTECT:onenterAttacked() - - self:Mark() -end - - ---- Check status Coalition ownership. --- @param #PROTECT self -function PROTECT:StatusCoalition() - - self:F( { State = self:GetState() } ) - - self.ProtectZone:Scan() - - if self:IsGuarded() then - self:Guard() - else - if self:IsCaptured() then - self:Capture() - end - end -end - ---- Check status Zone. --- @param #PROTECT self -function PROTECT:StatusZone() - - self:F( { State = self:GetState() } ) - - self.ProtectZone:Scan() - - if self:IsAttacked() then - self:Attack() - else - if self:IsEmpty() then - self:Empty() - end - end -end - ---- Check status Smoke. --- @param #PROTECT self -function PROTECT:StatusSmoke() - - local CurrentTime = timer.getTime() - - if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then - if self.SmokeColor then - self.ProtectZone:GetCoordinate():Smoke( self.SmokeColor ) - --self.SmokeColor = nil - self.SmokeTime = CurrentTime - end - end -end - - - - - diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua new file mode 100644 index 000000000..a6b42d68f --- /dev/null +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -0,0 +1,1000 @@ +--- **Functional** - (R2.4) Rudimentary ATC. +-- +-- ![Banner Image](..\Presentations\PSEUDOATC\PSEUDOATC_Main.jpg) +-- +-- ==== +-- +-- The pseudo ATC enhances the standard DCS ATC functions. +-- +-- In particular, a menu entry "Pseudo ATC" is created in the "F10 Other..." radiomenu. +-- +-- ## Features +-- +-- * Weather report at nearby airbases and mission waypoints. +-- * Report absolute bearing and range to nearest airports and mission waypoints. +-- * Report current altitude AGL of own aircraft. +-- * Upon request, ATC reports altitude until touchdown. +-- * Works with static and dynamic weather. +-- * Player can select the unit system (metric or imperial) in which information is reported. +-- * All maps supported (Caucasus, NTTR, Normandy, Persian Gulf and all future maps). +-- +-- ==== +-- +-- # Demo Missions +-- +-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS) +-- +-- ==== +-- +-- # YouTube Channel +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- +-- === +-- +-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** +-- +-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536) +-- +-- ==== +-- @module Functional.PseudoATC +-- @image Pseudo_ATC.JPG + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- PSEUDOATC class +-- @type PSEUDOATC +-- @field #string ClassName Name of the Class. +-- @field #table player Table comprising each player info. +-- @field #boolean Debug If true, print debug info to dcs.log file. +-- @field #number mdur Duration in seconds how low messages to the player are displayed. +-- @field #number mrefresh Interval in seconds after which the F10 menu is refreshed. E.g. by the closest airports. Default is 120 sec. +-- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec. +-- @field #boolean chatty Display some messages on events like take-off and touchdown. +-- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. +-- @extends Core.Base#BASE + +--- Adds some rudimentary ATC functionality via the radio menu. +-- +-- Local weather reports can be requested for nearby airports and player's mission waypoints. +-- The weather report includes +-- +-- * QFE and QNH pressures, +-- * Temperature, +-- * Wind direction and strength. +-- +-- The list of airports is updated every 60 seconds. This interval can be adjusted by the function @{#PSEUDOATC.SetMenuRefresh}(*interval*). +-- +-- Likewise, absolute bearing and range to the close by airports and mission waypoints can be requested. +-- +-- The player can switch the unit system in which all information is displayed during the mission with the MOOSE settings radio menu. +-- The unit system can be set to either imperial or metric. Altitudes are reported in feet or meter, distances in kilometers or nautical miles, +-- temperatures in degrees Fahrenheit or Celsius and QFE/QNH pressues in inHg or mmHg. +-- Note that the pressures are also reported in hPa independent of the unit system setting. +-- +-- In bad weather conditions, the ATC can "talk you down", i.e. will continuously report your altitude on the final approach. +-- Default reporting time interval is 3 seconds. This can be adjusted via the @{#PSEUDOATC.SetReportAltInterval}(*interval*) function. +-- The reporting stops automatically when the player lands or can be stopped manually by clicking on the radio menu item again. +-- So the radio menu item acts as a toggle to switch the reporting on and off. +-- +-- ## Scripting +-- +-- Scripting is almost trivial. Just add the following two lines to your script: +-- +-- pseudoATC=PSEUDOATC:New() +-- pseudoATC:Start() +-- +-- +-- @field #PSEUDOATC +PSEUDOATC={ + ClassName = "PSEUDOATC", + player={}, + Debug=false, + mdur=30, + mrefresh=120, + talt=3, + chatty=true, + eventsmoose=true, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Some ID to identify who we are in output of the DCS.log file. +-- @field #string id +PSEUDOATC.id="PseudoATC | " + +--- PSEUDOATC version. +-- @field #number version +PSEUDOATC.version="0.9.0" + +----------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO list +-- DONE: Add takeoff event. +-- DONE: Add user functions. + +----------------------------------------------------------------------------------------------------------------------------------------- + +--- PSEUDOATC contructor. +-- @param #PSEUDOATC self +-- @return #PSEUDOATC Returns a PSEUDOATC object. +function PSEUDOATC:New() + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #PSEUDOATC + + -- Debug info + self:E(PSEUDOATC.id..string.format("PseudoATC version %s", PSEUDOATC.version)) + + -- Return object. + return self +end + +--- Starts the PseudoATC event handlers. +-- @param #PSEUDOATC self +function PSEUDOATC:Start() + self:F() + + -- Debug info + self:E(PSEUDOATC.id.."Starting PseudoATC") + + -- Handle events. + if self.eventsmoose then + self:T(PSEUDOATC.id.."Events are handled by MOOSE.") + self:HandleEvent(EVENTS.Birth, self._OnBirth) + self:HandleEvent(EVENTS.Land, self._PlayerLanded) + self:HandleEvent(EVENTS.Takeoff, self._PlayerTakeOff) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) + self:HandleEvent(EVENTS.Crash, self._PlayerLeft) + --self:HandleEvent(EVENTS.Ejection, self._PlayerLeft) + --self:HandleEvent(EVENTS.PilotDead, self._PlayerLeft) + else + self:T(PSEUDOATC.id.."Events are handled by DCS.") + -- Events are handled directly by DCS. + world.addEventHandler(self) + end + +end + +----------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions + +--- Debug mode on. Send messages to everone. +-- @param #PSEUDOATC self +function PSEUDOATC:DebugOn() + self.Debug=true +end + +--- Debug mode off. This is the default setting. +-- @param #PSEUDOATC self +function PSEUDOATC:DebugOff() + self.Debug=false +end + +--- Chatty mode on. Display some messages on take-off and touchdown. +-- @param #PSEUDOATC self +function PSEUDOATC:ChattyOn() + self.chatty=true +end + +--- Chatty mode off. Don't display some messages on take-off and touchdown. +-- @param #PSEUDOATC self +function PSEUDOATC:ChattyOff() + self.chatty=false +end + +--- Set duration how long messages are displayed. +-- @param #PSEUDOATC self +-- @param #number duration Time in seconds. Default is 30 sec. +function PSEUDOATC:SetMessageDuration(duration) + self.mdur=duration or 30 +end + +--- Set time interval after which the F10 radio menu is refreshed. +-- @param #PSEUDOATC self +-- @param #number interval Interval in seconds. Default is every 120 sec. +function PSEUDOATC:SetMenuRefresh(interval) + self.mrefresh=interval or 120 +end + +--- Enable/disable event handling by MOOSE or DCS. +-- @param #PSEUDOATC self +-- @param #boolean switch If true, events are handled by MOOSE (default). If false, events are handled directly by DCS. +function PSEUDOATC:SetEventsMoose(switch) + self.eventsmoose=switch +end + +--- Set time interval for reporting altitude until touchdown. +-- @param #PSEUDOATC self +-- @param #number interval Interval in seconds. Default is every 3 sec. +function PSEUDOATC:SetReportAltInterval(interval) + self.talt=interval or 3 +end + +----------------------------------------------------------------------------------------------------------------------------------------- +-- Event Handling + +--- Event handler for suppressed groups. +--@param #PSEUDOATC self +--@param #table Event Event data table. Holds event.id, event.initiator and event.target etc. +function PSEUDOATC:onEvent(Event) + if Event == nil or Event.initiator == nil or Unit.getByName(Event.initiator:getName()) == nil then + return true + end + + local DCSiniunit = Event.initiator + local DCSplace = Event.place + local DCSsubplace = Event.subplace + + local EventData={} + local _playerunit=nil + local _playername=nil + + if Event.initiator then + EventData.IniUnitName = Event.initiator:getName() + EventData.IniDCSGroup = Event.initiator:getGroup() + EventData.IniGroupName = Event.initiator:getGroup():getName() + -- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in. + _playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName) + end + + if Event.place then + EventData.Place=Event.place + EventData.PlaceName=Event.place:getName() + end + if Event.subplace then + EventData.SubPlace=Event.subplace + EventData.SubPlaceName=Event.subplace:getName() + end + + -- Event info. + self:T3(PSEUDOATC.id..string.format("EVENT: Event in onEvent with ID = %s", tostring(Event.id))) + self:T3(PSEUDOATC.id..string.format("EVENT: Ini unit = %s" , tostring(EventData.IniUnitName))) + self:T3(PSEUDOATC.id..string.format("EVENT: Ini group = %s" , tostring(EventData.IniGroupName))) + self:T3(PSEUDOATC.id..string.format("EVENT: Ini player = %s" , tostring(_playername))) + self:T3(PSEUDOATC.id..string.format("EVENT: Place = %s" , tostring(EventData.PlaceName))) + self:T3(PSEUDOATC.id..string.format("EVENT: SubPlace = %s" , tostring(EventData.SubPlaceName))) + + -- Event birth. + if Event.id == world.event.S_EVENT_BIRTH and _playername then + self:_OnBirth(EventData) + end + + -- Event takeoff. + if Event.id == world.event.S_EVENT_TAKEOFF and _playername and EventData.Place then + self:_PlayerTakeOff(EventData) + end + + -- Event land. + if Event.id == world.event.S_EVENT_LAND and _playername and EventData.Place then + self:_PlayerLanded(EventData) + end + + -- Event player left unit + if Event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and _playername then + self:_PlayerLeft(EventData) + end + + -- Event crash ==> player left unit + if Event.id == world.event.S_EVENT_CRASH and _playername then + self:_PlayerLeft(EventData) + end + +--[[ + -- Event eject ==> player left unit + if Event.id == world.event.S_EVENT_EJECTION and _playername then + self:_PlayerLeft(EventData) + end + + -- Event pilot dead ==> player left unit + if Event.id == world.event.S_EVENT_PILOT_DEAD and _playername then + self:_PlayerLeft(EventData) + end +]] +end + +--- Function called my MOOSE event handler when a player enters a unit. +-- @param #PSEUDOATC self +-- @param Core.Event#EVENTDATA EventData +function PSEUDOATC:_OnBirth(EventData) + self:F({EventData=EventData}) + + -- Get unit and player. + local _unitName=EventData.IniUnitName + local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + + -- Check if a player entered. + if _unit and _playername then + self:PlayerEntered(_unit) + end + +end + +--- Function called by MOOSE event handler when a player leaves a unit or dies. +-- @param #PSEUDOATC self +-- @param Core.Event#EVENTDATA EventData +function PSEUDOATC:_PlayerLeft(EventData) + self:F({EventData=EventData}) + + -- Get unit and player. + local _unitName=EventData.IniUnitName + local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + + -- Check if a player left. + if _unit and _playername then + self:PlayerLeft(_unit) + end +end + +--- Function called by MOOSE event handler when a player landed. +-- @param #PSEUDOATC self +-- @param Core.Event#EVENTDATA EventData +function PSEUDOATC:_PlayerLanded(EventData) + self:F({EventData=EventData}) + + -- Get unit, player and place. + local _unitName=EventData.IniUnitName + local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + local _base=nil + local _baseName=nil + if EventData.place then + _base=EventData.place + _baseName=EventData.place:getName() + end +-- if EventData.subplace then +-- local _subPlace=EventData.subplace +-- local _subPlaceName=EventData.subplace:getName() +-- end + + -- Call landed function. + if _unit and _playername and _base then + self:PlayerLanded(_unit, _baseName) + end +end + +--- Function called by MOOSE/DCS event handler when a player took off. +-- @param #PSEUDOATC self +-- @param Core.Event#EVENTDATA EventData +function PSEUDOATC:_PlayerTakeOff(EventData) + self:F({EventData=EventData}) + + -- Get unit, player and place. + local _unitName=EventData.IniUnitName + local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) + local _base=nil + local _baseName=nil + if EventData.place then + _base=EventData.place + _baseName=EventData.place:getName() + end + + -- Call take-off function. + if _unit and _playername and _base then + self:PlayerTakeOff(_unit, _baseName) + end +end + +----------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions + +--- Function called when a player enters a unit. +-- @param #PSEUDOATC self +-- @param Wrapper.Unit#UNIT unit Unit the player entered. +function PSEUDOATC:PlayerEntered(unit) + self:F2({unit=unit}) + + -- Get player info. + local group=unit:GetGroup() --Wrapper.Group#GROUP + local GID=group:GetID() + local GroupName=group:GetName() + local PlayerName=unit:GetPlayerName() + local UnitName=unit:GetName() + local CallSign=unit:GetCallsign() + + -- Init player table. + self.player[GID]={} + self.player[GID].group=group + self.player[GID].unit=unit + self.player[GID].groupname=GroupName + self.player[GID].unitname=UnitName + self.player[GID].playername=PlayerName + self.player[GID].callsign=CallSign + self.player[GID].waypoints=group:GetTaskRoute() + + -- Info message. + local text=string.format("Player %s entered unit %s of group %s (id=%d).", PlayerName, UnitName, GroupName, GID) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + + -- Create main F10 menu, i.e. "F10/Pseudo ATC" + self.player[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC") + + -- Create/update list of nearby airports. + self:LocalAirports(GID) + + -- Create submenu of local airports. + self:MenuAirports(GID) + + -- Create submenu Waypoints. + self:MenuWaypoints(GID) + + -- Start scheduler to refresh the F10 menues. + self.player[GID].scheduler, self.player[GID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID}, self.mrefresh, self.mrefresh) + +end + +--- Function called when a player has landed. +-- @param #PSEUDOATC self +-- @param Wrapper.Unit#UNIT unit Unit of player which has landed. +-- @param #string place Name of the place the player landed at. +function PSEUDOATC:PlayerLanded(unit, place) + self:F2({unit=unit, place=place}) + + -- Gather some information. + local group=unit:GetGroup() + local id=group:GetID() + local PlayerName=self.player[id].playername + local Callsign=self.player[id].callsign + local UnitName=self.player[id].unitname + local GroupName=self.player[id].groupname + local CallSign=self.player[id].callsign + + -- Debug message. + local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, id, place) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + + -- Stop altitude reporting timer if its activated. + self:AltitudeTimerStop(id) + + -- Welcome message. + if place and self.chatty then + local text=string.format("Touchdown! Welcome to %s. Have a nice day!", place) + MESSAGE:New(text, self.mdur):ToGroup(group) + end + +end + +--- Function called when a player took off. +-- @param #PSEUDOATC self +-- @param Wrapper.Unit#UNIT unit Unit of player which has landed. +-- @param #string place Name of the place the player landed at. +function PSEUDOATC:PlayerTakeOff(unit, place) + self:F2({unit=unit, place=place}) + + -- Gather some information. + local group=unit:GetGroup() + local id=group:GetID() + local PlayerName=self.player[id].playername + local Callsign=self.player[id].callsign + local UnitName=self.player[id].unitname + local GroupName=self.player[id].groupname + local CallSign=self.player[id].callsign + + -- Debug message. + local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, id, place) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + + -- Bye-Bye message. + if place and self.chatty then + local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign) + MESSAGE:New(text, self.mdur):ToGroup(group) + end + +end + +--- Function called when a player leaves a unit or dies. +-- @param #PSEUDOATC self +-- @param Wrapper.Unit#UNIT unit Player unit which was left. +function PSEUDOATC:PlayerLeft(unit) + self:F({unit=unit}) + + -- Get id. + local group=unit:GetGroup() + local id=group:GetID() + + if self.player[id] then + + -- Debug message. + local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", self.player[id].playername, self.player[id].callsign, self.player[id].groupname, self.player[id].unitname) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + + -- Stop scheduler for menu updates + if self.player[id].schedulerid then + self.player[id].scheduler:Stop(self.player[id].schedulerid) + end + + -- Stop scheduler for reporting alt if it runs. + self:AltitudeTimerStop(id) + + -- Remove main menu. + if self.player[id].menu_main then + missionCommands.removeItem(self.player[id].menu_main) + end + + -- Remove player array. + self.player[id]=nil + + end +end + +----------------------------------------------------------------------------------------------------------------------------------------- +-- Menu Functions + +--- Refreshes all player menues. +-- @param #PSEUDOATC self. +-- @param #number id Group id of player unit. +function PSEUDOATC:MenuRefresh(id) + self:F({id=id}) + + -- Debug message. + local text=string.format("Refreshing menues for player %s in group %s.", self.player[id].playername, self.player[id].groupname) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text,30):ToAllIf(self.Debug) + + -- Clear menu. + self:MenuClear(id) + + -- Create list of nearby airports. + self:LocalAirports(id) + + -- Create submenu Local Airports. + self:MenuAirports(id) + + -- Create submenu Waypoints etc. + self:MenuWaypoints(id) + +end + + +--- Clear player menus. +-- @param #PSEUDOATC self. +-- @param #number id Group id of player unit. +function PSEUDOATC:MenuClear(id) + self:F(id) + + -- Debug message. + local text=string.format("Clearing menus for player %s in group %s.", self.player[id].playername, self.player[id].groupname) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text,30):ToAllIf(self.Debug) + + -- Delete Airports menu. + if self.player[id].menu_airports then + missionCommands.removeItemForGroup(id, self.player[id].menu_airports) + self.player[id].menu_airports=nil + else + self:T2(PSEUDOATC.id.."No airports to clear menus.") + end + + -- Delete waypoints menu. + if self.player[id].menu_waypoints then + missionCommands.removeItemForGroup(id, self.player[id].menu_waypoints) + self.player[id].menu_waypoints=nil + end + + -- Delete report alt until touchdown menu command. + if self.player[id].menu_reportalt then + missionCommands.removeItemForGroup(id, self.player[id].menu_reportalt) + self.player[id].menu_reportalt=nil + end + + -- Delete request current alt menu command. + if self.player[id].menu_requestalt then + missionCommands.removeItemForGroup(id, self.player[id].menu_requestalt) + self.player[id].menu_requestalt=nil + end + +end + +--- Create "F10/Pseudo ATC/Local Airports/Airport Name/" menu items each containing weather report and BR request. +-- @param #PSEUDOATC self +-- @param #number id Group id of player unit for which menues are created. +function PSEUDOATC:MenuAirports(id) + self:F(id) + + -- Table for menu entries. + self.player[id].menu_airports=missionCommands.addSubMenuForGroup(id, "Local Airports", self.player[id].menu_main) + + local i=0 + for _,airport in pairs(self.player[id].airports) do + + i=i+1 + if i > 10 then + break -- Max 10 airports due to 10 menu items restriction. + end + + local name=airport.name + local d=airport.distance + local pos=AIRBASE:FindByName(name):GetCoordinate() + + --F10menu_ATC_airports[ID][name] = missionCommands.addSubMenuForGroup(ID, name, F10menu_ATC) + local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_airports) + + -- Create menu reporting commands + missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name) + missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name) + + -- Debug message. + self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, id)) + end +end + +--- Create "F10/Pseudo ATC/Waypoints/ menu items. +-- @param #PSEUDOATC self +-- @param #number id Group id of player unit for which menues are created. +function PSEUDOATC:MenuWaypoints(id) + self:F(id) + + -- Player unit and callsign. + local unit=self.player[id].unit --Wrapper.Unit#UNIT + local callsign=self.player[id].callsign + + -- Debug info. + self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, id)) + + if #self.player[id].waypoints>0 then + + -- F10/PseudoATC/Waypoints + self.player[id].menu_waypoints=missionCommands.addSubMenuForGroup(id, "Waypoints", self.player[id].menu_main) + + local j=0 + for i, wp in pairs(self.player[id].waypoints) do + + -- Increase counter + j=j+1 + + if j>10 then + break -- max ten menu entries + end + + -- Position of Waypoint + local pos=COORDINATE:New(wp.x, wp.alt, wp.y) + local name=string.format("Waypoint %d", i-1) + + -- "F10/PseudoATC/Waypoints/Waypoint X" + local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_waypoints) + + -- Menu commands for each waypoint "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X/" + missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name) + missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name) + end + end + + self.player[id].menu_reportalt = missionCommands.addCommandForGroup(id, "Talk me down", self.player[id].menu_main, self.AltidudeTimerToggle, self, id) + self.player[id].menu_requestalt = missionCommands.addCommandForGroup(id, "Request altitude", self.player[id].menu_main, self.ReportHeight, self, id) +end + +----------------------------------------------------------------------------------------------------------------------------------------- +-- Reporting Functions + +--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location. +-- @param #PSEUDOATC self +-- @param #number id Group id to which the report is delivered. +-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured. +-- @param #string location Name of the location at which the pressure is measured. +function PSEUDOATC:ReportWeather(id, position, location) + self:F({id=id, position=position, location=location}) + + -- Player unit system settings. + local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS + + local text=string.format("Local weather at %s:\n", location) + + -- Get pressure in hPa. + local Pqnh=position:GetPressure(0) -- Get pressure at sea level. + local Pqfe=position:GetPressure() -- Get pressure at (land) height of position. + + -- Pressure conversion + local hPa2inHg=0.0295299830714 + local hPa2mmHg=0.7500615613030 + + -- Unit conversion. + local _Pqnh=string.format("%.2f inHg", Pqnh * hPa2inHg) + local _Pqfe=string.format("%.2f inHg", Pqfe * hPa2inHg) + if settings:IsMetric() then + _Pqnh=string.format("%.1f mmHg", Pqnh * hPa2mmHg) + _Pqfe=string.format("%.1f mmHg", Pqfe * hPa2mmHg) + end + + -- Message text. + text=text..string.format("QFE %.1f hPa = %s.\n", Pqfe, _Pqfe) + text=text..string.format("QNH %.1f hPa = %s.\n", Pqnh, _Pqnh) + + -- Get temperature at position in degrees Celsius. + local T=position:GetTemperature() + + -- Correct unit system. + local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T)) + if settings:IsMetric() then + _T=string.format('%d°C', T) + end + + -- Message text. + local text=text..string.format("Temperature %s\n", _T) + + -- Get wind direction and speed. + local Dir,Vel=position:GetWind() + + -- Get Beaufort wind scale. + local Bn,Bd=UTILS.BeaufortScale(Vel) + + -- Formatted wind direction. + local Ds = string.format('%03d°', Dir) + + -- Velocity in player units. + local Vs=string.format("%.1f knots", UTILS.MpsToKnots(Vel)) + if settings:IsMetric() then + Vs=string.format('%.1f m/s', Vel) + end + + -- Message text. + local text=text..string.format("Wind from %s at %s (%s).", Ds, Vs, Bd) + + -- Send message + self:_DisplayMessageToGroup(self.player[id].unit, text, self.mdur, true) + +end + +--- Report absolute bearing and range form player unit to airport. +-- @param #PSEUDOATC self +-- @param #number id Group id to the report is delivered. +-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured. +-- @param #string location Name of the location at which the pressure is measured. +function PSEUDOATC:ReportBR(id, position, location) + self:F({id=id, position=position, location=location}) + + -- Current coordinates. + local unit=self.player[id].unit --Wrapper.Unit#UNIT + local coord=unit:GetCoordinate() + + -- Direction vector from current position (coord) to target (position). + local pos=coord:Translate(30,90) + local vec3=coord:GetDirectionVec3(pos) + local angle=coord:GetAngleDegrees(vec3) + local range=coord:Get2DDistance(position) + + -- Bearing string. + local Bs=string.format('%03d°', angle) + + -- Settings. + local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS + + + local Rs=string.format("%.1f NM", UTILS.MetersToNM(range)) + if settings:IsMetric() then + Rs=string.format("%.1f km", range/1000) + end + + -- Message text. + local text=string.format("%s: Bearing %s, Range %s.", location, Bs, Rs) + + -- Send message to player group. + MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group) +end + +--- Report altitude above ground level of player unit. +-- @param #PSEUDOATC self +-- @param #number id Group id to the report is delivered. +-- @param #number dt (Optional) Duration the message is displayed. +-- @param #boolean _clear (Optional) Clear previouse messages. +-- @return #number Altitude above ground. +function PSEUDOATC:ReportHeight(id, dt, _clear) + self:F({id=id, dt=dt}) + + local dt = dt or self.mdur + if _clear==nil then + _clear=false + end + + -- Return height [m] above ground level. + local function get_AGL(p) + local agl=0 + local vec2={x=p.x,y=p.z} + local ground=land.getHeight(vec2) + local agl=p.y-ground + return agl + end + + -- Get height AGL. + local unit=self.player[id].unit --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + + local position=unit:GetCoordinate() + local height=get_AGL(position) + local callsign=unit:GetCallsign() + + -- Settings. + local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS + + -- Height string. + local Hs=string.format("%d ft", UTILS.MetersToFeet(height)) + if settings:IsMetric() then + Hs=string.format("%d m", height) + end + + -- Message text. + local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs) + + -- Append flight level. + if _clear==false then + _text=_text..string.format(" FL%03d.", position.y/30.48) + end + + -- Send message to player group. + self:_DisplayMessageToGroup(self.player[id].unit,_text, dt,_clear) + + -- Return height + return height + end + + return 0 +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Toggle report altitude reporting on/off. +-- @param #PSEUDOATC self. +-- @param #number id Group id of player unit. +function PSEUDOATC:AltidudeTimerToggle(id) + self:F(id) + + if self.player[id].altimerid then + -- If the timer is on, we turn it off. + self:AltitudeTimerStop(id) + else + -- If the timer is off, we turn it on. + self:AltitudeTimeStart(id) + end +end + +--- Start altitude reporting scheduler. +-- @param #PSEUDOATC self. +-- @param #number id Group id of player unit. +function PSEUDOATC:AltitudeTimeStart(id) + self:F(id) + + -- Debug info. + self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", id)) + + -- Start timer. Altitude is reported every ~3 seconds. + self.player[id].altimer, self.player[id].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, id, 0.1, true}, 1, 3) +end + +--- Stop/destroy DCS scheduler function for reporting altitude. +-- @param #PSEUDOATC self. +-- @param #number id Group id of player unit. +function PSEUDOATC:AltitudeTimerStop(id) + + -- Debug info. + self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", id)) + + -- Stop timer. + if self.player[id].altimerid then + self.player[id].altimer:Stop(self.player[id].altimerid) + end + + self.player[id].altimer=nil + self.player[id].altimerid=nil +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc + +--- Create list of nearby airports sorted by distance to player unit. +-- @param #PSEUDOATC self +-- @param #number id Group id of player unit. +function PSEUDOATC:LocalAirports(id) + self:F(id) + + -- Airports table. + self.player[id].airports=nil + self.player[id].airports={} + + -- Current player position. + local pos=self.player[id].unit:GetCoordinate() + + -- Loop over coalitions. + for i=0,2 do + + -- Get all airbases of coalition. + local airports=coalition.getAirbases(i) + + -- Loop over airbases + for _,airbase in pairs(airports) do + + local name=airbase:getName() + local q=AIRBASE:FindByName(name):GetCoordinate() + local d=q:Get2DDistance(pos) + + -- Add to table. + table.insert(self.player[id].airports, {distance=d, name=name}) + + end + end + + --- compare distance (for sorting airports) + local function compare(a,b) + return a.distance < b.distance + end + + -- Sort airports table w.r.t. distance to player. + table.sort(self.player[id].airports, compare) + +end + +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +-- @param #PSEUDOATC self +-- @param #string _unitName Name of the player unit. +-- @return Wrapper.Unit#UNIT Unit of player. +-- @return #string Name of the player. +-- @return nil If player does not exist. +function PSEUDOATC:_GetPlayerUnitAndName(_unitName) + self:F(_unitName) + + if _unitName ~= nil then + + -- Get DCS unit from its name. + local DCSunit=Unit.getByName(_unitName) + if DCSunit then + + -- Get the player name to make sure a player entered. + local playername=DCSunit:getPlayerName() + local unit=UNIT:Find(DCSunit) + + -- Debug output. + self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) + + if unit and playername then + -- Return MOOSE unit and player name + return unit, playername + end + + end + end + + return nil,nil +end + + +--- Display message to group. +-- @param #PSEUDOATC self +-- @param Wrapper.Unit#UNIT _unit Player unit. +-- @param #string _text Message text. +-- @param #number _time Duration how long the message is displayed. +-- @param #boolean _clear Clear up old messages. +function PSEUDOATC:_DisplayMessageToGroup(_unit, _text, _time, _clear) + self:F({unit=_unit, text=_text, time=_time, clear=_clear}) + + _time=_time or self.Tmsg + if _clear==nil then + _clear=false + end + + -- Group ID. + local _gid=_unit:GetGroup():GetID() + + if _gid then + if _clear == true then + trigger.action.outTextForGroup(_gid, _text, _time, _clear) + else + trigger.action.outTextForGroup(_gid, _text, _time) + end + end + +end + +--- Returns a string which consits of this callsign and the player name. +-- @param #PSEUDOATC self +-- @param #string unitname Name of the player unit. +function PSEUDOATC:_myname(unitname) + self:F2(unitname) + + local unit=UNIT:FindByName(unitname) + local pname=unit:GetPlayerName() + local csign=unit:GetCallsign() + + return string.format("%s (%s)", csign, pname) +end + diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index c6c422798..53b4d67f6 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1,12 +1,7 @@ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- **Functional** - (R2.2) - Create random airtraffic in your missions. -- -- === -- --- ![Banner Image](..\Presentations\RAT\RAT.png) --- --- === --- -- The aim of the RAT class is to fill the empty DCS world with randomized air traffic and bring more life to your airports. -- -- In particular, it is designed to spawn AI air units at random airports. These units will be assigned a random flight path to another random airport on the map. @@ -42,23 +37,25 @@ -- -- # Demo Missions -- --- ### [RAT Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/Release/RAT%20-%20Random%20Air%20Traffic) --- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) +-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS) +-- ### [MOOSE - RAT Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAT%20-%20Random%20Air%20Traffic) -- -- === -- -- # YouTube Channel -- --- ### [DCS WORLD - MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO) +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- ### [MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO) -- -- === -- -- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** -- --- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))** +-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536) -- -- === --- @module Rat +-- @module Functional.Rat +-- @image RAT.JPG ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- RAT class @@ -116,7 +113,7 @@ -- @field #boolean continuejourney Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. -- @field #number ngroups Number of groups to be spawned in total. -- @field #number alive Number of groups which are alive. --- @field #boolean f10menu Add an F10 menu for RAT. +-- @field #boolean f10menu If true, add an F10 radiomenu for RAT. Default is false. -- @field #table Menu F10 menu items for this RAT object. -- @field #string SubMenuName Submenu name for RAT object. -- @field #boolean respawn_at_landing Respawn aircraft the moment they land rather than at engine shutdown. @@ -151,9 +148,7 @@ -- @field #boolean useparkingdb Parking spots are added to data base once an aircraft has used it. These spots can later be used by other aircraft. Default is true. -- @extends Core.Spawn#SPAWN ----# RAT class, extends @{Spawn#SPAWN} --- The RAT class implements an easy to use way to randomly fill your map with AI aircraft. --- +--- Implements an easy to use way to randomly fill your map with AI aircraft. -- -- ## Airport Selection -- @@ -350,7 +345,7 @@ RAT={ continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination. alive=0, -- Number of groups which are alive. ngroups=nil, -- Number of groups to be spawned in total. - f10menu=true, -- Add an F10 menu for RAT. + f10menu=false, -- Add an F10 menu for RAT. Menu={}, -- F10 menu items for this RAT object. SubMenuName=nil, -- Submenu name for RAT object. respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown. @@ -506,7 +501,7 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.2.1", + version = "2.2.2", print = true, } @@ -1280,7 +1275,7 @@ end --- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediatly. -- @param #RAT self --- @param #booblen switch If true, check is performed. If false, this check is omitted. +-- @param #boolean switch If true, check is performed. If false, this check is omitted. function RAT:CheckOnRunway(switch) self:F2(switch) if switch==nil then @@ -1291,7 +1286,7 @@ end --- Check if aircraft have accidentally been spawned on top of each other. If yes, they will be removed immediately. -- @param #RAT self --- @param #booblen switch If true, check is performed. If false, this check is omitted. +-- @param #boolean switch If true, check is performed. If false, this check is omitted. function RAT:CheckOnTop(switch) self:F2(switch) if switch==nil then @@ -1302,7 +1297,7 @@ end --- Put parking spot coordinates in a data base for future use of aircraft. -- @param #RAT self --- @param #booblen switch If true, parking spots are memorized. This is also the default setting. +-- @param #boolean switch If true, parking spots are memorized. This is also the default setting. function RAT:ParkingSpotDB(switch) self:F2(switch) if switch==nil then @@ -1363,6 +1358,21 @@ function RAT:Immortal() self.immortal=true end +--- Radio menu On. Default is off. +-- @param #RAT self +function RAT:RadioMenuON() + self:F2() + self.f10menu=true +end + +--- Radio menu Off. This is the default setting. +-- @param #RAT self +function RAT:RadioMenuOFF() + self:F2() + self.f10menu=false +end + + --- Activate uncontrolled aircraft. -- @param #RAT self -- @param #number maxactivated Maximal numnber of activated aircraft. Absolute maximum will be the number of spawned groups. Default is 1. @@ -1652,7 +1662,7 @@ end --- Initialize basic parameters of the aircraft based on its (template) group in the mission editor. -- @param #RAT self --- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor. +-- @param DCS#Group DCSgroup Group of the aircraft in the mission editor. function RAT:_InitAircraft(DCSgroup) self:F2(DCSgroup) @@ -3751,11 +3761,11 @@ end function RAT:_Destroy(group) self:F2(group) - local DCSGroup = group:GetDCSObject() -- Dcs.DCSGroup#Group + local DCSGroup = group:GetDCSObject() -- DCS#Group if DCSGroup and DCSGroup:isExist() then - --local DCSUnit = DCSGroup:getUnit(1) -- Dcs.DCSUnit#Unit + --local DCSUnit = DCSGroup:getUnit(1) -- DCS#Unit --if DCSUnit then -- self:_CreateEventDead(timer.getTime(), DCSUnit) --end @@ -3786,8 +3796,8 @@ end --- Create a Dead event. -- @param #RAT self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. function RAT:_CreateEventDead(EventTime, Initiator) self:F( { EventTime, Initiator } ) @@ -4019,11 +4029,11 @@ end --- Orbit at a specified position at a specified alititude with a specified speed. -- @param #RAT self --- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position. +-- @param DCS#Vec2 P1 The point to hold the position. -- @param #number Altitude The altitude ASL at which to hold the position. -- @param #number Speed The speed flying when holding the position in m/s. -- @param #number Duration Duration of holding pattern in seconds. --- @return Dcs.DCSTasking.Task#Task DCSTask +-- @return DCS#Task DCSTask function RAT:_TaskHolding(P1, Altitude, Speed, Duration) --local LandHeight = land.getHeight(P1) @@ -4265,10 +4275,10 @@ end function RAT:_CommandImmortal(group, switch) -- Command structure for setting groups to invisible. - local SetInvisible = {id = 'SetImmortal', params = {value = switch}} + local SetImmortal = {id = 'SetImmortal', params = {value = switch}} -- Execute command. - group:SetCommand(SetInvisible) + group:SetCommand(SetImmortal) end --- Adds a parking spot at an airport when it has been used by a spawned RAT aircraft to the RAT parking data base. @@ -5222,7 +5232,7 @@ end -- @field #number managerid Managing scheduler id. -- @extends Core.Base#BASE ----# RATMANAGER class, extends @{Base#BASE} +---# RATMANAGER class, extends @{Core.Base#BASE} -- The RATMANAGER class manages spawning of multiple RAT objects in a very simple way. It is created by the @{#RATMANAGER.New}() contructor. -- RAT objects with different "tasks" can be defined as usual. However, they **must not** be spawned via the @{#RAT.Spawn}() function. -- diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9748e89d6..1e39d0735 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1,12 +1,7 @@ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- **Functional** - (R2.3) - Range Practice. -- -- === -- --- ![Banner Image](..\Presentations\RANGE\RANGE_Main.png) --- --- === --- -- The RANGE class enables easy set up of bombing and strafing ranges within DCS World. -- -- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by [Ciribob](https://forums.eagle.ru/member.php?u=112175), which itself was motivated @@ -32,12 +27,13 @@ -- -- # Demo Missions -- --- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) +-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS) -- -- === -- -- # YouTube Channel --- +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- ### [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) -- -- === @@ -47,7 +43,8 @@ -- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536), [Ciribob](https://forums.eagle.ru/member.php?u=112175) -- -- === --- @module Range +-- @module Functional.Range +-- @image Range.JPG ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- RANGE class @@ -69,6 +66,7 @@ -- @field #table bombPlayerResults Table containing the bombing results of each player. -- @field #table PlayerSettings Indiviual player settings. -- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds. +-- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threashold [m]. Default 25000 m. -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. -- @field #number strafemaxalt Maximum altitude above ground for registering for a strafe run. Default is 914 m = 3000 ft. -- @field #number ndisplayresult Number of (player) results that a displayed. Default is 10. @@ -85,8 +83,7 @@ -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @extends Core.Base#BASE ----# RANGE class, extends @{Base#BASE} --- The RANGE class enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor. +--- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor. -- The parameter "rangename" defindes the name of the range. It has to be unique since this is also the name displayed in the radio menu. -- -- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated. @@ -107,7 +104,7 @@ -- -- A strafe pit can be added to the range by the @{#RANGE.AddStrafepit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. -- --- * The first parameter *targetnames* defines the target or targets. This has to be given as a lua table which contains the names of @{Unit} or @{Static} objects defined in the mission editor. +-- * The first parameter *targetnames* defines the target or targets. This has to be given as a lua table which contains the names of @{Wrapper.Unit} or @{Static} objects defined in the mission editor. -- * In order to perform a valid pass on the strafe pit, the pilot has to begin his run from the correct direction. Therefore, an "approach box" is defined in front -- of the strafe targets. The parameters *boxlength* and *boxwidth* define the size of the box while the parameter *heading* defines its direction. -- If the parameter *heading* is passed as **nil**, the heading is automatically taken from the heading of the first target unit as defined in the ME. @@ -117,7 +114,7 @@ -- * The last parameter *foulline* sets the distance from the pit targets to the foul line. Hit from closer than this line are not counted! -- -- Another function to add a strafe pit is @{#RANGE.AddStrafePitGroup}(*group, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*). Here, --- the first parameter *group* is a MOOSE @{Group} object and **all** units in this group define **one** strafe pit. +-- the first parameter *group* is a MOOSE @{Wrapper.Group} object and **all** units in this group define **one** strafe pit. -- -- Finally, a valid approach has to be performed below a certain maximum altitude. The default is 914 meters (3000 ft) AGL. This is a parameter valid for all -- strafing pits of the range and can be adjusted by the @{#RANGE.SetMaxStrafeAlt}(maxalt) function. @@ -125,13 +122,13 @@ -- ## Bombing targets -- One ore multiple bombing targets can be added to the range by the @{#RANGE.AddBombingTargets}(targetnames, goodhitrange, randommove) function. -- --- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Unit} and/or @{Static} objects defined in the mission editor. --- Note that the @{Range} logic **automatically** determines, if a name belongs to a @{Unit} or @{Static} object now. +-- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Wrapper.Unit} and/or @{Static} objects defined in the mission editor. +-- Note that the @{Range} logic **automatically** determines, if a name belongs to a @{Wrapper.Unit} or @{Static} object now. -- * The (optional) parameter *goodhitrange* specifies the radius around the target. If a bomb or rocket falls at a distance smaller than this number, the hit is considered to be "good". -- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- --- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Group} object +-- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Wrapper.Group} object -- and **all** units in this group are defined as bombing targets. -- -- ## Fine Tuning @@ -211,6 +208,9 @@ -- -- The function @{#RANGE.DebugON}() can be used to send messages on screen. It also smokes all defined strafe and bombing targets, the strafe pit approach boxes and the range zone. -- +-- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one. +-- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects. +-- -- -- -- @field #RANGE @@ -232,6 +232,7 @@ RANGE={ bombPlayerResults = {}, PlayerSettings = {}, dtBombtrack=0.005, + BombtrackThreshold=25000, Tmsg=30, strafemaxalt=914, ndisplayresult=10, @@ -279,7 +280,7 @@ RANGE.id="RANGE | " --- Range script version. -- @field #number version -RANGE.version="1.1.0" +RANGE.version="1.2.0" --TODO list: --TODO: Add custom weapons, which can be specified by the user. @@ -310,7 +311,7 @@ function RANGE:New(rangename) self.rangename=rangename or "Practice Range" -- Debug info. - local text=string.format("RANGE script version %s. Creating new RANGE object. Range name: %s.", RANGE.version, self.rangename) + local text=string.format("RANGE script version %s - creating new RANGE object of name: %s.", RANGE.version, self.rangename) self:E(RANGE.id..text) MESSAGE:New(text, 10):ToAllIf(self.Debug) @@ -447,6 +448,13 @@ function RANGE:SetRangeRadius(radius) self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius end +--- Set bomb track threshold distance. Bombs/rockets/missiles are only tracked if player-range distance is less than this distance. Default 25 km. +-- @param #RANGE self +-- @param #number distance Threshold distance in km. Default 25 km. +function RANGE:SetBombtrackThreshold(distance) + self.BombtrackThreshold=distance*1000 or 25*1000 +end + --- Set range location. If this is not done, one (random) unit position of the range is used to determine the center of the range. -- @param #RANGE self -- @param Core.Point#COORDINATE coordinate Coordinate of the center of the range. @@ -1077,6 +1085,7 @@ function RANGE:OnEventShot(EventData) local _weaponName = _weaponStrArray[#_weaponStrArray] -- Debug info. + self:T(RANGE.id.."EVENT SHOT: Range "..self.rangename) self:T(RANGE.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName) self:T(RANGE.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName) self:T(RANGE.id.."EVENT SHOT: Weapon type = ".._weapon) @@ -1093,129 +1102,141 @@ function RANGE:OnEventShot(EventData) -- Check if any condition applies here. local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles) - if _track then + -- Get unit name. + local _unitName = EventData.IniUnitName + + -- Get player unit and name. + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - -- Weapon - local _ordnance = EventData.weapon + -- Set this to larger value than the threshold. + local dPR=self.BombtrackThreshold*2 + + -- Distance player to range. + if _unit and _playername then + dPR=_unit:GetCoordinate():Get2DDistance(self.location) + self:T(RANGE.id..string.format("Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR/1000)) + end + + -- Only track if distance player to range is < 25 km. + if _track and dPR<=self.BombtrackThreshold then -- Tracking info and init of last bomb position. - self:T(RANGE.id..string.format("Tracking %s - %s.", _weapon, _ordnance:getName())) + self:T(RANGE.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName())) -- Init bomb position. local _lastBombPos = {x=0,y=0,z=0} - - -- Get unit name. - local _unitName = EventData.IniUnitName -- Function monitoring the position of a bomb until impact. - local function trackBomb(_previousPos) + local function trackBomb(_ordnance) + + -- When the pcall returns a failure the weapon has hit. + local _status,_bombPos = pcall( + function() + return _ordnance:getPoint() + end) + + self:T3(RANGE.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status))) + if _status then - -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) - local _callsign=self:_myname(_unitName) + -- Still in the air. Remember this position. + _lastBombPos = {x = _bombPos.x, y = _bombPos.y, z= _bombPos.z } - if _unit and _playername then - - -- When the pcall returns a failure the weapon has hit. - local _status,_bombPos = pcall( - function() - return _ordnance:getPoint() - end) - - if _status then + -- Check again in 0.005 seconds. + return timer.getTime() + self.dtBombtrack - -- Still in the air. Remember this position. - _lastBombPos = {x = _bombPos.x, y = _bombPos.y, z= _bombPos.z } - - -- Check again in 0.005 seconds. - return timer.getTime() + self.dtBombtrack - - else + else + + -- Bomb did hit the ground. + -- Get closet target to last position. + local _closetTarget = nil + local _distance = nil + local _hitquality = "POOR" - -- Bomb did hit the ground. - -- Get closet target to last position. - local _closetTarget = nil - local _distance = nil - local _hitquality = "POOR" - - -- Coordinate of impact point. - local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) - - -- Distance from range. We dont want to smoke targets outside of the range. - local impactdist=impactcoord:Get2DDistance(self.location) - - -- Smoke impact point of bomb. - if self.PlayerSettings[_playername].smokebombimpact and impactdist approach heading. + if heading>180 then + heading=heading-180 + else + heading=heading+180 + end + + local mycoord=coord:ToStringA2G(_unit, _settings) + _text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading) + end + + self:_DisplayMessageToGroup(_unit,_text, nil, true) + end +end + + --- Report weather conditions at range. Temperature, QFE pressure and wind data. -- @param #RANGE self -- @param #string _unitname Name of the player unit. @@ -1792,11 +1887,11 @@ function RANGE:_AddF10Commands(_unitName) local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath) local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath) local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) + local _infoPath = missionCommands.addSubMenuForGroup(_gid, "Range Info", _rangePath) -- F10/On the Range//My Settings/ local _mysmokePath = missionCommands.addSubMenuForGroup(_gid, "Smoke Color", _settingsPath) local _myflarePath = missionCommands.addSubMenuForGroup(_gid, "Flare Color", _settingsPath) - --TODO: Convert to MOOSE menu. -- F10/On the Range//Mark Targets/ missionCommands.addCommandForGroup(_gid, "Mark On Map", _markPath, self._MarkTargetsOnMap, self, _unitName) missionCommands.addCommandForGroup(_gid, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName) @@ -1824,9 +1919,11 @@ function RANGE:_AddF10Commands(_unitName) missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) - -- F10/On the Range// - missionCommands.addCommandForGroup(_gid, "Range Information", _rangePath, self._DisplayRangeInfo, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Weather Report", _rangePath, self._DisplayRangeWeather, self, _unitName) + -- F10/On the Range//Range Information + missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) + missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) + missionCommands.addCommandForGroup(_gid, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) + missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName) end else self:T(RANGE.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index cab7839ae..cea1f948b 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -2,15 +2,11 @@ -- -- === -- --- ![Banner Image](..\Presentations\SCORING\Dia1.JPG) --- --- === --- --- The @{#SCORING} class administers the scoring of player achievements, +-- Administers the scoring of player achievements, -- and creates a CSV file logging the scoring events and results for use at team or squadron websites. -- -- SCORING automatically calculates the threat level of the objects hit and destroyed by players, --- which can be @{Unit}, @{Static) and @{Scenery} objects. +-- which can be @{Wrapper.Unit}, @{Static) and @{Scenery} objects. -- -- Positive score points are granted when enemy or neutral targets are destroyed. -- Negative score points or penalties are given when a friendly target is hit or destroyed. @@ -56,7 +52,7 @@ -- Use the radio menu F10 to consult the scores while running the mission. -- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission. -- --- # 1) @{Scoring#SCORING} class, extends @{Base#BASE} +-- # 1) @{Functional.Scoring#SCORING} class, extends @{Core.Base#BASE} -- -- ## 1.1) Set the destroy score or penalty scale -- @@ -74,9 +70,9 @@ -- ## 1.2) Define special targets that will give extra scores. -- -- Special targets can be set that will give extra scores to the players when these are destroyed. --- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Unit}s. +-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. -- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Group}s. +-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. -- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) @@ -93,7 +89,7 @@ -- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points. -- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. -- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. --- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Zone#ZONE_UNIT}, +-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT}, -- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points. -- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, -- just large enough around that building. @@ -206,7 +202,8 @@ -- -- * **FlightControl**: Concept, Design & Programming. -- --- @module Scoring +-- @module Functional.Scoring +-- @image Scoring.JPG --- The Scoring class @@ -339,11 +336,11 @@ function SCORING:SetScaleDestroyPenalty( Scale ) return self end ---- Add a @{Unit} for additional scoring when the @{Unit} is destroyed. --- Note that if there was already a @{Unit} declared within the scoring with the same name, --- then the old @{Unit} will be replaced with the new @{Unit}. +--- Add a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed. +-- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name, +-- then the old @{Wrapper.Unit} will be replaced with the new @{Wrapper.Unit}. -- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. +-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given. -- @param #number Score The Score value. -- @return #SCORING function SCORING:AddUnitScore( ScoreUnit, Score ) @@ -355,9 +352,9 @@ function SCORING:AddUnitScore( ScoreUnit, Score ) return self end ---- Removes a @{Unit} for additional scoring when the @{Unit} is destroyed. +--- Removes a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed. -- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. +-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given. -- @return #SCORING function SCORING:RemoveUnitScore( ScoreUnit ) @@ -398,9 +395,9 @@ function SCORING:RemoveStaticScore( ScoreStatic ) end ---- Specify a special additional score for a @{Group}. +--- Specify a special additional score for a @{Wrapper.Group}. -- @param #SCORING self --- @param Wrapper.Group#GROUP ScoreGroup The @{Group} for which each @{Unit} a Score is given. +-- @param Wrapper.Group#GROUP ScoreGroup The @{Wrapper.Group} for which each @{Wrapper.Unit} a Score is given. -- @param #number Score The Score value. -- @return #SCORING function SCORING:AddScoreGroup( ScoreGroup, Score ) @@ -714,7 +711,7 @@ end -- A free text can be given that is shown to the players. -- The Score can be both positive and negative. -- @param #SCORING self --- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. +-- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. -- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel). -- @param #string Text A free text that is shown to the players. -- @param #number Score The score can be both positive or negative ( Penalty ). @@ -858,7 +855,7 @@ function SCORING:OnEventBirth( Event ) if Event.IniUnit then if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() - if PlayerName ~= "" then + if PlayerName then self:_AddPlayerFromUnit( Event.IniUnit ) self:SetScoringMenu( Event.IniGroup ) end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index f1a18b8df..db25b81e1 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -2,7 +2,8 @@ -- -- === -- --- @module Sead +-- @module Functional.Sead +-- @image SEAD.JPG --- The SEAD class -- @type SEAD diff --git a/Moose Development/Moose/Functional/Suppression.lua b/Moose Development/Moose/Functional/Suppression.lua new file mode 100644 index 000000000..805008e45 --- /dev/null +++ b/Moose Development/Moose/Functional/Suppression.lua @@ -0,0 +1,1842 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- **Functional** - (R2.4) Suppress fire of ground units when they get hit. +-- +-- ==== +-- +-- When ground units get hit by (suppressive) enemy fire, they will not be able to shoot back for a certain amount of time. +-- +-- The implementation is based on an idea and script by MBot. See the [DCS forum threat](https://forums.eagle.ru/showthread.php?t=107635) for details. +-- +-- In addition to suppressing the fire, conditions can be specified which let the group retreat to a defined zone, move away from the attacker +-- or hide at a nearby scenery object. +-- +-- ==== +-- +-- # Demo Missions +-- +-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS) +-- +-- ==== +-- +-- # YouTube Channel +-- +-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- +-- === +-- +-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** +-- +-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536) +-- +-- ==== +-- @module Functional.Suppression +-- @image Suppression.JPG + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- SUPPRESSION class +-- @type SUPPRESSION +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players. +-- @field #boolean flare Flare units when they get hit or die. +-- @field #boolean smoke Smoke places to which the group retreats, falls back or hides. +-- @field #list DCSdesc Table containing all DCS descriptors of the group. +-- @field #string Type Type of the group. +-- @field #number SpeedMax Maximum speed of group in km/h. +-- @field #boolean IsInfantry True if group has attribute Infantry. +-- @field Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the FSM. Must be a ground group. +-- @field #number Tsuppress_ave Average time in seconds a group gets suppressed. Actual value is sampled randomly from a Gaussian distribution. +-- @field #number Tsuppress_min Minimum time in seconds the group gets suppressed. +-- @field #number Tsuppress_max Maximum time in seconds the group gets suppressed. +-- @field #number TsuppressionOver Time at which the suppression will be over. +-- @field #number IniGroupStrength Number of units in a group at start. +-- @field #number Nhit Number of times the group was hit. +-- @field #string Formation Formation which will be used when falling back, taking cover or retreating. Default "Vee". +-- @field #number Speed Speed the unit will use when falling back, taking cover or retreating. Default 999. +-- @field #boolean MenuON If true creates a entry in the F10 menu. +-- @field #boolean FallbackON If true, group can fall back, i.e. move away from the attacking unit. +-- @field #number FallbackWait Time in seconds the unit will wait at the fall back point before it resumes its mission. +-- @field #number FallbackDist Distance in meters the unit will fall back. +-- @field #number FallbackHeading Heading in degrees to which the group should fall back. Default is directly away from the attacking unit. +-- @field #boolean TakecoverON If true, group can hide at a nearby scenery object. +-- @field #number TakecoverWait Time in seconds the group will hide before it will resume its mission. +-- @field #number TakecoverRange Range in which the group will search for scenery objects to hide at. +-- @field Core.Point#COORDINATE hideout Coordinate/place where the group will try to take cover. +-- @field #number PminFlee Minimum probability in percent that a group will flee (fall back or take cover) at each hit event. Default is 10 %. +-- @field #number PmaxFlee Maximum probability in percent that a group will flee (fall back or take cover) at each hit event. Default is 90 %. +-- @field Core.Zone#ZONE RetreatZone Zone to which a group retreats. +-- @field #number RetreatDamage Damage in percent at which the group will be ordered to retreat. +-- @field #number RetreatWait Time in seconds the group will wait in the retreat zone before it resumes its mission. Default two hours. +-- @field #string CurrentAlarmState Alam state the group is currently in. +-- @field #string CurrentROE ROE the group currently has. +-- @field #string DefaultAlarmState Alarm state the group will go to when it is changed back from another state. Default is "Auto". +-- @field #string DefaultROE ROE the group will get once suppression is over. Default is "Free". +-- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true. +-- @extends Core.Fsm#FSM_CONTROLLABLE +-- + +--- Mimic suppressive enemy fire and let groups flee or retreat. +-- +-- ## Suppression Process +-- +-- ![Process](..\Presentations\SUPPRESSION\Suppression_Process.png) +-- +-- The suppression process can be described as follows. +-- +-- ### CombatReady +-- +-- A group starts in the state **CombatReady**. In this state the group is ready to fight. The ROE is set to either "Weapon Free" or "Return Fire". +-- The alarm state is set to either "Auto" or "Red". +-- +-- ### Event Hit +-- The most important event in this scenario is the **Hit** event. This is an event of the FSM and triggered by the DCS event hit. +-- +-- ### Suppressed +-- After the **Hit** event the group changes its state to **Suppressed**. Technically, the ROE of the group is changed to "Weapon Hold". +-- The suppression of the group will last a certain amount of time. It is randomized an will vary each time the group is hit. +-- The expected suppression time is set to 15 seconds by default. But the actual value is sampled from a Gaussian distribution. +-- +-- ![Process](..\Presentations\SUPPRESSION\Suppression_Gaussian.png) +-- +-- The graph shows the distribution of suppression times if a group would be hit 100,000 times. As can be seen, on most hits the group gets +-- suppressed for around 15 seconds. Other values are also possible but they become less likely the further away from the "expected" suppression time they are. +-- Minimal and maximal suppression times can also be specified. By default these are set to 5 and 25 seconds, respectively. This can also be seen in the graph +-- because the tails of the Gaussian distribution are cut off at these values. +-- +-- ### Event Recovered +-- After the suppression time is over, the event **Recovered** is initiated and the group becomes **CombatReady** again. +-- The ROE of the group will be set to "Weapon Free". +-- +-- Of course, it can also happen that a group is hit again while it is still suppressed. In that case a new random suppression time is calculated. +-- If the new suppression time is longer than the remaining suppression of the previous hit, then the group recovers when the suppression time of the last +-- hit has passed. +-- If the new suppression time is shorter than the remaining suppression, the group will recover after the longer time of the first suppression has passed. +-- +-- For example: +-- +-- * A group gets hit the first time and is suppressed for - let's say - 15 seconds. +-- * After 10 seconds, i.e. when 5 seconds of the old suppression are left, the group gets hit a again. +-- * A new suppression time is calculated which can be smaller or larger than the remaining 5 seconds. +-- * If the new suppression time is smaller, e.g. three seconds, than five seconds, the group will recover after the 5 remaining seconds of the first suppression have passed. +-- * If the new suppression time is longer than last suppression time, e.g. 10 seconds, then the group will recover after the 10 seconds of the new hit have passed. +-- +-- Generally speaking, the suppression times are not just added on top of each other. Because this could easily lead to the situation that a group +-- never becomes CombatReady again before it gets destroyed. +-- +-- The mission designer can capture the event **Recovered** by the function @{#SUPPRESSION.OnAfterRecovered}(). +-- +-- ## Flee Events and States +-- Apart from being suppressed the groups can also flee from the enemy under certain conditions. +-- +-- ### Event Retreat +-- The first option is a retreat. This can be enabled by setting a retreat zone, i.e. a trigger zone defined in the mission editor. +-- +-- If the group takes a certain amount of damage, the event **Retreat** will be called and the group will start to move to the retreat zone. +-- The group will be in the state **Retreating**, which means that its ROE is set to "Weapon Hold" and the alarm state is set to "Green". +-- Setting the alarm state to green is necessary to enable the group to move under fire. +-- +-- When the group has reached the retreat zone, the event **Retreated** is triggered and the state will change to **Retreated** (note that both the event and +-- the state of the same name in this case). ROE and alarm state are +-- set to "Return Fire" and "Auto", respectively. The group will stay in the retreat zone and not actively participate in the combat any more. +-- +-- If no option retreat zone has been specified, the option retreat is not available. +-- +-- The mission designer can capture the events **Retreat** and **Retreated** by the functions @{#SUPPRESSION.OnAfterRetreat}() and @{#SUPPRESSION.OnAfterRetreated}(). +-- +-- ### Fallback +-- +-- If a group is attacked by another ground group, it has the option to fall back, i.e. move away from the enemy. The probability of the event **FallBack** to +-- happen depends on the damage of the group that was hit. The more a group gets damaged, the more likely **FallBack** event becomes. +-- +-- If the group enters the state **FallingBack** it will move 100 meters in the opposite direction of the attacking unit. ROE and alarmstate are set to "Weapon Hold" +-- and "Green", respectively. +-- +-- At the fallback point the group will wait for 60 seconds before it resumes its normal mission. +-- +-- The mission designer can capture the event **FallBack** by the function @{#SUPPRESSION.OnAfterFallBack}(). +-- +-- ### TakeCover +-- +-- If a group is hit by either another ground or air unit, it has the option to "take cover" or "hide". This means that the group will move to a random +-- scenery object in it vicinity. +-- +-- Analogously to the fall back case, the probability of a **TakeCover** event to occur, depends on the damage of the group. The more a group is damaged, the more +-- likely it becomes that a group takes cover. +-- +-- When a **TakeCover** event occurs an area with a radius of 300 meters around the hit group is searched for an arbitrary scenery object. +-- If at least one scenery object is found, the group will move there. One it has reached its "hideout", it will wait there for two minutes before it resumes its +-- normal mission. +-- +-- If more than one scenery object is found, the group will move to a random one. +-- If no scenery object is near the group the **TakeCover** event is rejected and the group will not move. +-- +-- The mission designer can capture the event **TakeCover** by the function @{#SUPPRESSION.OnAfterTakeCover}(). +-- +-- ### Choice of FallBack or TakeCover if both are enabled? +-- +-- If both **FallBack** and **TakeCover** events are enabled by the functions @{#SUPPRESSION.Fallback}() and @{#SUPPRESSION.Takecover}() the algorithm does the following: +-- +-- * If the attacking unit is a ground unit, then the **FallBack** event is executed. +-- * Otherwise, i.e. if the attacker is *not* a ground unit, then the **TakeCover** event is triggered. +-- +-- ### FightBack +-- +-- When a group leaves the states **TakingCover** or **FallingBack** the event **FightBack** is triggered. This changes the ROE and the alarm state back to their default values. +-- +-- The mission designer can capture the event **FightBack** by the function @{#SUPPRESSION.OnAfterFightBack}() +-- +-- # Examples +-- +-- ## Simple Suppression +-- This example shows the basic steps to use suppressive fire for a group. +-- +-- ![Process](..\Presentations\SUPPRESSION\Suppression_Example_01.png) +-- +-- +-- # Customization and Fine Tuning +-- The following user functions can be used to change the default values +-- +-- * @{#SUPPRESSION.SetSuppressionTime}() can be used to set the time a goup gets suppressed. +-- * @{#SUPPRESSION.SetRetreatZone}() sets the retreat zone and enables the possiblity for the group to retreat. +-- * @{#SUPPRESSION.SetFallbackDistance}() sets a value how far the unit moves away from the attacker after the fallback event. +-- * @{#SUPPRESSION.SetFallbackWait}() sets the time after which the group resumes its mission after a FallBack event. +-- * @{#SUPPRESSION.SetTakecoverWait}() sets the time after which the group resumes its mission after a TakeCover event. +-- * @{#SUPPRESSION.SetTakecoverRange}() sets the radius in which hideouts are searched. +-- * @{#SUPPRESSION.SetTakecoverPlace}() explicitly sets the place where the group will run at a TakeCover event. +-- * @{#SUPPRESSION.SetMinimumFleeProbability}() sets the minimum probability that a group flees (FallBack or TakeCover) after a hit. Note taht the probability increases with damage. +-- * @{#SUPPRESSION.SetMaximumFleeProbability}() sets the maximum probability that a group flees (FallBack or TakeCover) after a hit. Default is 90%. +-- * @{#SUPPRESSION.SetRetreatDamage}() sets the damage a group/unit can take before it is ordered to retreat. +-- * @{#SUPPRESSION.SetRetreatWait}() sets the time a group waits in the retreat zone after a retreat. +-- * @{#SUPPRESSION.SetDefaultAlarmState}() sets the alarm state a group gets after it becomes CombatReady again. +-- * @{#SUPPRESSION.SetDefaultROE}() set the rules of engagement a group gets after it becomes CombatReady again. +-- * @{#SUPPRESSION.FlareOn}() is mainly for debugging. A flare is fired when a unit is hit, gets suppressed, recovers, dies. +-- * @{#SUPPRESSION.SmokeOn}() is mainly for debugging. Puts smoke on retreat zone, hideouts etc. +-- * @{#SUPPRESSION.MenuON}() is mainly for debugging. Activates a radio menu item where certain functions like retreat etc. can be triggered manually. +-- +-- +-- @field #SUPPRESSION +SUPPRESSION={ + ClassName = "SUPPRESSION", + Debug = false, + flare = false, + smoke = false, + DCSdesc = nil, + Type = nil, + IsInfantry=nil, + SpeedMax = nil, + Tsuppress_ave = 15, + Tsuppress_min = 5, + Tsuppress_max = 25, + TsuppressOver = nil, + IniGroupStrength = nil, + Nhit = 0, + Formation = "Off road", + Speed = 4, + MenuON = false, + FallbackON = false, + FallbackWait = 60, + FallbackDist = 100, + FallbackHeading = nil, + TakecoverON = false, + TakecoverWait = 120, + TakecoverRange = 300, + hideout = nil, + PminFlee = 10, + PmaxFlee = 90, + RetreatZone = nil, + RetreatDamage = nil, + RetreatWait = 7200, + CurrentAlarmState = "unknown", + CurrentROE = "unknown", + DefaultAlarmState = "Auto", + DefaultROE = "Weapon Free", + eventmoose = true, +} + +--- Enumerator of possible rules of engagement. +-- @field #list ROE +SUPPRESSION.ROE={ + Hold="Weapon Hold", + Free="Weapon Free", + Return="Return Fire", +} + +--- Enumerator of possible alarm states. +-- @field #list AlarmState +SUPPRESSION.AlarmState={ + Auto="Auto", + Green="Green", + Red="Red", +} + +--- Main F10 menu for suppresion, i.e. F10/Suppression. +-- @field #string MenuF10 +SUPPRESSION.MenuF10=nil + +--- Some ID to identify who we are in output of the DCS.log file. +-- @field #string id +SUPPRESSION.id="SUPPRESSION | " + +--- PSEUDOATC version. +-- @field #number version +SUPPRESSION.version="0.9.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--TODO list +--DONE: Figure out who was shooting and move away from him. +--DONE: Move behind a scenery building if there is one nearby. +--DONE: Retreat to a given zone or point. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Creates a new AI_suppression object. +-- @param #SUPPRESSION self +-- @param Wrapper.Group#GROUP group The GROUP object for which suppression should be applied. +-- @return #SUPPRESSION SUPPRESSION object. +-- @return nil If group does not exist or is not a ground group. +function SUPPRESSION:New(group) + BASE:F2(group) + + -- Inherits from FSM_CONTROLLABLE + local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #SUPPRESSION + + -- Check that group is present. + if group then + self:T(SUPPRESSION.id..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName())) + else + self:E(SUPPRESSION.id.."Suppressive fire: Requested group does not exist! (Has to be a MOOSE group.)") + return nil + end + + -- Check that we actually have a GROUND group. + if group:IsGround()==false then + self:E(SUPPRESSION.id..string.format("SUPPRESSION fire group %s has to be a GROUND group!", group:GetName())) + return nil + end + + -- Set the controllable for the FSM. + self:SetControllable(group) + + -- Get DCS descriptors of group. + local DCSgroup=Group.getByName(group:GetName()) + local DCSunit=DCSgroup:getUnit(1) + self.DCSdesc=DCSunit:getDesc() + + -- Get max speed the group can do and convert to km/h. + self.SpeedMax=self.DCSdesc.speedMaxOffRoad*3.6 + + -- Set speed to maximum. + self.Speed=self.SpeedMax + + -- Is this infantry or not. + self.IsInfantry=DCSunit:hasAttribute("Infantry") + + -- Type of group. + self.Type=group:GetTypeName() + + -- Initial group strength. + self.IniGroupStrength=#group:GetUnits() + + -- Set ROE and Alarm State. + self:SetDefaultROE("Free") + self:SetDefaultAlarmState("Auto") + + -- Transitions + self:AddTransition("*", "Start", "CombatReady") + self:AddTransition("CombatReady", "Hit", "Suppressed") + self:AddTransition("Suppressed", "Hit", "Suppressed") + self:AddTransition("Suppressed", "Recovered", "CombatReady") + self:AddTransition("Suppressed", "TakeCover", "TakingCover") + self:AddTransition("Suppressed", "FallBack", "FallingBack") + self:AddTransition("*", "Retreat", "Retreating") + self:AddTransition("TakingCover", "FightBack", "CombatReady") + self:AddTransition("FallingBack", "FightBack", "CombatReady") + self:AddTransition("Retreating", "Retreated", "Retreated") + self:AddTransition("*", "Dead", "*") + + self:AddTransition("TakingCover", "Hit", "TakingCover") + self:AddTransition("FallingBack", "Hit", "FallingBack") + + --- User function for OnBefore "Hit" event. + -- @function [parent=#SUPPRESSION] OnBeforeHit + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT Unit Unit that was hit. + -- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked. + -- @return #boolean + + --- User function for OnAfer "Hit" event. + -- @function [parent=#SUPPRESSION] OnAfterHit + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT Unit Unit that was hit. + -- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked. + + + --- User function for OnBefore "Recovered" event. + -- @function [parent=#SUPPRESSION] OnBeforeRecovered + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @return #boolean + + --- User function for OnAfter "Recovered" event. + -- @function [parent=#SUPPRESSION] OnAfterRecovered + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- User function for OnBefore "TakeCover" event. + -- @function [parent=#SUPPRESSION] OnBeforeTakeCover + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Point#COORDINATE Hideout Place where the group will hide. + -- @return #boolean + + --- User function for OnAfter "TakeCover" event. + -- @function [parent=#SUPPRESSION] OnAfterTakeCover + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Point#COORDINATE Hideout Place where the group will hide. + + + --- User function for OnBefore "FallBack" event. + -- @function [parent=#SUPPRESSION] OnBeforeFallBack + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this. + -- @return #boolean + + --- User function for OnAfter "FallBack" event. + -- @function [parent=#SUPPRESSION] OnAfterFallBack + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this. + + + --- User function for OnBefore "Retreat" event. + -- @function [parent=#SUPPRESSION] OnBeforeRetreat + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @return #boolean + + --- User function for OnAfter "Retreat" event. + -- @function [parent=#SUPPRESSION] OnAfterRetreat + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- User function for OnBefore "Retreated" event. + -- @function [parent=#SUPPRESSION] OnBeforeRetreated + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @return #boolean + + --- User function for OnAfter "Retreated" event. + -- @function [parent=#SUPPRESSION] OnAfterRetreated + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- User function for OnBefore "FlightBack" event. + -- @function [parent=#SUPPRESSION] OnBeforeFightBack + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @return #boolean + + --- User function for OnAfter "FlightBack" event. + -- @function [parent=#SUPPRESSION] OnAfterFightBack + -- @param #SUPPRESSION self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set average, minimum and maximum time a unit is suppressed each time it gets hit. +-- @param #SUPPRESSION self +-- @param #number Tave Average time [seconds] a group will be suppressed. Default is 15 seconds. +-- @param #number Tmin (Optional) Minimum time [seconds] a group will be suppressed. Default is 5 seconds. +-- @param #number Tmax (Optional) Maximum time a group will be suppressed. Default is 25 seconds. +function SUPPRESSION:SetSuppressionTime(Tave, Tmin, Tmax) + self:F({Tave=Tave, Tmin=Tmin, Tmax=Tmax}) + + -- Minimum suppression time is input or default but at least 1 second. + self.Tsuppress_min=Tmin or self.Tsuppress_min + self.Tsuppress_min=math.max(self.Tsuppress_min, 1) + + -- Maximum suppression time is input or dault but at least Tmin. + self.Tsuppress_max=Tmax or self.Tsuppress_max + self.Tsuppress_max=math.max(self.Tsuppress_max, self.Tsuppress_min) + + -- Expected suppression time is input or default but at leat Tmin and at most Tmax. + self.Tsuppress_ave=Tave or self.Tsuppress_ave + self.Tsuppress_ave=math.max(self.Tsuppress_min) + self.Tsuppress_ave=math.min(self.Tsuppress_max) + + self:T(SUPPRESSION.id..string.format("Set ave suppression time to %d seconds.", self.Tsuppress_ave)) + self:T(SUPPRESSION.id..string.format("Set min suppression time to %d seconds.", self.Tsuppress_min)) + self:T(SUPPRESSION.id..string.format("Set max suppression time to %d seconds.", self.Tsuppress_max)) +end + +--- Set the zone to which a group retreats after being damaged too much. +-- @param #SUPPRESSION self +-- @param Core.Zone#ZONE zone MOOSE zone object. +function SUPPRESSION:SetRetreatZone(zone) + self:F({zone=zone}) + self.RetreatZone=zone +end + +--- Turn Debug mode on. Enables messages and more output to DCS log file. +-- @param #SUPPRESSION self +function SUPPRESSION:DebugOn() + self:F() + self.Debug=true +end + +--- Flare units when they are hit, die or recover from suppression. +-- @param #SUPPRESSION self +function SUPPRESSION:FlareOn() + self:F() + self.flare=true +end + +--- Smoke positions where units fall back to, hide or retreat. +-- @param #SUPPRESSION self +function SUPPRESSION:SmokeOn() + self:F() + self.smoke=true +end + +--- Set the formation a group uses for fall back, hide or retreat. +-- @param #SUPPRESSION self +-- @param #string formation Formation of the group. Default "Vee". +function SUPPRESSION:SetFormation(formation) + self:F(formation) + self.Formation=formation or "Vee" +end + +--- Set speed a group moves at for fall back, hide or retreat. +-- @param #SUPPRESSION self +-- @param #number speed Speed in km/h of group. Default max speed the group can do. +function SUPPRESSION:SetSpeed(speed) + self:F(speed) + self.Speed=speed or self.SpeedMax + self.Speed=math.min(self.Speed, self.SpeedMax) +end + +--- Enable fall back if a group is hit. +-- @param #SUPPRESSION self +-- @param #boolean switch Enable=true or disable=false fall back of group. +function SUPPRESSION:Fallback(switch) + self:F(switch) + if switch==nil then + switch=true + end + self.FallbackON=switch +end + +--- Set distance a group will fall back when it gets hit. +-- @param #SUPPRESSION self +-- @param #number distance Distance in meters. +function SUPPRESSION:SetFallbackDistance(distance) + self:F(distance) + self.FallbackDist=distance +end + +--- Set time a group waits at its fall back position before it resumes its normal mission. +-- @param #SUPPRESSION self +-- @param #number time Time in seconds. +function SUPPRESSION:SetFallbackWait(time) + self:F(time) + self.FallbackWait=time +end + +--- Enable take cover option if a unit is hit. +-- @param #SUPPRESSION self +-- @param #boolean switch Enable=true or disable=false fall back of group. +function SUPPRESSION:Takecover(switch) + self:F(switch) + if switch==nil then + switch=true + end + self.TakecoverON=switch +end + +--- Set time a group waits at its hideout position before it resumes its normal mission. +-- @param #SUPPRESSION self +-- @param #number time Time in seconds. +function SUPPRESSION:SetTakecoverWait(time) + self:F(time) + self.TakecoverWait=time +end + +--- Set distance a group searches for hideout places. +-- @param #SUPPRESSION self +-- @param #number range Search range in meters. +function SUPPRESSION:SetTakecoverRange(range) + self:F(range) + self.TakecoverRange=range +end + +--- Set hideout place explicitly. +-- @param #SUPPRESSION self +-- @param Core.Point#COORDINATE Hideout Place where the group will hide after the TakeCover event. +function SUPPRESSION:SetTakecoverPlace(Hideout) + self.hideout=Hideout +end + +--- Set minimum probability that a group flees (falls back or takes cover) after a hit event. Default is 10%. +-- @param #SUPPRESSION self +-- @param #number probability Probability in percent. +function SUPPRESSION:SetMinimumFleeProbability(probability) + self:F(probability) + self.PminFlee=probability or 10 +end + +--- Set maximum probability that a group flees (falls back or takes cover) after a hit event. Default is 90%. +-- @param #SUPPRESSION self +-- @param #number probability Probability in percent. +function SUPPRESSION:SetMaximumFleeProbability(probability) + self:F(probability) + self.PmaxFlee=probability or 90 +end + +--- Set damage threshold before a group is ordered to retreat if a retreat zone was defined. +-- If the group consists of only a singe unit, this referrs to the life of the unit. +-- If the group consists of more than one unit, this referrs to the group strength relative to its initial strength. +-- @param #SUPPRESSION self +-- @param #number damage Damage in percent. If group gets damaged above this value, the group will retreat. Default 50 %. +function SUPPRESSION:SetRetreatDamage(damage) + self:F(damage) + self.RetreatDamage=damage or 50 +end + +--- Set time a group waits in the retreat zone before it resumes its mission. Default is two hours. +-- @param #SUPPRESSION self +-- @param #number time Time in seconds. Default 7200 seconds = 2 hours. +function SUPPRESSION:SetRetreatWait(time) + self:F(time) + self.RetreatWait=time or 7200 +end + +--- Set alarm state a group will get after it returns from a fall back or take cover. +-- @param #SUPPRESSION self +-- @param #string alarmstate Alarm state. Possible "Auto", "Green", "Red". Default is "Auto". +function SUPPRESSION:SetDefaultAlarmState(alarmstate) + self:F(alarmstate) + if alarmstate:lower()=="auto" then + self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto + elseif alarmstate:lower()=="green" then + self.DefaultAlarmState=SUPPRESSION.AlarmState.Green + elseif alarmstate:lower()=="red" then + self.DefaultAlarmState=SUPPRESSION.AlarmState.Red + else + self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto + end +end + +--- Set Rules of Engagement (ROE) a group will get when it recovers from suppression. +-- @param #SUPPRESSION self +-- @param #string roe ROE after suppression. Possible "Free", "Hold" or "Return". Default "Free". +function SUPPRESSION:SetDefaultROE(roe) + self:F(roe) + if roe:lower()=="free" then + self.DefaultROE=SUPPRESSION.ROE.Free + elseif roe:lower()=="hold" then + self.DefaultROE=SUPPRESSION.ROE.Hold + elseif roe:lower()=="return" then + self.DefaultROE=SUPPRESSION.ROE.Return + else + self.DefaultROE=SUPPRESSION.ROE.Free + end +end + +--- Create an F10 menu entry for the suppressed group. The menu is mainly for Debugging purposes. +-- @param #SUPPRESSION self +-- @param #boolean switch Enable=true or disable=false menu group. Default is true. +function SUPPRESSION:MenuOn(switch) + self:F(switch) + if switch==nil then + switch=true + end + self.MenuON=switch +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create F10 main menu, i.e. F10/Suppression. The menu is mainly for Debugging purposes. +-- @param #SUPPRESSION self +function SUPPRESSION:_CreateMenuGroup() + local SubMenuName=self.Controllable:GetName() + local MenuGroup=MENU_MISSION:New(SubMenuName, SUPPRESSION.MenuF10) + MENU_MISSION_COMMAND:New("Fallback!", MenuGroup, self.OrderFallBack, self) + MENU_MISSION_COMMAND:New("Take Cover!", MenuGroup, self.OrderTakeCover, self) + MENU_MISSION_COMMAND:New("Retreat!", MenuGroup, self.OrderRetreat, self) + MENU_MISSION_COMMAND:New("Report Status", MenuGroup, self.Status, self, true) +end + +--- Order group to fall back between 100 and 150 meters in a random direction. +-- @param #SUPPRESSION self +function SUPPRESSION:OrderFallBack() + local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE + local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150, 100) + local coord=COORDINATE:NewFromVec2(vicinity) + self:FallBack(self.Controllable) +end + +--- Order group to take cover at a nearby scenery object. +-- @param #SUPPRESSION self +function SUPPRESSION:OrderTakeCover() + -- Search place to hide or take specified one. + local Hideout=self.hideout + if self.hideout==nil then + Hideout=self:_SearchHideout() + end + -- Trigger TakeCover event. + self:TakeCover(Hideout) +end + +--- Order group to retreat to a pre-defined zone. +-- @param #SUPPRESSION self +function SUPPRESSION:OrderRetreat() + self:Retreat() +end + +--- Status of group. Current ROE, alarm state, life. +-- @param #SUPPRESSION self +-- @param #boolean message Send message to all players. +function SUPPRESSION:Status(message) + + local name=self.Controllable:GetName() + local nunits=#self.Controllable:GetUnits() + local roe=self.CurrentROE + local state=self.CurrentAlarmState + local life_min, life_max, life_ave, life_ave0, groupstrength=self:_GetLife() + + local text=string.format("Status of group %s\n", name) + text=text..string.format("Number of units: %d of %d\n", nunits, self.IniGroupStrength) + text=text..string.format("Current state: %s\n", self:GetState()) + text=text..string.format("ROE: %s\n", roe) + text=text..string.format("Alarm state: %s\n", state) + text=text..string.format("Hits taken: %d\n", self.Nhit) + text=text..string.format("Life min: %3.0f\n", life_min) + text=text..string.format("Life max: %3.0f\n", life_max) + text=text..string.format("Life ave: %3.0f\n", life_ave) + text=text..string.format("Life ave0: %3.0f\n", life_ave0) + text=text..string.format("Group strength: %3.0f", groupstrength) + + MESSAGE:New(text, 10):ToAllIf(message or self.Debug) + self:T(SUPPRESSION.id.."\n"..text) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Start" event. Initialized ROE and alarm state. Starts the event handler. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterStart(Controllable, From, Event, To) + self:_EventFromTo("onafterStart", Event, From, To) + + local text=string.format("Started SUPPRESSION for group %s.", Controllable:GetName()) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + + local rzone="not defined" + if self.RetreatZone then + rzone=self.RetreatZone:GetName() + end + + -- Set retreat damage value if it was not set by user input. + if self.RetreatDamage==nil then + if self.RetreatZone then + if self.IniGroupStrength==1 then + self.RetreatDamage=60.0 -- 40% of life is left. + elseif self.IniGroupStrength==2 then + self.RetreatDamage=50.0 -- 50% of group left, i.e. 1 of 2. We already order a retreat, because if for a group 2 two a zone is defined it would not be used at all. + else + self.RetreatDamage=66.5 -- 34% of the group is left, e.g. 1 of 3,4 or 5, 2 of 6,7 or 8, 3 of 9,10 or 11, 4/12, 4/13, 4/14, 5/15, ... + end + else + self.RetreatDamage=100 -- If no retreat then this should be set to 100%. + end + end + + -- Create main F10 menu if it is not there yet. + if self.MenuON then + if not SUPPRESSION.MenuF10 then + SUPPRESSION.MenuF10 = MENU_MISSION:New("Suppression") + end + self:_CreateMenuGroup() + end + + -- Set the current ROE and alam state. + self:_SetAlarmState(self.DefaultAlarmState) + self:_SetROE(self.DefaultROE) + + local text=string.format("\n******************************************************\n") + text=text..string.format("Suppressed group = %s\n", Controllable:GetName()) + text=text..string.format("Type = %s\n", self.Type) + text=text..string.format("IsInfantry = %s\n", tostring(self.IsInfantry)) + text=text..string.format("Group strength = %d\n", self.IniGroupStrength) + text=text..string.format("Average time = %5.1f seconds\n", self.Tsuppress_ave) + text=text..string.format("Minimum time = %5.1f seconds\n", self.Tsuppress_min) + text=text..string.format("Maximum time = %5.1f seconds\n", self.Tsuppress_max) + text=text..string.format("Default ROE = %s\n", self.DefaultROE) + text=text..string.format("Default AlarmState = %s\n", self.DefaultAlarmState) + text=text..string.format("Fall back ON = %s\n", tostring(self.FallbackON)) + text=text..string.format("Fall back distance = %5.1f m\n", self.FallbackDist) + text=text..string.format("Fall back wait = %5.1f seconds\n", self.FallbackWait) + text=text..string.format("Fall back heading = %s degrees\n", tostring(self.FallbackHeading)) + text=text..string.format("Take cover ON = %s\n", tostring(self.TakecoverON)) + text=text..string.format("Take cover search = %5.1f m\n", self.TakecoverRange) + text=text..string.format("Take cover wait = %5.1f seconds\n", self.TakecoverWait) + text=text..string.format("Min flee probability = %5.1f\n", self.PminFlee) + text=text..string.format("Max flee probability = %5.1f\n", self.PmaxFlee) + text=text..string.format("Retreat zone = %s\n", rzone) + text=text..string.format("Retreat damage = %5.1f %%\n", self.RetreatDamage) + text=text..string.format("Retreat wait = %5.1f seconds\n", self.RetreatWait) + text=text..string.format("Speed = %5.1f km/h\n", self.Speed) + text=text..string.format("Speed max = %5.1f km/h\n", self.SpeedMax) + text=text..string.format("Formation = %s\n", self.Formation) + text=text..string.format("******************************************************\n") + self:T(SUPPRESSION.id..text) + + -- Add event handler. + if self.eventmoose then + self:HandleEvent(EVENTS.Hit, self._OnEventHit) + self:HandleEvent(EVENTS.Dead, self._OnEventDead) + else + world.addEventHandler(self) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Hit" event. (Of course, this is not really before the group got hit.) +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Unit Unit that was hit. +-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked. +-- @return boolean +function SUPPRESSION:onbeforeHit(Controllable, From, Event, To, Unit, AttackUnit) + self:_EventFromTo("onbeforeHit", Event, From, To) + + --local Tnow=timer.getTime() + --env.info(SUPPRESSION.id..string.format("Last hit = %s %s", tostring(self.LastHit), tostring(Tnow))) + + return true +end + +--- After "Hit" event. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Unit Unit that was hit. +-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked. +function SUPPRESSION:onafterHit(Controllable, From, Event, To, Unit, AttackUnit) + self:_EventFromTo("onafterHit", Event, From, To) + + -- Suppress unit. + if From=="CombatReady" or From=="Suppressed" then + self:_Suppress() + end + + -- Get life of group in %. + local life_min, life_max, life_ave, life_ave0, groupstrength=self:_GetLife() + + -- Damage in %. If group consists only of one unit, we take its life value. + local Damage=100-life_ave0 + + -- Condition for retreat. + local RetreatCondition = Damage >= self.RetreatDamage-0.01 and self.RetreatZone + + -- Probability that a unit flees. The probability increases linearly with the damage of the group/unit. + -- If Damage=0 ==> P=Pmin + -- if Damage=RetreatDamage ==> P=Pmax + -- If no retreat zone has been specified, RetreatDamage is 100. + local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage * math.min(Damage, self.RetreatDamage) + self.PminFlee + + -- Evaluate flee condition. + local P=math.random(0,100) + local FleeCondition = P < Pflee + + local text + text=string.format("\nGroup %s: Life min=%5.1f, max=%5.1f, ave=%5.1f, ave0=%5.1f group=%5.1f\n", Controllable:GetName(), life_min, life_max, life_ave, life_ave0, groupstrength) + text=string.format("Group %s: Damage = %8.4f (%8.4f retreat threshold).\n", Controllable:GetName(), Damage, self.RetreatDamage) + text=string.format("Group %s: P_Flee = %5.1f %5.1f=P_rand (P_Flee > Prand ==> Flee)\n", Controllable:GetName(), Pflee, P) + self:T(SUPPRESSION.id..text) + + -- Group is obviously destroyed. + if Damage >= 99.9 then + return + end + + if RetreatCondition then + + -- Trigger Retreat event. + self:Retreat() + + elseif FleeCondition then + + if self.FallbackON and AttackUnit:IsGround() then + + -- Trigger FallBack event. + self:FallBack(AttackUnit) + + elseif self.TakecoverON then + + -- Search place to hide or take specified one. + local Hideout=self.hideout + if self.hideout==nil then + Hideout=self:_SearchHideout() + end + + -- Trigger TakeCover event. + self:TakeCover(Hideout) + end + end + + -- Give info on current status. + if self.Debug then + self:Status() + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Recovered" event. Check if suppression time is over. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean +function SUPPRESSION:onbeforeRecovered(Controllable, From, Event, To) + self:_EventFromTo("onbeforeRecovered", Event, From, To) + + -- Current time. + local Tnow=timer.getTime() + + -- Debug info + self:T(SUPPRESSION.id..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver)) + + -- Recovery is only possible if enough time since the last hit has passed. + if Tnow >= self.TsuppressionOver then + return true + else + return false + end + +end + +--- After "Recovered" event. Group has recovered and its ROE is set back to the "normal" unsuppressed state. Optionally the group is flared green. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterRecovered(Controllable, From, Event, To) + self:_EventFromTo("onafterRecovered", Event, From, To) + + if Controllable and Controllable:IsAlive() then + + -- Debug message. + local text=string.format("Group %s has recovered!", Controllable:GetName()) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + self:T(SUPPRESSION.id..text) + + -- Set ROE back to default. + self:_SetROE() + + -- Flare unit green. + if self.flare or self.Debug then + Controllable:FlareGreen() + end + + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "FightBack" event. ROE and Alarm state are set back to default. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterFightBack(Controllable, From, Event, To) + self:_EventFromTo("onafterFightBack", Event, From, To) + + -- Set ROE and alarm state back to default. + self:_SetROE() + self:_SetAlarmState() +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "FallBack" event. We check that group is not already falling back. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this. +-- @return #boolean +function SUPPRESSION:onbeforeFallBack(Controllable, From, Event, To, AttackUnit) + self:_EventFromTo("onbeforeFallBack", Event, From, To) + + --TODO: Add retreat? Only allowd transition is Suppressed-->Fallback. So in principle no need. + if From == "FallingBack" then + return false + else + return true + end +end + +--- After "FallBack" event. We get the heading away from the attacker and route the group a certain distance in that direction. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this. +function SUPPRESSION:onafterFallBack(Controllable, From, Event, To, AttackUnit) + self:_EventFromTo("onafterFallback", Event, From, To) + + -- Debug info + self:T(SUPPRESSION.id..string.format("Group %s is falling back after %d hits.", Controllable:GetName(), self.Nhit)) + + -- Coordinate of the attacker and attacked unit. + local ACoord=AttackUnit:GetCoordinate() + local DCoord=Controllable:GetCoordinate() + + -- Heading from attacker to attacked unit. + local heading=self:_Heading(ACoord, DCoord) + + -- Overwrite heading with user specified heading. + if self.FallbackHeading then + heading=self.FallbackHeading + end + + -- Create a coordinate ~ 100 m in opposite direction of the attacking unit. + local Coord=DCoord:Translate(self.FallbackDist, heading) + + -- Place marker + if self.Debug then + local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName()) + end + + -- Smoke the coordinate. + if self.smoke or self.Debug then + Coord:SmokeBlue() + end + + -- Set ROE to weapon hold. + self:_SetROE(SUPPRESSION.ROE.Hold) + + -- Set alarm state to GREEN and let the unit run away. + self:_SetAlarmState(SUPPRESSION.AlarmState.Green) + + -- Make the group run away. + self:_Run(Coord, self.Speed, self.Formation, self.FallbackWait) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "TakeCover" event. Search an area around the group for possible scenery objects where the group can hide. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Hideout Place where the group will hide. +-- @return #boolean +function SUPPRESSION:onbeforeTakeCover(Controllable, From, Event, To, Hideout) + self:_EventFromTo("onbeforeTakeCover", Event, From, To) + + --TODO: Need to test this! + if From=="TakingCover" then + return false + end + + -- Block transition if no hideout place is given. + if Hideout ~= nil then + return true + else + return false + end + +end + +--- After "TakeCover" event. Group will run to a nearby scenery object and "hide" there for a certain time. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Hideout Place where the group will hide. +function SUPPRESSION:onafterTakeCover(Controllable, From, Event, To, Hideout) + self:_EventFromTo("onafterTakeCover", Event, From, To) + + if self.Debug then + local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s", Controllable:GetName())) + end + + -- Smoke place of hideout. + if self.smoke or self.Debug then + Hideout:SmokeBlue() + end + + -- Set ROE to weapon hold. + self:_SetROE(SUPPRESSION.ROE.Hold) + + -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. + self:_SetAlarmState(SUPPRESSION.AlarmState.Green) + + -- Make the group run away. + self:_Run(Hideout, self.Speed, self.Formation, self.TakecoverWait) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Retreat" event. We check that the group is not already retreating. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean True if transition is allowed, False if transition is forbidden. +function SUPPRESSION:onbeforeRetreat(Controllable, From, Event, To) + self:_EventFromTo("onbeforeRetreat", Event, From, To) + + if From=="Retreating" then + local text=string.format("Group %s is already retreating.") + self:T2(SUPPRESSION.id..text) + return false + else + return true + end + +end + +--- After "Retreat" event. Find a random point in the retreat zone and route the group there. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterRetreat(Controllable, From, Event, To) + self:_EventFromTo("onafterRetreat", Event, From, To) + + -- Route the group to a zone. + local text=string.format("Group %s is retreating! Alarm state green.", Controllable:GetName()) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + self:T(SUPPRESSION.id..text) + + -- Get a random point in the retreat zone. + local ZoneCoord=self.RetreatZone:GetRandomCoordinate() -- Core.Point#COORDINATE + local ZoneVec2=ZoneCoord:GetVec2() + + -- Debug smoke zone and point. + if self.smoke or self.Debug then + ZoneCoord:SmokeBlue() + end + if self.Debug then + self.RetreatZone:SmokeZone(SMOKECOLOR.Red, 12) + end + + -- Set ROE to weapon hold. + self:_SetROE(SUPPRESSION.ROE.Hold) + + -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. + self:_SetAlarmState(SUPPRESSION.AlarmState.Green) + + -- Make unit run to retreat zone and wait there for ~two hours. + self:_Run(ZoneCoord, self.Speed, self.Formation, self.RetreatWait) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Before "Retreateded" event. Check that the group is really in the retreat zone. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onbeforeRetreated(Controllable, From, Event, To) + self:_EventFromTo("onbeforeRetreated", Event, From, To) + + -- Check that the group is inside the zone. + local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3()) + + return inzone +end + +--- After "Retreateded" event. Group has reached the retreat zone. Set ROE to return fire and alarm state to auto. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterRetreated(Controllable, From, Event, To) + self:_EventFromTo("onafterRetreated", Event, From, To) + + -- Set ROE to weapon return fire. + self:_SetROE(SUPPRESSION.ROE.Return) + + -- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire. + self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) + + -- TODO: Add hold task? Move from _Run() +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- After "Dead" event, when a unit has died. When all units of a group are dead, FSM is stopped and eventhandler removed. +-- @param #SUPPRESSION self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function SUPPRESSION:onafterDead(Controllable, From, Event, To) + self:_EventFromTo("onafterDead", Event, From, To) + + -- Number of units left in the group. + local nunits=#self.Controllable:GetUnits() + + local text=string.format("Group %s: One of our units just died! %d units left.", self.Controllable:GetName(), nunits) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + self:T(SUPPRESSION.id..text) + + -- Go to stop state. + if nunits==0 then + self:T(SUPPRESSION.id..string.format("Stopping SUPPRESSION for group %s.", Controllable:GetName())) + self:Stop() + if self.mooseevents then + self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.Hit) + else + world.removeEventHandler(self) + end + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Event Handler +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event handler for suppressed groups. +--@param #SUPPRESSION self +function SUPPRESSION:onEvent(Event) + --self:E(event) + + if Event == nil or Event.initiator == nil or Unit.getByName(Event.initiator:getName()) == nil then + return true + end + + local EventData={} + if Event.initiator then + EventData.IniDCSUnit = Event.initiator + EventData.IniUnitName = Event.initiator:getName() + EventData.IniDCSGroup = Event.initiator:getGroup() + EventData.IniGroupName = Event.initiator:getGroup():getName() + EventData.IniGroup = GROUP:FindByName(EventData.IniGroupName) + EventData.IniUnit = UNIT:FindByName(EventData.IniUnitName) + end + + if Event.target then + EventData.TgtDCSUnit = Event.target + EventData.TgtUnitName = Event.target:getName() + EventData.TgtDCSGroup = Event.target:getGroup() + EventData.TgtGroupName = Event.target:getGroup():getName() + EventData.TgtGroup = GROUP:FindByName(EventData.TgtGroupName) + EventData.TgtUnit = UNIT:FindByName(EventData.TgtUnitName) + end + + + -- Event HIT + if Event.id == world.event.S_EVENT_HIT then + self:_OnEventHit(EventData) + end + + -- Event DEAD + if Event.id == world.event.S_EVENT_DEAD then + self:_OnEventDead(EventData) + end + +end + +--- Event handler for Dead event of suppressed groups. +-- @param #SUPPRESSION self +-- @param Core.Event#EVENTDATA EventData +function SUPPRESSION:_OnEventHit(EventData) + self:F(EventData) + + local GroupNameSelf=self.Controllable:GetName() + local GroupNameTgt=EventData.TgtGroupName + local TgtUnit=EventData.TgtUnit + local tgt=EventData.TgtDCSUnit + local IniUnit=EventData.IniUnit + + -- Check that correct group was hit. + if GroupNameTgt == GroupNameSelf then + + self:T(SUPPRESSION.id..string.format("Hit event at t = %5.1f", timer.getTime())) + + -- Flare unit that was hit. + if self.flare or self.Debug then + TgtUnit:FlareRed() + end + + -- Increase Hit counter. + self.Nhit=self.Nhit+1 + + -- Info on hit times. + self:T(SUPPRESSION.id..string.format("Group %s has just been hit %d times.", self.Controllable:GetName(), self.Nhit)) + + --self:Status() + local life=tgt:getLife()/(tgt:getLife0()+1)*100 + self:T2(SUPPRESSION.id..string.format("Target unit life = %5.1f", life)) + + -- FSM Hit event. + self:__Hit(3, TgtUnit, IniUnit) + end + +end + +--- Event handler for Dead event of suppressed groups. +-- @param #SUPPRESSION self +-- @param Core.Event#EVENTDATA EventData +function SUPPRESSION:_OnEventDead(EventData) + + local GroupNameSelf=self.Controllable:GetName() + local GroupNameIni=EventData.IniGroupName + + -- Check for correct group. + if GroupNameIni== GroupNameSelf then + + -- Dead Unit. + local IniUnit=EventData.IniUnit --Wrapper.Unit#UNIT + local IniUnitName=EventData.IniUnitName + + if EventData.IniUnit then + self:T2(SUPPRESSION.id..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName)) + else + self:T2(SUPPRESSION.id..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.", GroupNameIni, IniUnitName)) + end + + if EventData.IniDCSUnit then + self:T2(SUPPRESSION.id..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName)) + else + self:T2(SUPPRESSION.id..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.", GroupNameIni, IniUnitName)) + end + + -- Flare unit that died. + if IniUnit and (self.flare or self.Debug) then + IniUnit:FlareWhite() + self:T(SUPPRESSION.id..string.format("Flare Dead MOOSE unit.")) + end + + -- Flare unit that died. + if EventData.IniDCSUnit and (self.flare or self.Debug) then + local p=EventData.IniDCSUnit:getPosition().p + trigger.action.signalFlare(p, trigger.flareColor.Yellow , 0) + self:T(SUPPRESSION.id..string.format("Flare Dead DCS unit.")) + end + + -- Get status. + self:Status() + + -- FSM Dead event. + self:__Dead(0.1) + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Suppress fire of a unit by setting its ROE to "Weapon Hold". +-- @param #SUPPRESSION self +function SUPPRESSION:_Suppress() + + -- Current time. + local Tnow=timer.getTime() + + -- Controllable + local Controllable=self.Controllable --Wrapper.Controllable#CONTROLLABLE + + -- Group will hold their weapons. + self:_SetROE(SUPPRESSION.ROE.Hold) + + -- Get randomized time the unit is suppressed. + local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4 + local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min, self.Tsuppress_max) + + -- Time at which the suppression is over. + local renew=true + if self.TsuppressionOver ~= nil then + if Tsuppress+Tnow > self.TsuppressionOver then + self.TsuppressionOver=Tnow+Tsuppress + else + renew=false + end + else + self.TsuppressionOver=Tnow+Tsuppress + end + + -- Recovery event will be called in Tsuppress seconds. + if renew then + self:__Recovered(self.TsuppressionOver-Tnow) + end + + -- Debug message. + local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.", Controllable:GetName(), Tsuppress, self.TsuppressionOver/60, self.TsuppressionOver%60) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + self:T(SUPPRESSION.id..text) + +end + + +--- Make group run/drive to a certain point. We put in several intermediate waypoints because sometimes the group stops before it arrived at the desired point. +--@param #SUPPRESSION self +--@param Core.Point#COORDINATE fin Coordinate where we want to go. +--@param #number speed Speed of group. Default is 20. +--@param #string formation Formation of group. Default is "Vee". +--@param #number wait Time the group will wait/hold at final waypoint. Default is 30 seconds. +function SUPPRESSION:_Run(fin, speed, formation, wait) + + speed=speed or 20 + formation=formation or "Off road" + wait=wait or 30 + + local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE + + -- Clear all tasks. + group:ClearTasks() + + -- Current coordinates of group. + local ini=group:GetCoordinate() + + -- Distance between current and final point. + local dist=ini:Get2DDistance(fin) + + -- Heading from ini to fin. + local heading=self:_Heading(ini, fin) + + -- Number of waypoints. + local nx + if dist <= 50 then + nx=2 + elseif dist <= 100 then + nx=3 + elseif dist <= 500 then + nx=4 + else + nx=5 + end + + -- Number of intermediate waypoints. + local dx=dist/(nx-1) + + -- Waypoint and task arrays. + local wp={} + local tasks={} + + -- First waypoint is the current position of the group. + wp[1]=ini:WaypointGround(speed, formation) + tasks[1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, 1, false) + + if self.Debug then + local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)", #wp, self.Controllable:GetName())) + end + + self:T2(SUPPRESSION.id..string.format("Number of waypoints %d", nx)) + for i=1,nx-2 do + + local x=dx*i + local coord=ini:Translate(x, heading) + + wp[#wp+1]=coord:WaypointGround(speed, formation) + tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, false) + + self:T2(SUPPRESSION.id..string.format("%d x = %4.1f", i, x)) + if self.Debug then + local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s", #wp, self.Controllable:GetName())) + end + + end + self:T2(SUPPRESSION.id..string.format("Total distance: %4.1f", dist)) + + -- Final waypoint. + wp[#wp+1]=fin:WaypointGround(speed, formation) + if self.Debug then + local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName())) + end + + -- Task to hold. + local ConditionWait=group:TaskCondition(nil, nil, nil, nil, wait, nil) + local TaskHold = group:TaskHold() + + -- Task combo to make group hold at final waypoint. + local TaskComboFin = {} + TaskComboFin[#TaskComboFin+1] = group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, true) + TaskComboFin[#TaskComboFin+1] = group:TaskControlled(TaskHold, ConditionWait) + + -- Add final task. + tasks[#tasks+1]=group:TaskCombo(TaskComboFin) + + -- Original waypoints of the group. + local Waypoints = group:GetTemplateRoutePoints() + + -- New points are added to the default route. + for i,p in ipairs(wp) do + table.insert(Waypoints, i, wp[i]) + end + + -- Set task for all waypoints. + for i,wp in ipairs(Waypoints) do + group:SetTaskWaypoint(Waypoints[i], tasks[i]) + end + + -- Submit task and route group along waypoints. + group:Route(Waypoints) + +end + +--- Function called when group is passing a waypoint. At the last waypoint we set the group back to CombatReady. +--@param Wrapper.Group#GROUP group Group which is passing a waypoint. +--@param #SUPPRESSION Fsm The suppression object. +--@param #number i Waypoint number that has been reached. +--@param #boolean final True if it is the final waypoint. Start Fightback. +function SUPPRESSION._Passing_Waypoint(group, Fsm, i, final) + + -- Debug message. + local text=string.format("Group %s passing waypoint %d (final=%s)", group:GetName(), i, tostring(final)) + MESSAGE:New(text,10):ToAllIf(Fsm.Debug) + if Fsm.Debug then + env.info(SUPPRESSION.id..text) + end + + if final then + if Fsm:is("Retreating") then + -- Retreated-->Retreated. + Fsm:Retreated() + else + -- FightBack-->Combatready: Change alarm state back to default. + Fsm:FightBack() + end + end +end + + +--- Search a place to hide. This is any scenery object in the vicinity. +--@param #SUPPRESSION self +--@return Core.Point#COORDINATE Coordinate of the hideout place. +--@return nil If no scenery object is within search radius. +function SUPPRESSION:_SearchHideout() + -- We search objects in a zone with radius ~300 m around the group. + local Zone = ZONE_GROUP:New("Zone_Hiding", self.Controllable, self.TakecoverRange) + local gpos = self.Controllable:GetCoordinate() + + -- Scan for Scenery objects to run/drive to. + Zone:Scan(Object.Category.SCENERY) + + -- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group. + local hideouts={} + + for SceneryTypeName, SceneryData in pairs(Zone:GetScannedScenery()) do + for SceneryName, SceneryObject in pairs(SceneryData) do + + local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY + + -- Position of the scenery object. + local spos=SceneryObject:GetCoordinate() + + -- Distance from group to hideout. + local distance= spos:Get2DDistance(gpos) + + if self.Debug then + -- Place markers on every possible scenery object. + local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(),SceneryObject:GetTypeName())) + local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS()) + self:T2(SUPPRESSION.id..text) + end + + -- Add to table. + table.insert(hideouts, {object=SceneryObject, distance=distance}) + end + end + + -- Get random hideout place. + local Hideout=nil + if #hideouts>0 then + + -- Debug info. + self:T(SUPPRESSION.id.."Number of hideouts "..#hideouts) + + -- Sort results table wrt number of hits. + local _sort = function(a,b) return a.distance < b.distance end + table.sort(hideouts,_sort) + + -- Pick a random location. + --Hideout=hideouts[math.random(#hideouts)].object + + -- Pick closest location. + Hideout=hideouts[1].object:GetCoordinate() + + else + self:E(SUPPRESSION.id.."No hideouts found!") + end + + return Hideout + +end + +--- Get (relative) life in percent of a group. Function returns the value of the units with the smallest and largest life. Also the average value of all groups is returned. +-- @param #SUPPRESSION self +-- @return #number Smallest life value of all units. +-- @return #number Largest life value of all units. +-- @return #number Average life value of all alife groups +-- @return #number Average life value of all groups including already dead ones. +-- @return #number Relative group strength. +function SUPPRESSION:_GetLife() + + local group=self.Controllable --Wrapper.Group#GROUP + + if group and group:IsAlive() then + + local units=group:GetUnits() + + local life_min=nil + local life_max=nil + local life_ave=0 + local life_ave0=0 + local n=0 + + local groupstrength=#units/self.IniGroupStrength*100 + + self.T2(SUPPRESSION.id..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units)) + + for _,unit in pairs(units) do + + local unit=unit -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + n=n+1 + local life=unit:GetLife()/(unit:GetLife0()+1)*100 + if life_min==nil or life < life_min then + life_min=life + end + if life_max== nil or life > life_max then + life_max=life + end + life_ave=life_ave+life + if self.Debug then + local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f", n, unit:GetLife(), unit:GetLife0(), life_min, life_max, life_ave/n,groupstrength) + self:T2(SUPPRESSION.id..text) + end + end + + end + + -- If the counter did not increase (can happen!) return 0 + if n==0 then + return 0,0,0,0,0 + end + + -- Average life relative to initial group strength including the dead ones. + life_ave0=life_ave/self.IniGroupStrength + + -- Average life of all alive units. + life_ave=life_ave/n + + return life_min, life_max, life_ave, life_ave0, groupstrength + else + return 0, 0, 0, 0, 0 + end +end + + +--- Heading from point a to point b in degrees. +--@param #SUPPRESSION self +--@param Core.Point#COORDINATE a Coordinate. +--@param Core.Point#COORDINATE b Coordinate. +--@return #number angle Angle from a to b in degrees. +function SUPPRESSION:_Heading(a, b) + local dx = b.x-a.x + local dy = b.z-a.z + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + return angle +end + +--- Generate Gaussian pseudo-random numbers. +-- @param #SUPPRESSION self +-- @param #number x0 Expectation value of distribution. +-- @param #number sigma (Optional) Standard deviation. Default 10. +-- @param #number xmin (Optional) Lower cut-off value. +-- @param #number xmax (Optional) Upper cut-off value. +-- @return #number Gaussian random number. +function SUPPRESSION:_Random_Gaussian(x0, sigma, xmin, xmax) + + -- Standard deviation. Default 5 if not given. + sigma=sigma or 5 + + local r + local gotit=false + local i=0 + while not gotit do + + -- Uniform numbers in [0,1). We need two. + local x1=math.random() + local x2=math.random() + + -- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). + r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 + + i=i+1 + if (r>=xmin and r<=xmax) or i>100 then + gotit=true + end + end + + return r + +end + +--- Sets the ROE for the group and updates the current ROE variable. +-- @param #SUPPRESSION self +-- @param #string roe ROE the group will get. Possible "Free", "Hold", "Return". Default is self.DefaultROE. +function SUPPRESSION:_SetROE(roe) + local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE + + -- If no argument is given, we take the default ROE. + roe=roe or self.DefaultROE + + -- Update the current ROE. + self.CurrentROE=roe + + -- Set the ROE. + if roe==SUPPRESSION.ROE.Free then + group:OptionROEOpenFire() + elseif roe==SUPPRESSION.ROE.Hold then + group:OptionROEHoldFire() + elseif roe==SUPPRESSION.ROE.Return then + group:OptionROEReturnFire() + else + self:E(SUPPRESSION.id.."Unknown ROE requested: "..tostring(roe)) + group:OptionROEOpenFire() + self.CurrentROE=SUPPRESSION.ROE.Free + end + + local text=string.format("Group %s now has ROE %s.", self.Controllable:GetName(), self.CurrentROE) + self:T(SUPPRESSION.id..text) +end + +--- Sets the alarm state of the group and updates the current alarm state variable. +-- @param #SUPPRESSION self +-- @param #string state Alarm state the group will get. Possible "Auto", "Green", "Red". Default is self.DefaultAlarmState. +function SUPPRESSION:_SetAlarmState(state) + local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE + + -- Input or back to default alarm state. + state=state or self.DefaultAlarmState + + -- Update the current alam state of the group. + self.CurrentAlarmState=state + + -- Set the alarm state. + if state==SUPPRESSION.AlarmState.Auto then + group:OptionAlarmStateAuto() + elseif state==SUPPRESSION.AlarmState.Green then + group:OptionAlarmStateGreen() + elseif state==SUPPRESSION.AlarmState.Red then + group:OptionAlarmStateRed() + else + self:E(SUPPRESSION.id.."Unknown alarm state requested: "..tostring(state)) + group:OptionAlarmStateAuto() + self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto + end + + local text=string.format("Group %s now has Alarm State %s.", self.Controllable:GetName(), self.CurrentAlarmState) + self:T(SUPPRESSION.id..text) +end + +--- Print event-from-to string to DCS log file. +-- @param #SUPPRESSION self +-- @param #string BA Before/after info. +-- @param #string Event Event. +-- @param #string From From state. +-- @param #string To To state. +function SUPPRESSION:_EventFromTo(BA, Event, From, To) + local text=string.format("\n%s: %s EVENT %s: %s --> %s", BA, self.Controllable:GetName(), Event, From, To) + self:T2(SUPPRESSION.id..text) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index e093eb3ef..767f2e634 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -2,10 +2,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\ZONE_CAPTURE_COALITION\Dia1.JPG) --- --- === --- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAZ - Capture Zones) -- -- - CAZ-000 - Capture Zone: Demonstrates the basic concept of capturing a zone. @@ -21,7 +17,8 @@ -- -- === -- --- @module ZoneCaptureCoalition +-- @module Functional.ZoneCaptureCoalition +-- @image Capture_Zones.JPG do -- ZONE_CAPTURE_COALITION @@ -29,9 +26,7 @@ do -- ZONE_CAPTURE_COALITION -- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION - --- # ZONE\_CAPTURE\_COALITION class, extends @{ZoneGoalCoalition#ZONE_GOAL_COALITION} - -- - -- Models the process to capture a Zone for a Coalition, which is guarded by another Coalition. + --- Models the process to capture a Zone for a Coalition, which is guarded by another Coalition. -- This is a powerful concept that allows to create very dynamic missions based on the different state transitions of various zones. -- -- --- diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua index 5bca12a4e..0caabc8a3 100644 --- a/Moose Development/Moose/Functional/ZoneGoal.lua +++ b/Moose Development/Moose/Functional/ZoneGoal.lua @@ -11,7 +11,8 @@ -- -- === -- --- @module ZoneGoal +-- @module Functional.ZoneGoal +-- @image MOOSE.JPG do -- Zone @@ -19,9 +20,7 @@ do -- Zone -- @extends Core.Fsm#FSM - --- # ZONE_GOAL class, extends @{Fsm#FSM} - -- - -- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone. + -- Models processes that have a Goal with a defined achievement involving a Zone. -- Derived classes implement the ways how the achievements can be realized. -- -- ## 1. ZONE_GOAL constructor @@ -36,7 +35,7 @@ do -- Zone -- -- ### 2.2 ZONE_GOAL Events -- - -- * DestroyedUnit: A @{Unit} is destroyed in the Zone. The event will only get triggered if the method @{#ZONE_GOAL.MonitorDestroyedUnits}() is used. + -- * DestroyedUnit: A @{Wrapper.Unit} is destroyed in the Zone. The event will only get triggered if the method @{#ZONE_GOAL.MonitorDestroyedUnits}() is used. -- -- @field #ZONE_GOAL ZONE_GOAL = { diff --git a/Moose Development/Moose/Functional/ZoneGoalCargo.lua b/Moose Development/Moose/Functional/ZoneGoalCargo.lua index e5ea44310..9ae8ae613 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCargo.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCargo.lua @@ -11,7 +11,8 @@ -- -- === -- --- @module ZoneGoalCargo +-- @module Functional.ZoneGoalCargo +-- @image MOOSE.JPG do -- ZoneGoal @@ -19,9 +20,7 @@ do -- ZoneGoal -- @extends Functional.ZoneGoal#ZONE_GOAL - --- # ZONE_GOAL_CARGO class, extends @{ZoneGoal#ZONE_GOAL} - -- - -- ZONE_GOAL_CARGO models processes that have a Goal with a defined achievement involving a Zone and Cargo. + --- Models processes that have a Goal with a defined achievement involving a Zone and Cargo. -- Derived classes implement the ways how the achievements can be realized. -- -- ## 1. ZONE_GOAL_CARGO constructor diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua index 91c7df0dc..ba86ffe95 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -11,7 +11,8 @@ -- -- === -- --- @module ZoneGoalCoalition +-- @module Functional.ZoneGoalCoalition +-- @image MOOSE.JPG do -- ZoneGoal @@ -19,9 +20,7 @@ do -- ZoneGoal -- @extends Functional.ZoneGoal#ZONE_GOAL - --- # ZONE_GOAL_COALITION class, extends @{ZoneGoal#ZONE_GOAL} - -- - -- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. + --- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition. -- Derived classes implement the ways how the achievements can be realized. -- -- ## 1. ZONE_GOAL_COALITION constructor diff --git a/Moose Development/Moose/Moose.lua b/Moose Development/Moose/Moose.lua index 5e1e394cc..05f3b8cf3 100644 --- a/Moose Development/Moose/Moose.lua +++ b/Moose Development/Moose/Moose.lua @@ -13,3 +13,6 @@ _DATABASE = DATABASE:New() -- Core.Database#DATABASE _SETTINGS = SETTINGS:Set() _SETTINGS:SetPlayerMenuOn() +_DATABASE:_RegisterCargos() +_DATABASE:_RegisterZones() + diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index e8e4f1d32..e8f9204e4 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -1,5 +1,12 @@ ---- **Tasking** -- A COMMANDCENTER is the owner of multiple missions within MOOSE. --- A COMMANDCENTER governs multiple missions, the tasking and the reporting. +--- **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. -- -- === -- @@ -9,23 +16,19 @@ -- -- === -- --- @module CommandCenter - - - +-- @module Tasking.CommandCenter +-- @image Task_Command_Center.JPG --- The COMMANDCENTER class -- @type COMMANDCENTER -- @field Wrapper.Group#GROUP HQ --- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition +-- @field DCS#coalition CommandCenterCoalition -- @list Missions -- @extends Core.Base#BASE ---- # COMMANDCENTER class, extends @{Base#BASE} --- --- The COMMANDCENTER class governs multiple missions, the tasking and the reporting. +--- Governs multiple missions, the tasking and the reporting. -- -- The commandcenter communicates important messages between the various groups of human players executing tasks in missions. -- @@ -86,11 +89,13 @@ COMMANDCENTER = { -- @return #COMMANDCENTER function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER self.CommandCenterPositionable = CommandCenterPositionable self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() + + self.AutoAssignTasks = false self.Missions = {} @@ -171,7 +176,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) end ) - -- Handle when a player leaves a slot and goes back to spectators ... + -- 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, @@ -220,6 +225,14 @@ function COMMANDCENTER:GetShortText() end +--- Gets the coalition of the command center. +-- @param #COMMANDCENTER self +-- @return DCScoalition#coalition +function COMMANDCENTER:GetCoalition() + + return self.CommandCenterCoalition +end + --- Gets the POSITIONABLE of the HQ command center. -- @param #COMMANDCENTER self @@ -233,7 +246,7 @@ end -- @return #list function COMMANDCENTER:GetMissions() - return self.Missions + return self.Missions or {} end --- Add a MISSION to be governed by the HQ command center. @@ -322,7 +335,7 @@ end --- Sets the menu structure of the Missions governed by the HQ command center. -- @param #COMMANDCENTER self function COMMANDCENTER:SetMenu() - self:F() + self:F2() local MenuTime = timer.getTime() for MissionID, Mission in pairs( self:GetMissions() or {} ) do @@ -331,7 +344,7 @@ function COMMANDCENTER:SetMenu() end for MissionID, Mission in pairs( self:GetMissions() or {} ) do - local Mission = Mission -- Tasking.Mission#MISSION + Mission = Mission -- Tasking.Mission#MISSION Mission:RemoveMenu( MenuTime ) end @@ -340,10 +353,133 @@ end --- Gets the commandcenter menu structure governed by the HQ command center. -- @param #COMMANDCENTER self -- @return Core.Menu#MENU_COALITION -function COMMANDCENTER:GetMenu() - return self.CommandCenterMenu +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 AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask") + local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignRandomTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask") + else + local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):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:AssignRandomTask( TaskGroup ) + + local Tasks = {} + + 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 + Tasks[#Tasks+1] = MissionTask + end + end + + local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK + + Task:SetAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) ) + + Task:AssignToGroup( TaskGroup ) + +end + + +--- Automatically assigns tasks to all TaskGroups. +-- @param #COMMANDCENTER self +-- @param #boolean AutoAssign true for ON and false or nil for OFF. +-- @return #COMMANDCENTER +function COMMANDCENTER:SetAutoAssignTasks( AutoAssign ) + + self.AutoAssignTasks = AutoAssign or false + + local GroupSet = self:AddGroups() + + for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do + local TaskGroup = TaskGroup -- Wrapper.Group#GROUP + self:GetMenu( TaskGroup ) + end + + if self.AutoAssignTasks == true then + self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self ) + else + self:ScheduleStop( self.AssignTasks ) + end + +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 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:AssignRandomTask( TaskGroup ) + end + end + end +end + + +--- Get all the Groups active within the command center. +-- @param #COMMANDCENTER self +-- @return Core.Set#SET_GROUP +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 +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 COMMANDCENTER has a GROUP. -- @param #COMMANDCENTER self -- @param Wrapper.Group#GROUP @@ -399,7 +535,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition ) + self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) end @@ -413,7 +549,7 @@ function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition ) + self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() ) end diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 6252dc0a4..c27511cde 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -1,38 +1,36 @@ ---- This module contains the DETECTION_MANAGER class and derived classes. +--- **Tasking** - This module contains the DETECTION_MANAGER class and derived classes. -- -- === -- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Fsm#FSM} --- === --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- 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: -- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. +-- * @{#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 @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. +-- 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 @{DetectionManager#DETECTION_MANAGER.SetRefreshTimeInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- 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 @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). +-- 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) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- 2) @{#DETECTION_REPORTING} class, extends @{#DETECTION_MANAGER} -- === --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. +-- 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 @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. +-- The @{#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- -- === @@ -40,13 +38,14 @@ -- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing -- ### Author: FlightControl - Framework Design & Programming -- --- @module DetectionManager +-- @module Tasking.DetectionManager +-- @image Task_Detection_Manager.JPG do -- DETECTION MANAGER --- DETECTION_MANAGER class. -- @type DETECTION_MANAGER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @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 Core.Fsm#FSM DETECTION_MANAGER = { @@ -57,7 +56,7 @@ do -- DETECTION MANAGER --- FAC constructor. -- @param #DETECTION_MANAGER self - -- @param Set#SET_GROUP SetGroup + -- @param Core.Set#SET_GROUP SetGroup -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:New( SetGroup, Detection ) @@ -122,6 +121,48 @@ do -- 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" ) @@ -175,7 +216,7 @@ do -- DETECTION MANAGER return self._ReportDisplayTime end - --- Reports the detected items to the @{Set#SET_GROUP}. + --- Reports the detected items to the @{Core.Set#SET_GROUP}. -- @param #DETECTION_MANAGER self -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self @@ -191,7 +232,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING class. -- @type DETECTION_REPORTING - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @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 = { @@ -201,7 +242,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING constructor. -- @param #DETECTION_REPORTING self - -- @param Set#SET_GROUP SetGroup + -- @param Core.Set#SET_GROUP SetGroup -- @param Functional.Detection#DETECTION_AREAS Detection -- @return #DETECTION_REPORTING self function DETECTION_REPORTING:New( SetGroup, Detection ) @@ -215,7 +256,7 @@ do -- DETECTION_REPORTING --- Creates a string of the detected items in a @{Detection}. -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @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() @@ -245,10 +286,10 @@ do -- DETECTION_REPORTING - --- Reports the detected items to the @{Set#SET_GROUP}. + --- Reports the detected items to the @{Core.Set#SET_GROUP}. -- @param #DETECTION_REPORTING self - -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @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 ) diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index 06a7e7d9b..c2f9f2de1 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -1,4 +1,12 @@ ---- **Tasking** -- A MISSION is the main owner of a Mission orchestration within MOOSE. +--- **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. -- -- === -- @@ -8,7 +16,8 @@ -- -- === -- --- @module Mission +-- @module Tasking.Mission +-- @image Task_Mission.JPG --- The MISSION class -- @type MISSION @@ -29,7 +38,7 @@ MISSION = { -- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. -- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. -- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @param #string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... -- @return #MISSION self function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) @@ -265,6 +274,8 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi end + + --- FSM function for a MISSION -- @param #MISSION self -- @param #string From @@ -375,24 +386,30 @@ function MISSION:GetScoring() return self.Scoring end ---- Get the groups for which TASKS are given in the mission +--- 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() - local SetGroup = SET_GROUP:New() + 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 - local GroupSet = Task:GetGroups() - GroupSet:ForEachGroup( - function( TaskGroup ) - SetGroup:Add( TaskGroup, TaskGroup ) - end - ) + GroupSet = Task:AddGroups( GroupSet ) end - return SetGroup + return GroupSet end @@ -441,16 +458,16 @@ do -- Group Assignment local MissionGroupName = MissionGroup:GetName() if self.AssignedGroups[MissionGroupName] == MissionGroup then - self:T( { "Mission is assigned to:", MissionGroup:GetName() } ) + self:T2( { "Mission is assigned to:", MissionGroup:GetName() } ) return true end - self:T( { "Mission is not assigned to:", MissionGroup:GetName() } ) + self:T2( { "Mission is not assigned to:", MissionGroup:GetName() } ) return false end - --- Set @{Group} assigned to the @{Mission}. + --- Set @{Wrapper.Group} assigned to the @{Mission}. -- @param #MISSION self -- @param Wrapper.Group#GROUP MissionGroup -- @return #MISSION @@ -465,7 +482,7 @@ do -- Group Assignment return self end - --- Clear the @{Group} assignment from the @{Mission}. + --- Clear the @{Wrapper.Group} assignment from the @{Mission}. -- @param #MISSION self -- @param Wrapper.Group#GROUP MissionGroup -- @return #MISSION @@ -522,18 +539,13 @@ end function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() + local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup ) - --local MissionMenu = CommandCenterMenu:GetMenu( MissionName ) - self.MissionGroupMenu = self.MissionGroupMenu or {} self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {} local GroupMenu = self.MissionGroupMenu[TaskGroup] - local CommandCenterText = CommandCenter:GetText() - CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ) - local MissionText = self:GetText() self.MissionMenu = MENU_GROUP:New( TaskGroup, MissionText, CommandCenterMenu ) @@ -562,7 +574,7 @@ end -- @param #string TaskName The Name of the @{Task} within the @{Mission}. -- @return Tasking.Task#TASK The Task -- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) +function MISSION:GetTask( TaskName ) self:F( { TaskName } ) return self.Tasks[TaskName] @@ -1003,9 +1015,28 @@ end -- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) function MISSION:GetTasks() - return self.Tasks + 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. diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index e8ae381a1..f76cbf7fe 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module Task +-- @module Tasking.Task +-- @image MOOSE.JPG --- @type TASK -- @field Core.Scheduler#SCHEDULER TaskScheduler @@ -21,7 +22,7 @@ -- @extends Core.Fsm#FSM_TASK --- --- # TASK class, extends @{Base#BASE} +-- # TASK class, extends @{Core.Base#BASE} -- -- ## The TASK class implements the methods for task orchestration within MOOSE. -- @@ -30,9 +31,9 @@ -- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). -- * @{#TASK.AddProcess}():Add a @{Process} to a task. -- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. --- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. --- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} +-- * @{#TASK.SetStateMachine}():Set a @{Core.Fsm} to a task. +-- * @{#TASK.RemoveStateMachine}():Remove @{Core.Fsm} from a task. +-- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Core.Fsm} -- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. -- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. -- * @{#TASK.SetTimeOut}(): Set timer in seconds before task gets cancelled if not assigned. @@ -161,7 +162,7 @@ TASK = { -- @return #TASK self function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, FSM_TASK:New() ) -- Tasking.Task#TASK + local self = BASE:Inherit( self, FSM_TASK:New( TaskName ) ) -- Tasking.Task#TASK self:SetStartState( "Planned" ) self:AddTransition( "Planned", "Assign", "Assigned" ) @@ -169,17 +170,24 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) self:AddTransition( "Assigned", "Success", "Success" ) self:AddTransition( "Assigned", "Hold", "Hold" ) self:AddTransition( "Assigned", "Fail", "Failed" ) - self:AddTransition( "Assigned", "Abort", "Aborted" ) + 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 @{Unit} of the player. + -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. -- @param #string PlayerName The name of the player. -- @return #boolean @@ -189,26 +197,27 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) -- @param #string From -- @param #string Event -- @param #string To - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player. + -- @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 @{Unit} of the player. + -- @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 @{Unit} of the player. + -- @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" ) @@ -216,7 +225,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) self:F( "New TASK " .. TaskName ) self.Processes = {} - self.Fsm = {} self.Mission = Mission self.CommandCenter = Mission:GetCommandCenter() @@ -229,7 +237,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) self:SetBriefing( TaskBriefing ) - self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() self.TaskInfo = TASKINFO:New( self ) @@ -246,7 +253,8 @@ function TASK:GetUnitProcess( TaskUnit ) if TaskUnit then return self:GetStateMachine( TaskUnit ) else - return self.FsmTemplate + self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() + return self.FsmTemplate end end @@ -295,34 +303,61 @@ function TASK:JoinUnit( PlayerUnit, PlayerGroup ) return PlayerUnitAdded end ---- Abort a PlayerUnit from a Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. +--- A group rejecting a planned task. -- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @param Wrapper.Group#GROUP PlayerGroup The group rejecting the task. -- @return #TASK -function TASK:AbortGroup( PlayerGroup ) - self:F( { PlayerGroup = PlayerGroup } ) +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 to the Task. If yes, the PlayerGroup is aborted from the Task. + -- 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 + 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 ) - self:F( { IsGroupAssigned = IsGroupAssigned } ) if IsGroupAssigned then local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - --self:MessageToGroups( PlayerName .. " aborted Task " .. self:GetName() ) self:UnAssignFromGroup( PlayerGroup ) - --self:Abort() -- 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 @@ -347,11 +382,10 @@ function TASK:AbortGroup( PlayerGroup ) return self end ---- A PlayerUnit crashed in a Task. Abort the Player. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. + +--- A group crashing and thus aborting from the task. -- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task. -- @return #TASK function TASK:CrashGroup( PlayerGroup ) self:F( { PlayerGroup = PlayerGroup } ) @@ -413,9 +447,29 @@ end -- @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. @@ -436,7 +490,7 @@ do -- Group Assignment end - --- Set @{Group} assigned to the @{Task}. + --- Set @{Wrapper.Group} assigned to the @{Task}. -- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup -- @return #TASK @@ -466,7 +520,7 @@ do -- Group Assignment return self end - --- Clear the @{Group} assignment from the @{Task}. + --- Clear the @{Wrapper.Group} assignment from the @{Task}. -- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup -- @return #TASK @@ -500,7 +554,17 @@ end do -- Group Assignment - --- Assign the @{Task} to a @{Group}. + --- @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 @@ -535,7 +599,7 @@ do -- Group Assignment return self end - --- UnAssign the @{Task} from a @{Group}. + --- UnAssign the @{Task} from a @{Wrapper.Group}. -- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup function TASK:UnAssignFromGroup( TaskGroup ) @@ -570,7 +634,7 @@ function TASK:HasGroup( FindGroup ) end ---- Assign the @{Task} to an alive @{Unit}. +--- Assign the @{Task} to an alive @{Wrapper.Unit}. -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK self @@ -589,7 +653,7 @@ function TASK:AssignToUnit( TaskUnit ) return self end ---- UnAssign the @{Task} from an alive @{Unit}. +--- UnAssign the @{Task} from an alive @{Wrapper.Unit}. -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK self @@ -597,7 +661,9 @@ 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 @@ -612,7 +678,7 @@ function TASK:SetTimeOut ( Timer ) return self end ---- Send a message of the @{Task} to the assigned @{Group}s. +--- Send a message of the @{Task} to the assigned @{Wrapper.Group}s. -- @param #TASK self function TASK:MessageToGroups( Message ) self:F( { Message = Message } ) @@ -620,35 +686,40 @@ function TASK:MessageToGroups( Message ) local Mission = self:GetMission() local CC = Mission:GetCommandCenter() - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() ) - end -end - - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK self -function TASK:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do - - if self:IsGroupAssigned( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) + 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 ---- UnAssign the @{Task} from the @{Group}s. +--- Send the briefng 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:GetAliveSet() ) do - if self:IsGroupAssigned(TaskGroup) then - self:UnAssignFromGroup( TaskGroup ) + 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 @@ -661,13 +732,15 @@ end function TASK:HasAliveUnits() self:F() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do - 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 + 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 @@ -686,7 +759,8 @@ function TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. self:F( { self:GetName(), MenuTime } ) --self.SetGroup:Flush() - for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetAliveSet() ) do + --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 @@ -729,21 +803,14 @@ function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime ) local Mission = self:GetMission() local MissionName = Mission:GetName() - local CommandCenter = Mission:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() + local MissionMenu = Mission:GetMenu( TaskGroup ) 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 TaskText = string.format( "%s", self:GetName() ) local TaskName = string.format( "%s", self:GetName() ) - local MissionMenu = Mission:GetMenu( TaskGroup ) - --local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime ) - - --local MissionMenu = Mission:GetMenu( TaskGroup ) - 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" ) @@ -768,26 +835,24 @@ end function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) self:F( { TaskGroup:GetName(), MenuTime } ) - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local CommandCenter = Mission:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - 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() ) - local MissionMenu = Mission:GetMenu( TaskGroup ) --- local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime ) --- local MissionMenu = Mission:GetMenu( TaskGroup ) - - self.MenuAssigned = self.MenuAssigned or {} - self.MenuAssigned[TaskGroup] = MENU_GROUP_DELAYED:New( TaskGroup, string.format( "Assigned Task %s", TaskName ), MissionMenu ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Abort Task" ), self.MenuAssigned[TaskGroup], self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local MarkMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Mark Task Location on Map" ), self.MenuAssigned[TaskGroup], self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Report Task Details" ), self.MenuAssigned[TaskGroup], self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) + 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" ) + end + end return self end @@ -799,16 +864,18 @@ end function TASK:RemoveMenu( MenuTime ) self:F( { self:GetName(), MenuTime } ) - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then - self:RefreshMenus( TaskGroup, 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 @{Group}. +--- Remove the menu option of the @{Task} for a @{Wrapper.Group}. -- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup -- @param #number MenuTime @@ -818,9 +885,6 @@ function TASK:RefreshMenus( TaskGroup, MenuTime ) local Mission = self:GetMission() local MissionName = Mission:GetName() - local CommandCenter = Mission:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - local MissionMenu = Mission:GetMenu( TaskGroup ) local TaskName = self:GetName() @@ -842,7 +906,7 @@ function TASK:RefreshMenus( TaskGroup, MenuTime ) end ---- Remove the assigned menu option of the @{Task} for a @{Group}. +--- Remove the assigned menu option of the @{Task} for a @{Wrapper.Group}. -- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup -- @param #number MenuTime @@ -852,7 +916,6 @@ function TASK:RemoveAssignedMenuForGroup( TaskGroup ) local Mission = self:GetMission() local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) if MissionMenu then @@ -939,7 +1002,7 @@ end -- TODO: Obscolete? ---- Fail processes from @{Task} with key @{Unit} +--- Fail processes from @{Task} with key @{Wrapper.Unit} -- @param #TASK self -- @param #string TaskUnitName -- @return #TASK self @@ -951,7 +1014,7 @@ function TASK:FailProcesses( TaskUnitName ) end end ---- Add a FiniteStateMachine to @{Task} with key Task@{Unit} +--- Add a FiniteStateMachine to @{Task} with key Task@{Wrapper.Unit} -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Core.Fsm#FSM_PROCESS Fsm @@ -964,7 +1027,7 @@ function TASK:SetStateMachine( TaskUnit, Fsm ) return Fsm end ---- Gets the FiniteStateMachine of @{Task} with key Task@{Unit} +--- Gets the FiniteStateMachine of @{Task} with key Task@{Wrapper.Unit} -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Fsm#FSM_PROCESS @@ -974,7 +1037,7 @@ function TASK:GetStateMachine( TaskUnit ) return self.Fsm[TaskUnit] end ---- Remove FiniteStateMachines from @{Task} with key Task@{Unit} +--- Remove FiniteStateMachines from @{Task} with key Task@{Wrapper.Unit} -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK self @@ -998,7 +1061,7 @@ function TASK:RemoveStateMachine( TaskUnit ) end ---- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} +--- Checks if there is a FiniteStateMachine assigned to Task@{Wrapper.Unit} for @{Task} -- @param #TASK self -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK self @@ -1210,12 +1273,16 @@ 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 - self:F( { From, Event, To, PlayerUnit:GetName(), PlayerName } ) - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned." ) - + 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 @@ -1231,7 +1298,7 @@ function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName ) self:SetMenu() self:F( { "--> Task Assigned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "--> Task Player Names", PlayerNames = self:GetPlayerNames() } ) + self:F( { "--> Task Player Names", PlayerNames = PlayerNames } ) end end @@ -1273,6 +1340,7 @@ function TASK:onenterAborted( From, Event, To ) end + --- FSM function for a TASK -- @param #TASK self -- @param #string From @@ -1441,11 +1509,13 @@ 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():GetAliveSet() ) do + for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - PlayerCount = PlayerCount + #PlayerNames + if PlayerGroup:IsAlive() == true then + if self:IsGroupAssigned( PlayerGroup ) then + local PlayerNames = PlayerGroup:GetPlayerNames() + PlayerCount = PlayerCount + #PlayerNames + end end end @@ -1461,12 +1531,14 @@ 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():GetAliveSet() ) do + for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - for PlayerNameID, PlayerName in pairs( PlayerNames ) do - PlayerNameMap[PlayerName] = PlayerGroup + if PlayerGroup:IsAlive() == true then + if self:IsGroupAssigned( PlayerGroup ) then + local PlayerNames = PlayerGroup:GetPlayerNames() + for PlayerNameID, PlayerName in pairs( PlayerNames ) do + PlayerNameMap[PlayerName] = PlayerGroup + end end end end @@ -1499,7 +1571,7 @@ function TASK:ReportDetails( ReportGroup ) local PlayerReport = REPORT:New() for PlayerName, PlayerGroup in pairs( PlayerNames ) do - PlayerReport:Add( "Group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName ) + PlayerReport:Add( "Players group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName ) end local Players = PlayerReport:Text() @@ -1595,3 +1667,65 @@ do -- Additional Task Scoring and Task Progress 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 index bf75bcf5a..2822615d5 100644 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ b/Moose Development/Moose/Tasking/TaskInfo.lua @@ -8,13 +8,14 @@ -- -- === -- --- @module TaskInfo +-- @module Tasking.TaskInfo +-- @image MOOSE.JPG --- @type TASKINFO -- @extends Core.Base#BASE --- --- # TASKINFO class, extends @{Base#BASE} +-- # TASKINFO class, extends @{Core.Base#BASE} -- -- ## The TASKINFO class implements the methods to contain information and display information of a task. -- @@ -249,17 +250,15 @@ end function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep ) local CargoReport = REPORT:New() + CargoReport:Add( "" ) SetCargo:ForEachCargo( - --- @param Core.Cargo#CARGO Cargo + --- @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() ) ) + CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) ) end ) - self:AddInfo( "CargoSet", CargoReport:Text(), Order, Detail, Keep ) + self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep ) return self end @@ -319,7 +318,7 @@ function TASKINFO:Report( Report, Detail, ReportGroup ) local Coordinate = Data.Data -- Core.Point#COORDINATE Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, self ) end - if Key == "CargoSet" then + if Key == "Cargo" then local DataText = Data.Data -- #string Text = DataText end diff --git a/Moose Development/Moose/Tasking/TaskZoneCapture.lua b/Moose Development/Moose/Tasking/TaskZoneCapture.lua index fd5934dfd..fb1ab81c5 100644 --- a/Moose Development/Moose/Tasking/TaskZoneCapture.lua +++ b/Moose Development/Moose/Tasking/TaskZoneCapture.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module TaskZoneCapture +-- @module Tasking.TaskZoneCapture +-- @image MOOSE.JPG do -- TASK_ZONE_GOAL @@ -17,14 +18,14 @@ do -- TASK_ZONE_GOAL -- @field Core.ZoneGoal#ZONE_GOAL ZoneGoal -- @extends Tasking.Task#TASK - --- # TASK_ZONE_GOAL class, extends @{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 @{Fsm#FSM_TASK}, and has the following statuses: + -- 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 @{Group#GROUP}. + -- * **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. -- @@ -44,7 +45,7 @@ do -- TASK_ZONE_GOAL --- Instantiates a new TASK_ZONE_GOAL. -- @param #TASK_ZONE_GOAL self -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @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.ZoneGoal#ZONE_GOAL ZoneGoal -- @return #TASK_ZONE_GOAL self @@ -166,12 +167,12 @@ do -- TASK_ZONE_CAPTURE -- @field Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal -- @extends #TASK_ZONE_GOAL - --- # TASK_ZONE_CAPTURE class, extends @{TaskZoneGoal#TASK_ZONE_GOAL} + --- # TASK_ZONE_CAPTURE class, extends @{Tasking.TaskZoneGoal#TASK_ZONE_GOAL} -- -- The TASK_ZONE_CAPTURE 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_ZONE_CAPTURE is used by the @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks + -- The TASK_ZONE_CAPTURE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks -- based on detected enemy ground targets. -- -- @field #TASK_ZONE_CAPTURE diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index 4db992c40..5fb22050b 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -8,24 +8,23 @@ -- -- === -- --- @module Task_A2A +-- @module Tasking.Tasking.Task_A2A +-- @image MOOSE.JPG do -- TASK_A2A --- The TASK_A2A class -- @type TASK_A2A - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2A class, extends @{Task#TASK} - -- - -- The TASK_A2A class defines Air To Air tasks for a @{Set} of Target Units, - -- based on the tasking capabilities defined in @{Task#TASK}. - -- The TASK_A2A is implemented using a @{Fsm#FSM_TASK}, and has the following statuses: + --- Defines Air To Air tasks for a @{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 @{Group#GROUP}. + -- * **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. -- @@ -45,9 +44,9 @@ do -- TASK_A2A --- Instantiates a new TASK_A2A. -- @param #TASK_A2A self -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned. + -- @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 Set#SET_UNIT UnitSetTargets + -- @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. @@ -62,8 +61,6 @@ do -- TASK_A2A local Fsm = self:GetUnitProcess() - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } ) - 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" } ) @@ -84,6 +81,15 @@ do -- TASK_A2A 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 @@ -352,18 +358,16 @@ do -- TASK_A2A_INTERCEPT --- The TASK_A2A_INTERCEPT class -- @type TASK_A2A_INTERCEPT - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2A_INTERCEPT class, extends @{Task_A2A#TASK_A2A} - -- - -- The TASK_A2A_INTERCEPT class defines an intercept task for a human player to be executed. + --- 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 urgen the players to get out there! -- - -- The TASK_A2A_INTERCEPT is used by the @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks + -- 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 @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. + -- 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 @@ -451,20 +455,18 @@ do -- TASK_A2A_SWEEP --- The TASK_A2A_SWEEP class -- @type TASK_A2A_SWEEP - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2A_SWEEP class, extends @{Task_A2A#TASK_A2A} - -- - -- The TASK_A2A_SWEEP class defines a sweep task for a human player to be executed. + --- 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 @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks + -- 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 @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. + -- 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 @@ -562,18 +564,16 @@ do -- TASK_A2A_ENGAGE --- The TASK_A2A_ENGAGE class -- @type TASK_A2A_ENGAGE - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2A_ENGAGE class, extends @{Task_A2A#TASK_A2A} - -- - -- The TASK_A2A_ENGAGE class defines an engage task for a human player to be executed. + --- 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 @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks + -- 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 @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. + -- 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 diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index a3fc20c88..8f85ef7d2 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -1,7 +1,17 @@ ---- **Tasking** - The TASK_A2A_DISPATCHER creates and manages player TASK_A2A tasks based on detected targets. +--- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. -- --- The @{#TASK_A2A_DISPATCHER} classes implement the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. +-- **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** @@ -10,7 +20,8 @@ -- -- === -- --- @module Task_A2A_Dispatcher +-- @module Tasking.Task_A2A_Dispatcher +-- @image Task_A2A_Dispatcher.JPG do -- TASK_A2A_DISPATCHER @@ -18,11 +29,7 @@ do -- TASK_A2A_DISPATCHER -- @type TASK_A2A_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER - --- # TASK_A2A_DISPATCHER class, extends @{Tasking#DETECTION_MANAGER} - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia1.JPG) - -- - -- The @{#TASK_A2A_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. + --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) -- @@ -83,7 +90,7 @@ do -- TASK_A2A_DISPATCHER -- 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_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class. + -- 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. -- @@ -182,7 +189,7 @@ do -- TASK_A2A_DISPATCHER --- TASK_A2A_DISPATCHER constructor. -- @param #TASK_A2A_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. + -- @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 ) @@ -200,6 +207,7 @@ do -- TASK_A2A_DISPATCHER self.Detection:SetRefreshTimeInterval( 30 ) self:AddTransition( "Started", "Assign", "Started" ) + --- OnAfter Transition Handler for Event Assign. -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign @@ -210,7 +218,7 @@ do -- TASK_A2A_DISPATCHER -- @param Tasking.Task_A2A#TASK_A2A Task -- @param Wrapper.Unit#UNIT TaskUnit -- @param #string PlayerName - + self:__Start( 5 ) return self @@ -250,7 +258,7 @@ do -- TASK_A2A_DISPATCHER --- Creates an INTERCEPT task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @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 } ) @@ -277,7 +285,7 @@ do -- TASK_A2A_DISPATCHER --- Creates an SWEEP task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @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 } ) @@ -303,7 +311,7 @@ do -- TASK_A2A_DISPATCHER --- 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 Set#SET_UNIT TargetSetUnit: The target set of units. + -- @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 } ) @@ -336,7 +344,7 @@ do -- TASK_A2A_DISPATCHER -- @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 @{Detection#DETECTION_BASE} derived object. + -- @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 @@ -484,9 +492,9 @@ do -- TASK_A2A_DISPATCHER end - --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. + --- 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 @{Detection#DETECTION_BASE} derived object. + -- @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() @@ -561,6 +569,22 @@ do -- TASK_A2A_DISPATCHER 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 diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index 6ecb4ce78..51294075d 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -8,24 +8,23 @@ -- -- === -- --- @module Task_A2G +-- @module Tasking.Task_A2G +-- @image MOOSE.JPG do -- TASK_A2G --- The TASK_A2G class -- @type TASK_A2G - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2G class, extends @{Task#TASK} - -- - -- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, - -- based on the tasking capabilities defined in @{Task#TASK}. - -- The TASK_A2G is implemented using a @{Fsm#FSM_TASK}, and has the following statuses: + --- The TASK_A2G class defines Air To Ground tasks for a @{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 @{Group#GROUP}. + -- * **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. -- @@ -45,9 +44,9 @@ do -- TASK_A2G --- Instantiates a new TASK_A2G. -- @param #TASK_A2G self -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @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 Set#SET_UNIT UnitSetTargets + -- @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. @@ -61,9 +60,6 @@ do -- TASK_A2G local Fsm = self:GetUnitProcess() - - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } ) - 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" } ) @@ -84,6 +80,18 @@ do -- TASK_A2G 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 @@ -355,15 +363,13 @@ do -- TASK_A2G_SEAD --- The TASK_A2G_SEAD class -- @type TASK_A2G_SEAD - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2G_SEAD class, extends @{Task_A2G#TASK_A2G} - -- - -- The TASK_A2G_SEAD class defines an Suppression or Extermination of Air Defenses task for a human player to be executed. + --- 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 @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks + -- 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 @@ -448,16 +454,14 @@ do -- TASK_A2G_BAI --- The TASK_A2G_BAI class -- @type TASK_A2G_BAI - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2G_BAI class, extends @{Task_A2G#TASK_A2G} - -- - -- The TASK_A2G_BAI class defines an Battlefield Air Interdiction task for a human player to be executed. + -- Defines an 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 @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create BAI tasks + -- 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 @@ -544,15 +548,13 @@ do -- TASK_A2G_CAS --- The TASK_A2G_CAS class -- @type TASK_A2G_CAS - -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- # TASK_A2G_CAS class, extends @{Task_A2G#TASK_A2G} - -- - -- The TASK_A2G_CAS class defines an Close Air Support task for a human player to be executed. + -- 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 @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create CAS tasks + -- 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 diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 256ca2db2..13cd2c5e7 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -1,5 +1,16 @@ ---- **Tasking** - The TASK\_A2G\_DISPATCHER dispatches A2G Tasks to Players based on enemy location detection. +--- **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) Supression 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** @@ -8,31 +19,29 @@ -- -- === -- --- @module Task_A2G_Dispatcher +-- @module Tasking.Task_A2G_Dispatcher +-- @image Task_A2G_Dispatcher.JPG do -- TASK_A2G_DISPATCHER --- TASK\_A2G\_DISPATCHER class. -- @type TASK_A2G_DISPATCHER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @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 - --- # TASK\_A2G\_DISPATCHER class, extends @{DetectionManager#DETECTION_MANAGER} + --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object. -- - -- ![](..\Presentations\TASK\_A2G\_DISPATCHER\Dia1.JPG) - -- - -- The TASK\_A2G\_DISPATCHER class orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{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 @{Set} of @{Group}s that will be manned by **Players**. - -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Group} @{Set}. + -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{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 @{Client}s of the @{Wrapper.Group} @{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 @{Set} of @{Group}s that are Recce vehicles or air units. + -- The detection object will group the detected targets by its grouping method, and integrates a @{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.. @@ -379,8 +388,8 @@ do -- TASK_A2G_DISPATCHER -- - A @{Mission} object. Each task belongs to a Mission. -- - A @{Detection} object. There are several detection grouping methods to choose from. -- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher. - -- - A @{Set} of @{Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object. - -- - A @{Set} ob @{Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object. + -- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object. + -- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object. -- -- Below an example mission declaration that is defines a Task A2G Dispatcher object. -- @@ -432,7 +441,7 @@ do -- TASK_A2G_DISPATCHER --- TASK_A2G_DISPATCHER constructor. -- @param #TASK_A2G_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. + -- @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 ) @@ -582,9 +591,9 @@ do -- TASK_A2G_DISPATCHER end - --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. + --- 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 @{Detection#DETECTION_BASE} derived object. + -- @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() @@ -761,6 +770,23 @@ do -- TASK_A2G_DISPATCHER 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 diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index 7d11cae8d..cb6edeb49 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -13,6 +13,7 @@ -- The following classes are important to consider: -- -- * @{#TASK_CARGO_TRANSPORT}: Defines a task for a human player to transport a set of cargo between various zones. +-- * @{#TASK_CARGO_CSAR}: Defines a task for a human player to Search and Rescue wounded pilots. -- -- === -- @@ -22,7 +23,8 @@ -- -- === -- --- @module Task_Cargo +-- @module Tasking.Task_Cargo +-- @image MOOSE.JPG do -- TASK_CARGO @@ -30,7 +32,7 @@ do -- TASK_CARGO -- @extends Tasking.Task#TASK --- - -- # TASK_CARGO class, extends @{Task#TASK} + -- # TASK_CARGO class, extends @{Tasking.Task#TASK} -- -- ## A flexible tasking system -- @@ -117,7 +119,7 @@ do -- TASK_CARGO -- ## Handle TASK_CARGO Events ... -- -- The TASK_CARGO classes define @{Cargo} transport tasks, - -- based on the tasking capabilities defined in @{Task#TASK}. + -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. -- -- ### Specific TASK_CARGO Events -- @@ -128,11 +130,11 @@ do -- TASK_CARGO -- -- ### Standard TASK_CARGO Events -- - -- The TASK_CARGO is implemented using a @{Statemachine#FSM_TASK}, and has the following standard statuses: + -- 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 @{Group#GROUP}. + -- * **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. -- @@ -147,7 +149,7 @@ do -- TASK_CARGO --- Instantiates a new TASK_CARGO. -- @param #TASK_CARGO self -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @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. @@ -166,24 +168,69 @@ do -- TASK_CARGO 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 Core.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 Core.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. + + + 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 Core.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 Core.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:SetStartState( "Planned" ) +-- +-- Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } ) - Fsm:AddTransition( { "Planned", "Assigned", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded", "Landed", "Boarding" }, "SelectAction", "*" ) + 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", "WaitingForCommand" ) + 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", "WaitingForCommand" ) + Fsm:AddTransition( "Cancelled", "CancelRouteToDeploy", "Cancelled" ) Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy", "Landing" }, "Land", "Landing" ) Fsm:AddTransition( "Landing", "Landed", "Landed" ) @@ -191,10 +238,14 @@ do -- TASK_CARGO 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" ) @@ -202,30 +253,33 @@ do -- TASK_CARGO 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 Tasking.Task_CARGO#TASK_CARGO Task + -- @param #TASK_CARGO Task function Fsm:onafterSelectAction( TaskUnit, Task ) local TaskUnitName = TaskUnit:GetName() - - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - local MenuTime = timer.getTime() - - TaskUnit.Menu = MENU_GROUP:New( TaskUnit:GetGroup(), Task:GetName() .. " @ " .. TaskUnit:GetName() ) - + local MenuTime = Task:InitTaskControlMenu( TaskUnit ) + local MenuControl = Task:GetTaskControlMenu( TaskUnit ) local CargoItemCount = TaskUnit:CargoItemCount() - - --Task:GetMission():GetCommandCenter():MessageToGroup( "Cargo in carrier: " .. CargoItemCount, TaskUnit:GetGroup() ) - Task.SetCargo:ForEachCargo( - --- @param Core.Cargo#CARGO Cargo + --- @param Cargo.Cargo#CARGO Cargo function( Cargo ) if Cargo:IsAlive() then @@ -234,18 +288,20 @@ do -- TASK_CARGO -- MENU_GROUP_COMMAND:New( -- TaskUnit:GetGroup(), -- "Cancel Route " .. Cargo.Name, --- TaskUnit.Menu, +-- MenuControl, -- self.MenuRouteToPickupCancel, -- self, -- Cargo -- ):SetTime(MenuTime) -- end - self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } ) + --self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } ) + local TaskGroup = TaskUnit:GetGroup() + if Cargo:IsUnLoaded() then - if CargoItemCount <= Task.CargoLimit then - if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + if CargoItemCount < 1 then + if Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then local NotInDeployZones = true for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do if Cargo:IsInZone( DeployZone ) then @@ -254,28 +310,82 @@ do -- TASK_CARGO end if NotInDeployZones then if not TaskUnit:InAir() then - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Board cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime) - TaskUnit.Menu:SetTime( MenuTime ) + 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 + if Cargo:CanSlingload() == true then + if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then + Cargo:Report( "Ready for slingloading.", "slingload", TaskUnit:GetGroup() ) + 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 - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Pickup cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime) - TaskUnit.Menu:SetTime( MenuTime ) + 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() ) + end + end + end + + -- Cargo in deployzones are flagged as deployed. + for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do + if Cargo:IsInZone( DeployZone ) then + Task:E( { 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:E( { 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 - - if Cargo:IsLoaded() then - if not TaskUnit:InAir() then - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Unboard cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuUnBoardCargo, self, Cargo ):SetTime(MenuTime) - TaskUnit.Menu:SetTime( MenuTime ) - 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 - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Deploy cargo at " .. DeployZoneName, TaskUnit.Menu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime) - TaskUnit.Menu:SetTime( MenuTime ) - 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 @@ -283,10 +393,9 @@ do -- TASK_CARGO end ) - TaskUnit.Menu:Remove( MenuTime ) + Task:RefreshTaskControlMenu( TaskUnit, MenuTime, "Cargo" ) - - self:__SelectAction( -15 ) + self:__SelectAction( -1 ) end @@ -294,21 +403,31 @@ do -- TASK_CARGO --- -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task + -- @param #TASK_CARGO Task function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - TaskUnit.Menu:Remove() + --local MenuControl = Task:GetTaskControlMenu( TaskUnit ) + + --MenuControl:Remove() end function Fsm:MenuBoardCargo( Cargo ) self:__PrepareBoarding( 1.0, Cargo ) end - function Fsm:MenuUnBoardCargo( Cargo, DeployZone ) + 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 @@ -335,7 +454,7 @@ do -- TASK_CARGO self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) if Cargo:IsAlive() then - self.Cargo = Cargo -- Core.Cargo#CARGO + self.Cargo = Cargo -- Cargo.Cargo#CARGO Task:SetCargoPickup( self.Cargo, TaskUnit ) self:__RouteToPickupPoint( -0.1 ) end @@ -350,7 +469,6 @@ do -- TASK_CARGO function Fsm:onafterArriveAtPickup( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) if self.Cargo:IsAlive() then - self.Cargo:Smoke( Task:GetSmokeColor(), 15 ) if TaskUnit:IsAir() then Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() ) self:__Land( -0.1, "Pickup" ) @@ -367,6 +485,7 @@ do -- TASK_CARGO 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 @@ -404,6 +523,7 @@ do -- TASK_CARGO 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 @@ -415,19 +535,30 @@ do -- TASK_CARGO function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if self.Cargo:IsAlive() then - if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) + 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 - Task:GetMission():GetCommandCenter():MessageToGroup( "Landed ...", TaskUnit:GetGroup() ) - self:__Landed( -0.1, Action ) + self:__RouteToPickup( -0.1, self.Cargo ) end - else - if Action == "Pickup" then - self:__RouteToPickupZone( -0.1 ) + 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:__RouteToDeployZone( -0.1 ) + self:__RouteToDeploy( -0.1, self.Cargo ) end end end @@ -439,18 +570,28 @@ do -- TASK_CARGO function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if self.Cargo:IsAlive() then - if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -0.1, Action ) + 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:__SelectAction( -0.1 ) + self:__RouteToPickup( -0.1, self.Cargo ) end - else - if Action == "Pickup" then - self:__RouteToPickupZone( -0.1 ) + 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:__RouteToDeployZone( -0.1 ) + self:__RouteToDeploy( -0.1, self.Cargo ) end end end @@ -463,30 +604,30 @@ do -- TASK_CARGO self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) if Cargo and Cargo:IsAlive() then - self.Cargo = Cargo -- Core.Cargo#CARGO_GROUP - self:__Board( -0.1 ) + 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 ) + function Fsm:onafterBoard( TaskUnit, Task, From, Event, To, Cargo ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - function self.Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess ) + function Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess ) self:F({From, Event, To, TaskUnit, TaskProcess }) - TaskProcess:__Boarded( 0.1 ) + TaskProcess:__Boarded( 0.1, self ) end - if self.Cargo:IsAlive() then - if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + 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 - self.Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() ) - if not self.Cargo:IsBoarding() then - self.Cargo:Board( TaskUnit, 20, self ) + Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() ) + if not Cargo:IsBoarding() then + Cargo:Board( TaskUnit, 20, self ) end end else @@ -499,24 +640,36 @@ do -- TASK_CARGO --- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterBoarded( TaskUnit, 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() } ) - self.Cargo:MessageToGroup( "Boarded ...", TaskUnit:GetGroup() ) - - TaskUnit:AddCargo( self.Cargo ) - - self:__SelectAction( 1 ) - - -- TODO:I need to find a more decent solution for this. - Task:E( { CargoPickedUp = Task.CargoPickedUp } ) - if self.Cargo:IsAlive() then - if Task.CargoPickedUp then - Task:CargoPickedUp( TaskUnit, self.Cargo ) - end + 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 @@ -530,7 +683,7 @@ do -- TASK_CARGO -- @param To -- @param Cargo -- @param Core.Zone#ZONE_BASE DeployZone - function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo ) + 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 @@ -568,9 +721,9 @@ do -- TASK_CARGO if self.Cargo:IsAlive() then self.Cargo:MessageToGroup( "UnBoarding ...", TaskUnit:GetGroup() ) if DeployZone then - self.Cargo:UnBoard( DeployZone:GetPointVec2(), 400, self ) + self.Cargo:UnBoard( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) else - self.Cargo:UnBoard( TaskUnit:GetPointVec2():AddX(60), 400, self ) + self.Cargo:UnBoard( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) end end end @@ -585,34 +738,34 @@ do -- TASK_CARGO local TaskUnitName = TaskUnit:GetName() self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - self.Cargo:MessageToGroup( "UnBoarded ...", TaskUnit:GetGroup() ) - - TaskUnit:RemoveCargo( self.Cargo ) - - local NotInDeployZones = true - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if self.Cargo:IsInZone( DeployZone ) then - NotInDeployZones = false - end - end + self.Cargo:MessageToGroup( "UnBoarded cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() ) - if NotInDeployZones == false then - self.Cargo:SetDeployed( true ) - end - - -- TODO:I need to find a more decent solution for this. - Task:E( { CargoDeployed = Task.CargoDeployed and "true" or "false" } ) - Task:E( { CargoIsAlive = self.Cargo:IsAlive() and "true" or "false" } ) - if self.Cargo:IsAlive() then - if Task.CargoDeployed then - Task:CargoDeployed( TaskUnit, self.Cargo, self.DeployZone ) - end - end + 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 @@ -675,13 +828,17 @@ do -- TASK_CARGO 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:GetBoardingRange() ) - ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), TaskUnit.Menu ) + ActRouteCargo:SetRange( Cargo:GetLoadRadius() ) + ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), MenuControl, MenuTime, "Cargo" ) ActRouteCargo:Start() + return self end @@ -694,11 +851,15 @@ do -- TASK_CARGO 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(), TaskUnit.Menu ) + ActRouteDeployZone:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Deploy Zone" .. DeployZone:GetName(), MenuControl, MenuTime, "Cargo" ) ActRouteDeployZone:Start() + return self end @@ -726,12 +887,12 @@ do -- TASK_CARGO end --- @param #TASK_CARGO self - -- @param @list DeployZones + -- @param #list DeployZones -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO function TASK_CARGO:SetDeployZones( DeployZones, TaskUnit ) - for DeployZoneID, DeployZone in pairs( DeployZones ) do + for DeployZoneID, DeployZone in pairs( DeployZones or {} ) do self.DeployZones[DeployZone:GetName()] = DeployZone end @@ -813,7 +974,6 @@ do -- TASK_CARGO function TASK_CARGO:UpdateTaskInfo( DetectedItem ) if self:IsStatePlanned() or self:IsStateAssigned() then - self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddCargoSet( self.SetCargo, 10, "SOD", true ) end end @@ -823,186 +983,8 @@ do -- TASK_CARGO return 0 end + + end -do -- TASK_CARGO_TRANSPORT - - --- The TASK_CARGO_TRANSPORT class - -- @type TASK_CARGO_TRANSPORT - -- @extends #TASK_CARGO - TASK_CARGO_TRANSPORT = { - ClassName = "TASK_CARGO_TRANSPORT", - } - - --- Instantiates a new TASK_CARGO_TRANSPORT. - -- @param #TASK_CARGO_TRANSPORT self - -- @param Tasking.Mission#MISSION Mission - -- @param 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 ) - - - -- Events - - self:AddTransition( "*", "CargoPickedUp", "*" ) - self:AddTransition( "*", "CargoDeployed", "*" ) - - self:F( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } ) - - --- OnBefore Transition Handler for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoPickedUp - -- @param #TASK_CARGO_TRANSPORT 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 Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoPickedUp - -- @param #TASK_CARGO_TRANSPORT 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 Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - --- Synchronous Event Trigger for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_TRANSPORT] CargoPickedUp - -- @param #TASK_CARGO_TRANSPORT self - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - --- Asynchronous Event Trigger for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_TRANSPORT] __CargoPickedUp - -- @param #TASK_CARGO_TRANSPORT self - -- @param #number Delay The delay in seconds. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - --- OnBefore Transition Handler for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoDeployed - -- @param #TASK_CARGO_TRANSPORT 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 Core.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 Return false to cancel Transition. - - --- OnAfter Transition Handler for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoDeployed - -- @param #TASK_CARGO_TRANSPORT 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 Core.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. - - --- Synchronous Event Trigger for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_TRANSPORT] CargoDeployed - -- @param #TASK_CARGO_TRANSPORT self - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Core.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. - - --- Asynchronous Event Trigger for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_TRANSPORT] __CargoDeployed - -- @param #TASK_CARGO_TRANSPORT self - -- @param #number Delay The delay in seconds. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Core.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( "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_Cargo_CSAR.lua b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua new file mode 100644 index 000000000..616f8a89b --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua @@ -0,0 +1,188 @@ +--- **Tasking** -- Models tasks for players to execute CSAR @{Cargo} downed pilots. +-- +-- === +-- +-- @module Tasking.Task_Cargo_CSAR +-- @image Task_Cargo_CSAR.JPG + + +do -- TASK_CARGO_CSAR + + --- The TASK_CARGO_CSAR class + -- @type TASK_CARGO_CSAR + -- @extends Tasking.Task_Cargo#TASK_CARGO + 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" } ) + + --- OnBefore Transition Handler for Event CargoPickedUp. + -- @function [parent=#TASK_CARGO_CSAR] OnBeforeCargoPickedUp + -- @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 Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. + -- @return #boolean Return false to cancel Transition. + + --- 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 Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. + + --- Synchronous Event Trigger for Event CargoPickedUp. + -- @function [parent=#TASK_CARGO_CSAR] CargoPickedUp + -- @param #TASK_CARGO_CSAR self + -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. + -- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. + + --- Asynchronous Event Trigger for Event CargoPickedUp. + -- @function [parent=#TASK_CARGO_CSAR] __CargoPickedUp + -- @param #TASK_CARGO_CSAR self + -- @param #number Delay The delay in seconds. + -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. + -- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. + + --- OnBefore Transition Handler for Event CargoDeployed. + -- @function [parent=#TASK_CARGO_CSAR] OnBeforeCargoDeployed + -- @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 Core.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 Return false to cancel Transition. + + --- 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 Core.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. + + --- Synchronous Event Trigger for Event CargoDeployed. + -- @function [parent=#TASK_CARGO_CSAR] CargoDeployed + -- @param #TASK_CARGO_CSAR self + -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. + -- @param Core.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. + + --- Asynchronous Event Trigger for Event CargoDeployed. + -- @function [parent=#TASK_CARGO_CSAR] __CargoDeployed + -- @param #TASK_CARGO_CSAR self + -- @param #number Delay The delay in seconds. + -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. + -- @param Core.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 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_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 -- Core.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 new file mode 100644 index 000000000..7644674da --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua @@ -0,0 +1,616 @@ +--- **Tasking** - Creates and manages player TASK_CARGO tasks. +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- ### Contributions: +-- +-- === +-- +-- @module Tasking.Task_Cargo_Dispatcher +-- @image MOOSE.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 + + --- @type TASK_CARGO_DISPATCHER.CSAR + -- @field Wrapper.Unit#UNIT PilotUnit + -- @field Tasking.Task#TASK Task + + + --- Implements the dynamic dispatching of cargo tasks. + -- + -- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia3.JPG) + -- + -- The EWR will detect units, will group them, and will dispatch @{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_CARGO_DISPATCHER\Dia9.JPG) + -- + -- * **CSAR Task**: Is created when a fiendly pilot has ejected from a plane, and needs to be rescued (sometimes behind enemy lines). + -- + -- ## 1. TASK\_A2A\_DISPATCHER constructor: + -- + -- The @{#TASK_CARGO_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_CARGO_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_CARGO_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_CARGO_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_CARGO_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 choosen, which is 6km. + -- The **EWRDetection** object is then passed to the @{#TASK_CARGO_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_CARGO_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_CARGO_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 @{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_CARGO_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! + -- + -- @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" ) + + --- 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 ) + + -- For CSAR missions, we process the event when a pilot ejects. + + self:HandleEvent( EVENTS.Ejection ) + + return self + 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 + local CSARTaskName = self:AddCSARTask( self.CSARTaskName, CSARCoordinate, CSARHeading, CSARCountry, self.CSARBriefing ) + self:SetCSARDeployZones( CSARTaskName, self.CSARDeployZones ) + 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#Country 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( 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( 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 + + 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 TaskName (optional) The name of the transport task. + -- @param Core.SetCargo#SET_CARGO SetCargo The SetCargo to be transported. + -- @param #string Briefing The briefing of the task transport to be shown to the player. + -- @return #TASK_CARGO_DISPATCHER + -- @usage + -- + -- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone. + function TASK_CARGO_DISPATCHER:AddTransportTask( TaskName, SetCargo, Briefing ) + + self.TransportCount = self.TransportCount + 1 + + local TaskName = string.format( ( TaskName or "Transport" ) .. ".%03d", self.TransportCount ) + + self.Transport[TaskName] = {} + self.Transport[TaskName].SetCargo = SetCargo + self.Transport[TaskName].Briefing = Briefing + self.Transport[TaskName].Task = nil + + return TaskName + end + + + --- Add a Transport task to transport cargo from fixed locations to a deployment zone. + -- @param #TASK_CARGO_DISPATCHER self + -- @param #string TaskName (optional) The name of the transport task. + -- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT + function TASK_CARGO_DISPATCHER:GetTransportTask( TaskName ) + + self:ManageTasks() + return self.Transport[TaskName] and self.Transport[TaskName].Task + end + + + --- Define one deploy zone for the Transport tasks. + -- @param #TASK_CARGO_DISPATCHER self + -- @param #string TaskName (optional) The name of the Transport task. + -- @param TransportDeployZone A Transport deploy zone. + -- @return #TASK_CARGO_DISPATCHER + function TASK_CARGO_DISPATCHER:SetTransportDeployZone( TaskName, TransportDeployZone ) + + if self.Transport[TaskName] then + self.Transport[TaskName].DeployZones = { TransportDeployZone } + else + error( "TaskName does not exist" ) + end + + return self + end + + + --- Define the deploy zones for the Transport tasks. + -- @param #TASK_CARGO_DISPATCHER self + -- @param #string TaskName (optional) The name of the Transport task. + -- @param TransportDeployZones A list of the Transport deploy zones. + -- @return #TASK_CARGO_DISPATCHER + -- + function TASK_CARGO_DISPATCHER:SetTransportDeployZones( TaskName, TransportDeployZones ) + + if self.Transport[TaskName] then + self.Transport[TaskName].DeployZones = TransportDeployZones + else + error( "TaskName does not exist" ) + end + + 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 + -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. + function TASK_CARGO_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. +-- 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 ) + 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 + 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 ) + Mission:AddTask( Transport.Task ) + TaskReport:Add( TransportName ) + if Transport.DeployZones then + Transport.Task:SetDeployZones( Transport.DeployZones or {} ) + else + Transport.Task:SetDeployZones( self.DefaultDeployZones or {} ) + end + + 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 + + + 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 ~= "" 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 new file mode 100644 index 000000000..88bcb0ecb --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua @@ -0,0 +1,112 @@ +--- **Tasking** -- The TASK_CARGO models tasks for players to transport @{Cargo}. +-- +-- === +-- +-- @module Tasking.Task_Cargo_Transport +-- @image Task_Cargo_Transport.JPG + + +do -- TASK_CARGO_TRANSPORT + + --- The TASK_CARGO_TRANSPORT class + -- @type TASK_CARGO_TRANSPORT + -- @extends Tasking.Task_CARGO#TASK_CARGO + 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 new file mode 100644 index 000000000..8c2d76406 --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_Manager.lua @@ -0,0 +1,193 @@ +--- 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. +-- +-- === +-- +-- ### 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() + self:E() + + end + +end + diff --git a/Moose Development/Moose/Tasking/Task_Pickup.lua b/Moose Development/Moose/Tasking/Task_Pickup.lua deleted file mode 100644 index 459f9f4a2..000000000 --- a/Moose Development/Moose/Tasking/Task_Pickup.lua +++ /dev/null @@ -1,132 +0,0 @@ ---- This module contains the TASK_PICKUP classes. --- --- 1) @{#TASK_PICKUP} class, extends @{Task#TASK} --- === --- The @{#TASK_PICKUP} class defines a pickup task of a @{Set} of @{CARGO} objects defined within the mission. --- based on the tasking capabilities defined in @{Task#TASK}. --- The TASK_PICKUP is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_PICKUP - - -do -- TASK_PICKUP - - --- The TASK_PICKUP class - -- @type TASK_PICKUP - -- @extends Tasking.Task#TASK - TASK_PICKUP = { - ClassName = "TASK_PICKUP", - } - - --- Instantiates a new TASK_PICKUP. - -- @param #TASK_PICKUP self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP AssignedSetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param #string TaskType BAI or CAS - -- @param Set#SET_UNIT UnitSetTargets - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #TASK_PICKUP self - function TASK_PICKUP:New( Mission, AssignedSetGroup, TaskName, TaskType ) - local self = BASE:Inherit( self, TASK:New( Mission, AssignedSetGroup, TaskName, TaskType, "PICKUP" ) ) - self:F() - - return self - end - - --- Removes a TASK_PICKUP. - -- @param #TASK_PICKUP self - -- @return #nil - function TASK_PICKUP:CleanUp() - - self:GetParent( self ):CleanUp() - - return nil - end - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_PICKUP self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_PICKUP self - function TASK_PICKUP:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, ACT_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessPickup = self:AddProcess( TaskUnit, PROCESS_PICKUP:New( self, self.TaskType, TaskUnit ) ) - - local Process = self:AddStateMachine( TaskUnit, FSM_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - }, - callbacks = { - onNext = self.OnNext, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Pickup = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - ProcessDestroy:AddScore( "Pickup", "Picked-Up a Cargo", 25 ) - ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_PICKUP self - -- @param Core.Fsm#FSM_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_PICKUP:OnNext( Fsm, From, Event, To, Event ) - - self:SetState( self, "State", To ) - - end - - --- @param #TASK_PICKUP self - function TASK_PICKUP:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - - --- @param #TASK_PICKUP self - function TASK_PICKUP:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_PICKUP self - function TASK_PICKUP._Scheduler() - self:F2() - - return true - end - -end - - - diff --git a/Moose Development/Moose/Utilities/Routines.lua b/Moose Development/Moose/Utilities/Routines.lua index 508a35ed5..c63359c2e 100644 --- a/Moose Development/Moose/Utilities/Routines.lua +++ b/Moose Development/Moose/Utilities/Routines.lua @@ -1,6 +1,6 @@ --- Various routines -- @module routines --- @author Flightcontrol +-- @image MOOSE.JPG env.setErrorMessageBoxEnabled(false) @@ -2290,7 +2290,7 @@ end function MessageToBlue( MsgText, MsgTime, MsgName ) --trace.f() - MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.RED ) + MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) end function getCarrierHeight( CarrierGroup ) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index e7f730ad1..5dab11d52 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -10,6 +10,7 @@ -- * FlightControl : Rework to OO framework -- -- @module Utils +-- @image MOOSE.JPG --- @type SMOKECOLOR @@ -29,6 +30,19 @@ SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR FLARECOLOR = trigger.flareColor -- #FLARECOLOR +--- Big smoke preset enum. +-- @type BIGSMOKEPRESET +BIGSMOKEPRESET = { + SmallSmokeAndFire=0, + MediumSmokeAndFire=1, + LargeSmokeAndFire=2, + HugeSmokeAndFire=3, + SmallSmoke=4, + MediumSmoke=5, + LargeSmoke=6, + HugeSmoke=7, +} + --- Utilities static class. -- @type UTILS UTILS = { diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index e6db9284c..d31779479 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -8,15 +8,14 @@ -- -- === -- --- @module Airbase +-- @module Wrapper.Airbase +-- @image Wrapper_Airbase.JPG --- @type AIRBASE -- @extends Wrapper.Positionable#POSITIONABLE ---- # AIRBASE class, extends @{Positionable#POSITIONABLE} --- --- AIRBASE is a wrapper class to handle the DCS Airbase objects: +--- Wrapper class to handle the DCS Airbase objects: -- -- * Support all DCS Airbase APIs. -- * Enhance with Airbase specific APIs not in the DCS Airbase API set. @@ -239,7 +238,7 @@ end --- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. -- @param #AIRBASE self --- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. +-- @param DCS#Airbase DCSAirbase An existing DCS Airbase object reference. -- @return Wrapper.Airbase#AIRBASE self function AIRBASE:Find( DCSAirbase ) diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 71e2066cb..7df3c0586 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module Client +-- @module Wrapper.Client +-- @image Wrapper_Client.JPG --- The CLIENT class @@ -16,9 +17,8 @@ -- @extends Wrapper.Unit#UNIT ---- # CLIENT class, extends @{Unit#UNIT} +--- Wrapper class of those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. -- --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. -- Note that clients are NOT the same as Units, they are NOT necessarily alive. -- The CLIENT class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: -- @@ -179,11 +179,10 @@ function CLIENT:ShowBriefing() if not self.ClientBriefingShown then self.ClientBriefingShown = true local Briefing = "" - if self.ClientBriefing then + if self.ClientBriefing and self.ClientBriefing ~= "" then Briefing = Briefing .. self.ClientBriefing + self:Message( Briefing, 60, "Briefing" ) end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) end return self @@ -275,7 +274,7 @@ end --- Return the DCSGroup of a Client. -- This function is modified to deal with a couple of bugs in DCS 1.5.3 -- @param #CLIENT self --- @return Dcs.DCSWrapper.Group#Group +-- @return DCS#Group The group of the Client. function CLIENT:GetDCSGroup() self:F3() @@ -349,10 +348,10 @@ function CLIENT:GetDCSGroup() end --- TODO: Check Dcs.DCSTypes#Group.ID +-- TODO: Check DCS#Group.ID --- Get the group ID of the client. -- @param #CLIENT self --- @return Dcs.DCSTypes#Group.ID +-- @return DCS#Group.ID function CLIENT:GetClientGroupID() local ClientGroup = self:GetDCSGroup() @@ -391,7 +390,7 @@ end --- Returns the DCSUnit of the CLIENT. -- @param #CLIENT self --- @return Dcs.DCSTypes#Unit +-- @return DCS#Unit function CLIENT:GetClientGroupDCSUnit() self:F2() @@ -412,8 +411,8 @@ function CLIENT:IsTransport() return self.ClientTransport end ---- Shows the @{AI_Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{AI_Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. +--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message. +-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system. -- @param #CLIENT self function CLIENT:ShowCargo() self:F() @@ -446,7 +445,7 @@ end -- @param #string Message is the text describing the message. -- @param #number MessageDuration is the duration in seconds that the Message should be displayed. -- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. +-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air. -- @param #string MessageID is the identifier of the message when displayed with intervals. function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index fe3d1378d..0b56cc0d7 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -8,20 +8,18 @@ -- -- === -- --- @module Controllable - +-- @module Wrapper.Controllable +-- @image Wrapper_Controllable.JPG --- @type CONTROLLABLE -- @extends Wrapper.Positionable#POSITIONABLE --- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. +-- @field DCS#Controllable DCSControllable The DCS controllable class. -- @field #string ControllableName The name of the controllable. ---- # CONTROLLABLE class, extends @{Positionable#POSITIONABLE} --- --- CONTROLLABLE is a wrapper class to handle the "DCS Controllable objects", which are Groups and Units: +--- Wrapper class to handle the "DCS Controllable objects", which are Groups and Units: -- -- * Support all DCS Controllable APIs. -- * Enhance with Controllable specific APIs not in the DCS Controllable API set. @@ -37,7 +35,7 @@ -- ## CONTROLLABLE Task methods -- -- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. +-- These methods return a string consisting of the task description, which can then be given to either a @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#SetTask} method to assign the task to the CONTROLLABLE. -- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. -- Each task description where applicable indicates for which controllable category the task is valid. -- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. @@ -63,7 +61,7 @@ -- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. -- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. -- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). +-- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). -- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. -- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. -- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. @@ -173,7 +171,7 @@ CONTROLLABLE = { --- Create a new CONTROLLABLE from a DCSControllable -- @param #CONTROLLABLE self --- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name +-- @param #string ControllableName The DCS Controllable name -- @return #CONTROLLABLE self function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) -- #CONTROLLABLE @@ -188,7 +186,7 @@ end --- Get the controller for the CONTROLLABLE. -- @param #CONTROLLABLE self --- @return Dcs.DCSController#Controller +-- @return DCS#Controller function CONTROLLABLE:_GetController() local DCSControllable = self:GetDCSObject() @@ -202,26 +200,6 @@ end -- Get methods ---- Returns the UNITs wrappers of the DCS Units of the Controllable (default is a GROUP). --- @param #CONTROLLABLE self --- @return #list The UNITs wrappers. -function CONTROLLABLE:GetUnits() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local DCSUnits = DCSControllable:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - --- Returns the health. Dead controllables have health <= 1.0. -- @param #CONTROLLABLE self @@ -377,9 +355,11 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local function SetTask( Controller, DCSTask ) if self and self:IsAlive() then local Controller = self:_GetController() + --self:I( "Before SetTask" ) Controller:setTask( DCSTask ) + --self:I( "After SetTask" ) else - BASE:E( DCSControllableName .. " is not alive anymore. Cannot set DCSTask " .. DCSTask ) + BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) end end @@ -416,13 +396,13 @@ end --- Return a condition section for a controlled task. -- @param #CONTROLLABLE self --- @param Dcs.DCSTime#Time time +-- @param DCS#Time time -- @param #string userFlag -- @param #boolean userFlagValue -- @param #string condition --- @param Dcs.DCSTime#Time duration +-- @param DCS#Time duration -- @param #number lastWayPoint --- return Dcs.DCSTasking.Task#Task +-- return DCS#Task function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) @@ -440,9 +420,9 @@ end --- Return a Controlled Task taking a Task and a TaskCondition. -- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return Dcs.DCSTasking.Task#Task +-- @param DCS#Task DCSTask +-- @param DCS#DCSStopCondition DCSStopCondition +-- @return DCS#Task function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) self:F2( { DCSTask, DCSStopCondition } ) @@ -462,8 +442,8 @@ end --- Return a Combo Task taking an array of Tasks. -- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{DCSTasking.Task#Task} --- @return Dcs.DCSTasking.Task#Task +-- @param DCS#TaskArray DCSTasks Array of @{DCSTasking.Task#Task} +-- @return DCS#Task function CONTROLLABLE:TaskCombo( DCSTasks ) self:F2( { DCSTasks } ) @@ -486,8 +466,8 @@ end --- Return a WrappedAction Task taking a Command. -- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand --- @return Dcs.DCSTasking.Task#Task +-- @param DCS#Command DCSCommand +-- @return DCS#Task function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) self:F2( { DCSCommand } ) @@ -510,13 +490,13 @@ end --- Set a Task at a Waypoint using a Route list. -- @param #CONTROLLABLE self -- @param #table Waypoint The Waypoint! --- @param Dcs.DCSTasking.Task#Task Task The Task structure to be executed! --- @return Dcs.DCSTasking.Task#Task +-- @param DCS#Task Task The Task structure to be executed! +-- @return DCS#Task function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task ) Waypoint.task = self:TaskCombo( { Task } ) - self:T3( { Waypoint.task } ) + self:F( { Waypoint.task } ) return Waypoint.task end @@ -525,7 +505,7 @@ end --- Executes a command action -- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand +-- @param DCS#Command DCSCommand -- @return #CONTROLLABLE self function CONTROLLABLE:SetCommand( DCSCommand ) self:F2( DCSCommand ) @@ -545,7 +525,7 @@ end -- @param #CONTROLLABLE self -- @param #number FromWayPoint -- @param #number ToWayPoint --- @return Dcs.DCSTasking.Task#Task +-- @return DCS#Task -- @usage -- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. -- HeliGroup = GROUP:FindByName( "Helicopter" ) @@ -584,7 +564,7 @@ end -- -- @param #CONTROLLABLE self -- @param #boolean StopRoute true if the ground unit needs to stop, false if it needs to continue to move. --- @return Dcs.DCSTasking.Task#Task +-- @return DCS#Task function CONTROLLABLE:CommandStopRoute( StopRoute ) self:F2( { StopRoute } ) @@ -607,12 +587,12 @@ end -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param DCS#Azimuth Direction (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. +-- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) @@ -664,13 +644,13 @@ end -- @param #CONTROLLABLE self -- @param Wrapper.Unit#UNIT AttackUnit The UNIT. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. +-- @param DCS#Azimuth Direction (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. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #boolean Visible (optional) not a clue. -- @param #number WeaponType (optional) The WeaponType. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskAttackUnit( AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, WeaponType ) self:F2( { self.ControllableName, AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, WeaponType } ) @@ -700,14 +680,14 @@ end --- (AIR) Delivering weapon at the point on the ground. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. +-- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. +-- @param DCS#Azimuth Direction (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. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType ) self:F2( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType } ) @@ -734,14 +714,14 @@ end --- (AIR) Attacking the map object (building, structure, e.t.c). -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. +-- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. +-- @param DCS#Azimuth Direction (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. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskAttackMapObject( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType ) self:F2( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType } ) @@ -769,9 +749,9 @@ end --- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. +-- @param DCS#Vec2 Point The point to hold the position. +-- @param #number Altitude The altitude [m] to hold the position. +-- @param #number Speed The speed [m/s] flying when holding the position. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) self:F2( { self.ControllableName, Point, Altitude, Speed } ) @@ -814,17 +794,18 @@ end --- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. -- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. +-- @param #number Altitude The altitude [m] to hold the position. +-- @param #number Speed The speed [m/s] flying when holding the position. +-- @param Core.Point#COORDINATE Coordinate The coordinate where to orbit. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) +function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate ) self:F2( { self.ControllableName, Altitude, Speed } ) local DCSControllable = self:GetDCSObject() if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) + local OrbitVec2 = Coordinate and Coordinate:GetVec2() or self:GetVec2() + return self:TaskOrbitCircleAtVec2( OrbitVec2, Altitude, Speed ) end return nil @@ -851,11 +832,11 @@ end -- @param #CONTROLLABLE self -- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. +-- @param DCS#Azimuth Direction (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. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) @@ -890,7 +871,7 @@ end --- (AIR) Refueling from the nearest tanker. No parameters. -- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskRefueling() self:F2( { self.ControllableName } ) @@ -912,7 +893,7 @@ end --- (AIR HELICOPTER) Landing at the ground. For helicopters only. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to land. +-- @param DCS#Vec2 Point The point where to land. -- @param #number Duration The duration in seconds to stay on the ground. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) @@ -949,7 +930,7 @@ function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) return DCSTask end ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). +--- (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). -- @param #CONTROLLABLE self -- @param Core.Zone#ZONE Zone The zone where to land. -- @param #number Duration The duration in seconds to stay on the ground. @@ -977,9 +958,9 @@ end -- If another controllable is on land the unit / controllable will orbit around. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) @@ -1019,11 +1000,11 @@ end -- The unit / controllable will also protect that controllable from threats of specified types. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. -- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) @@ -1065,12 +1046,13 @@ end --- (GROUND) Fire at a VEC2 point until ammunition is finished. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. +-- @param DCS#Vec2 Vec2 The point to fire at. +-- @param DCS#Distance Radius The radius of the zone to deploy the fire at. -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount ) - self:F2( { self.ControllableName, Vec2, Radius, AmmoCount } ) +-- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag +-- @return DCS#Task The DCS task structure. +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) + self:F2( { self.ControllableName, Vec2, Radius, AmmoCount, WeaponType } ) -- FireAtPoint = { -- id = 'FireAtPoint', @@ -1096,6 +1078,10 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount ) DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end + + if WeaponType then + DCSTask.params.weaponType=WeaponType + end self:T3( { DCSTask } ) return DCSTask @@ -1103,7 +1089,7 @@ end --- (GROUND) Hold ground controllable from moving. -- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskHold() self:F2( { self.ControllableName } ) @@ -1132,9 +1118,9 @@ end -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. -- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param DCS#AI.Task.Designation Designation (optional) Designation type. -- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) @@ -1166,10 +1152,10 @@ end --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. +-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. -- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) @@ -1199,11 +1185,11 @@ end --- (AIR) Engaging a targets of defined types at circle-shaped zone. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param Dcs.DCSTypes#Distance Radius Radius of the zone. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param DCS#Vec2 Vec2 2D-coordinates of the zone. +-- @param DCS#Distance Radius Radius of the zone. +-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes, Priority ) self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) @@ -1237,12 +1223,12 @@ end -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param DCS#Azimuth Direction (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. +-- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) @@ -1298,13 +1284,13 @@ end -- @param Wrapper.Unit#UNIT EngageUnit The UNIT. -- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (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 DCS#AI.Task.WeaponExpend WeaponExpend (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 AttackQty (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.DCSTypes#Azimuth Direction (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. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired altitude to perform the unit engagement. +-- @param DCS#Azimuth Direction (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. +-- @param DCS#Distance Altitude (optional) Desired altitude to perform the unit engagement. -- @param #boolean Visible (optional) Unit must be visible. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack ) self:F2( { self.ControllableName, EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack } ) @@ -1348,7 +1334,7 @@ end --- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskAWACS( ) self:F2( { self.ControllableName } ) @@ -1371,7 +1357,7 @@ end --- (AIR) Aircraft will act as a tanker for friendly units. No parameters. -- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskTanker( ) self:F2( { self.ControllableName } ) @@ -1396,7 +1382,7 @@ end --- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEWR( ) self:F2( { self.ControllableName } ) @@ -1426,9 +1412,9 @@ end -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param DCS#AI.Task.Designation Designation (optional) Designation type. -- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) @@ -1463,9 +1449,9 @@ end -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. +-- @param DCS#Distance Radius The maximal distance from the FAC to a target. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) self:F2( { self.ControllableName, Radius, Priority } ) @@ -1494,10 +1480,10 @@ end --- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param DCS#Vec2 Point The point where to wait. -- @param #number Duration The duration in seconds to wait. -- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return Dcs.DCSTasking.Task#Task The DCS task structure +-- @return DCS#Task The DCS task structure function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) @@ -1521,13 +1507,13 @@ end --- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. -- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param DCS#Vec2 Point The point where to wait. -- @param #number Radius The radius of the embarking zone around the Point. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) self:F2( { self.ControllableName, Point, Radius } ) - local DCSTask --Dcs.DCSTasking.Task#Task + local DCSTask --DCS#Task DCSTask = { id = 'EmbarkToTransport', params = { x = Point.x, y = Point.y, @@ -1622,7 +1608,7 @@ end --- (AIR + GROUND) Return a mission task from a mission template. -- @param #CONTROLLABLE self -- @param #table TaskMission A table containing the mission task. --- @return Dcs.DCSTasking.Task#Task +-- @return DCS#Task function CONTROLLABLE:TaskMission( TaskMission ) self:F2( Points ) @@ -1672,6 +1658,9 @@ do -- Patrol methods --- (GROUND) Patrol randomly to the waypoints the for the (parent) group. -- A random waypoint will be picked and the group will move towards that point. -- @param #CONTROLLABLE self + -- @param #number Speed Speed in km/h. + -- @param #string Formation The formation the group uses. + -- @param Core.Point#COORDINATE ToWaypoint The waypoint where the group should move to. -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRouteRandom( Speed, Formation, ToWaypoint ) @@ -1723,6 +1712,9 @@ do -- Patrol methods --- (GROUND) Patrol randomly to the waypoints the for the (parent) group. -- A random waypoint will be picked and the group will move towards that point. -- @param #CONTROLLABLE self + -- @param #table ZoneList Table of zones. + -- @param #number Speed Speed in km/h the group moves at. + -- @param #string Formation (Optional) Formation the group should use. -- @return #CONTROLLABLE function CONTROLLABLE:PatrolZones( ZoneList, Speed, Formation ) @@ -1752,7 +1744,7 @@ do -- Patrol methods -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( 120 ) + Route[#Route+1] = FromCoord:WaypointGround( 20 ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) @@ -1770,7 +1762,7 @@ end --- Return a Misson task to follow a given route defined by Points. -- @param #CONTROLLABLE self -- @param #table Points A table of route points. --- @return Dcs.DCSTasking.Task#Task +-- @return DCS#Task function CONTROLLABLE:TaskRoute( Points ) self:F2( Points ) @@ -1781,336 +1773,386 @@ function CONTROLLABLE:TaskRoute( Points ) return DCSTask end ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) +do -- Route methods - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - - local PointFrom = {} - PointFrom.x = ControllableVec3.x - PointFrom.y = ControllableVec3.z - PointFrom.alt = ControllableVec3.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table Route A table of Route Points. --- @param #number DelaySeconds Wait for the specified seconds before executing the Route. --- @return #CONTROLLABLE The CONTROLLABLE. -function CONTROLLABLE:Route( Route, DelaySeconds ) - self:F2( Route ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route. - self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1). - return self - end - - return nil -end - - ---- Make the GROUND Controllable to drive towards a specific point. --- @param #CONTROLLABLE self --- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. --- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h. --- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right". --- @param #number DelaySeconds Wait for the specified seconds before executing the Route. --- @return #CONTROLLABLE The CONTROLLABLE. -function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds ) - - local FromCoordinate = self:GetCoordinate() + --- (AIR + GROUND) Make the Controllable move to fly to a given point. + -- @param #CONTROLLABLE self + -- @param DCS#Vec3 Point The destination point in Vec3 format. + -- @param #number Speed The speed [m/s] to travel. + -- @return #CONTROLLABLE self + function CONTROLLABLE:RouteToVec2( Point, Speed ) + self:F2( { Point, Speed } ) - local FromWP = FromCoordinate:WaypointGround() - local ToWP = ToCoordinate:WaypointGround( Speed, Formation ) - - self:Route( { FromWP, ToWP }, DelaySeconds ) - - return self -end - ---- Make the GROUND Controllable to drive towards a specific point using (only) roads. --- @param #CONTROLLABLE self --- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. --- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h. --- @param #number DelaySeconds Wait for the specified seconds before executing the Route. --- @return #CONTROLLABLE The CONTROLLABLE. -function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds ) - - -- Current coordinate. - local FromCoordinate = self:GetCoordinate() + local ControllablePoint = self:GetUnit( 1 ):GetVec2() - -- Formation is set to on road. - local Formation="On Road" - - -- Path on road from current position to destination coordinate. - local path=FromCoordinate:GetPathOnRoad(ToCoordinate) - - -- Route, ground waypoints along roads. - local route={} - table.insert(route, FromCoordinate:WaypointGround(Speed, Formation)) - - -- Convert coordinates to ground waypoints and insert into table. - for _, coord in ipairs(path) do - table.insert(route, coord:WaypointGround(Speed, Formation)) - end - - -- Add the final coordinate because the final coordinate in path is last point on road. - local dist=ToCoordinate:Get2DDistance(path[#path]) - if dist>10 then - table.insert(route, ToCoordinate:WaypointGround(Speed, "Vee")) - end - - -- Route controllable to destination. - self:Route(route, DelaySeconds) - - return self -end - - ---- Make the AIR Controllable fly towards a specific point. --- @param #CONTROLLABLE self --- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. --- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type. --- @param Core.Point#COORDINATE.RoutePointType Type The route point type. --- @param Core.Point#COORDINATE.RoutePointAction Action The route point action. --- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h. --- @param #number DelaySeconds Wait for the specified seconds before executing the Route. --- @return #CONTROLLABLE The CONTROLLABLE. -function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds ) - - local FromCoordinate = self:GetCoordinate() - local FromWP = FromCoordinate:WaypointAir() - - local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed ) - - self:Route( { FromWP, ToWP }, DelaySeconds ) - - return self -end - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Core.Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local PointFrom = {} PointFrom.x = ControllablePoint.x PointFrom.y = ControllablePoint.y PointFrom.type = "Turning Point" - PointFrom.action = Formation or "Cone" - PointFrom.speed = 20 / 1.6 - - + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y + PointTo.x = Point.x + PointTo.y = Point.y PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + return self end - - return nil -end - ---- (GROUND) Route the controllable to a given Vec2. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param #Vec2 Vec2 The Vec2 where to route to. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - + + --- (AIR + GROUND) Make the Controllable move to a given point. + -- @param #CONTROLLABLE self + -- @param DCS#Vec3 Point The destination point in Vec3 format. + -- @param #number Speed The speed [m/s] to travel. + -- @return #CONTROLLABLE self + function CONTROLLABLE:RouteToVec3( Point, Speed ) + self:F2( { Point, Speed } ) + + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() + local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y + PointFrom.x = ControllableVec3.x + PointFrom.y = ControllableVec3.z + PointFrom.alt = ControllableVec3.y + PointFrom.alt_type = "BARO" PointFrom.type = "Turning Point" - PointFrom.action = Formation or "Cone" - PointFrom.speed = 20 / 1.6 - - + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + local PointTo = {} - - PointTo.x = Vec2.x - PointTo.y = Vec2.y + PointTo.x = Point.x + PointTo.y = Point.z + PointTo.alt = Point.y + PointTo.alt_type = "BARO" PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 60 / 3.6 - end - + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + + return self + end + + + + --- Make the controllable to follow a given route. + -- @param #CONTROLLABLE self + -- @param #table Route A table of Route Points. + -- @param #number DelaySeconds (Optional) Wait for the specified seconds before executing the Route. Default is one second. + -- @return #CONTROLLABLE The CONTROLLABLE. + function CONTROLLABLE:Route( Route, DelaySeconds ) + self:F2( Route ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route. + self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1). + return self + end + + return nil + end + + + --- Stops the movement of the vehicle on the route. + -- @param #CONTROLLABLE self + -- @return #CONTROLLABLE + function CONTROLLABLE:RouteStop() + self:F("RouteStop") + + local CommandStop = self:CommandStopRoute( true ) + self:SetCommand( CommandStop ) + + end + + --- Resumes the movement of the vehicle on the route. + -- @param #CONTROLLABLE self + -- @return #CONTROLLABLE + function CONTROLLABLE:RouteResume() + self:F("RouteResume") + + local CommandResume = self:CommandStopRoute( false ) + self:SetCommand( CommandResume ) + + end + + --- Make the GROUND Controllable to drive towards a specific point. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param #number Speed (optional) Speed in km/h. The default speed is 20 km/h. + -- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right". + -- @param #number DelaySeconds Wait for the specified seconds before executing the Route. + -- @return #CONTROLLABLE The CONTROLLABLE. + function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds ) + + local FromCoordinate = self:GetCoordinate() + + local FromWP = FromCoordinate:WaypointGround() + local ToWP = ToCoordinate:WaypointGround( Speed, Formation ) + + self:Route( { FromWP, ToWP }, DelaySeconds ) + + return self + end + + --- Make the GROUND Controllable to drive towards a specific point using (mostly) roads. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. + -- @param #number DelaySeconds (Optional) Wait for the specified seconds before executing the Route. Default is one second. + -- @param #string OffRoadFormation (Optional) The formation at initial and final waypoint. Default is "Off Road". + -- @return #CONTROLLABLE The CONTROLLABLE. + function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds, OffRoadFormation ) + + -- Defaults. + Speed=Speed or 20 + DelaySeconds=DelaySeconds or 1 + OffRoadFormation=OffRoadFormation or "Off Road" + + -- Get the route task. + local route=self:TaskGroundOnRoad(ToCoordinate, Speed, OffRoadFormation) + + -- Route controllable to destination. + self:Route( route, DelaySeconds ) + return self end - return nil -end + + --- Make a task for a GROUND Controllable to drive towards a specific point using (mostly) roads. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. + -- @param #string OffRoadFormation (Optional) The formation at initial and final waypoint. Default is "Off Road". + -- @return Task + function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, OffRoadFormation ) + self:F2({ToCoordinate=ToCoordinate, Speed=Speed, OffRoadFormation=OffRoadFormation}) + + -- Defaults. + Speed=Speed or 20 + OffRoadFormation=OffRoadFormation or "Off Road" + + -- Current coordinate. + local FromCoordinate = self:GetCoordinate() + + -- First point on road. + local FromOnRoad = FromCoordinate:GetClosestPointToRoad() + + -- Last Point on road. + local ToOnRoad = ToCoordinate:GetClosestPointToRoad() + + -- Route, ground waypoints along road. + local route={} + + -- Create waypoints. + table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) + table.insert(route, FromOnRoad:WaypointGround(Speed, "On Road")) + table.insert(route, ToOnRoad:WaypointGround(Speed, "On Road")) + + -- Add the final coordinate because the final might not be on the road. + local dist=ToCoordinate:Get2DDistance(ToOnRoad) + if dist>10 then + table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) + end + + return route + end + + --- Make the AIR Controllable fly towards a specific point. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type. + -- @param Core.Point#COORDINATE.RoutePointType Type The route point type. + -- @param Core.Point#COORDINATE.RoutePointAction Action The route point action. + -- @param #number Speed (optional) Speed in km/h. The default speed is 500 km/h. + -- @param #number DelaySeconds Wait for the specified seconds before executing the Route. + -- @return #CONTROLLABLE The CONTROLLABLE. + function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds ) + + local FromCoordinate = self:GetCoordinate() + local FromWP = FromCoordinate:WaypointAir() + + local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed ) + + self:Route( { FromWP, ToWP }, DelaySeconds ) + + return self + end + + + --- (AIR + GROUND) Route the controllable to a given zone. + -- The controllable final destination point can be randomized. + -- A speed can be given in km/h. + -- A given formation can be given. + -- @param #CONTROLLABLE self + -- @param Core.Zone#ZONE Zone The zone where to route to. + -- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. + -- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h. + -- @param Base#FORMATION Formation The formation string. + function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) + self:F2( Zone ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = Formation or "Cone" + PointFrom.speed = 20 / 3.6 + + + local PointTo = {} + local ZonePoint + + if Randomize then + ZonePoint = Zone:GetRandomVec2() + else + ZonePoint = Zone:GetVec2() + end + + PointTo.x = ZonePoint.x + PointTo.y = ZonePoint.y + PointTo.type = "Turning Point" + + if Formation then + PointTo.action = Formation + else + PointTo.action = "Cone" + end + + if Speed then + PointTo.speed = Speed + else + PointTo.speed = 20 / 3.6 + end + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self + end + + return nil + end + + --- (GROUND) Route the controllable to a given Vec2. + -- A speed can be given in km/h. + -- A given formation can be given. + -- @param #CONTROLLABLE self + -- @param DCS#Vec2 Vec2 The Vec2 where to route to. + -- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h. + -- @param Base#FORMATION Formation The formation string. + function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = Formation or "Cone" + PointFrom.speed = 20 / 3.6 + + + local PointTo = {} + + PointTo.x = Vec2.x + PointTo.y = Vec2.y + PointTo.type = "Turning Point" + + if Formation then + PointTo.action = Formation + else + PointTo.action = "Cone" + end + + if Speed then + PointTo.speed = Speed + else + PointTo.speed = 20 / 3.6 + end + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self + end + + return nil + end + +end -- Route methods -- Commands --- Do Script command -- @param #CONTROLLABLE self -- @param #string DoScript --- @return #DCSCommand +-- @return DCS#DCSCommand function CONTROLLABLE:CommandDoScript( DoScript ) local DCSDoScript = { @@ -2146,7 +2188,7 @@ end ---- Return the route of a controllable by using the @{Database#DATABASE} class. +--- Return the route of a controllable by using the @{Core.Database#DATABASE} class. -- @param #CONTROLLABLE self -- @param #number Begin The route point from where the copy will start. The base route point is 0. -- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. @@ -2221,7 +2263,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - self:T( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } ) + self:T2( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } ) return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) end @@ -2609,8 +2651,9 @@ function CONTROLLABLE:OptionAlarmStateGreen() if self:IsGround() then Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + elseif self:IsShip() then + -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! + --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) end return self @@ -2693,7 +2736,7 @@ end --- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. +-- Use the method @{Wrapper.Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. -- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. -- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! -- @param #CONTROLLABLE self @@ -2781,16 +2824,6 @@ function CONTROLLABLE:IsAirPlane() return nil end -function CONTROLLABLE:GetSize() - - local DCSObject = self:GetDCSObject() - - if DCSObject then - return 1 - else - return 0 - end -end -- Message APIs \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index f46c2701b..776cf923c 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -23,7 +23,8 @@ -- -- === -- --- @module Group +-- @module Wrapper.Group +-- @image Wrapper_Group.JPG --- @type GROUP @@ -31,8 +32,7 @@ -- @field #string GroupName The name of the group. ---- --- # GROUP class, extends @{Controllable#CONTROLLABLE} +--- Wrapper class of the DCS world Group object. -- -- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). @@ -51,21 +51,21 @@ -- -- ## GROUP task methods -- --- A GROUP is a @{Controllable}. See the @{Controllable} task methods section for a description of the task methods. +-- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} task methods section for a description of the task methods. -- -- ### Obtain the mission from group templates -- -- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: -- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. -- -- ## GROUP Command methods -- --- A GROUP is a @{Controllable}. See the @{Controllable} command methods section for a description of the command methods. +-- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} command methods section for a description of the command methods. -- -- ## GROUP option methods -- --- A GROUP is a @{Controllable}. See the @{Controllable} option methods section for a description of the option methods. +-- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} option methods section for a description of the option methods. -- -- ## GROUP Zone validation methods -- @@ -76,7 +76,7 @@ -- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. -- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. -- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. +-- The zone can be of any @{Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. -- -- ## GROUP AI methods -- @@ -112,16 +112,16 @@ GROUPTEMPLATE.Takeoff = { --- Create a new GROUP from a given GroupTemplate as a parameter. -- Note that the GroupTemplate is NOT spawned into the mission. --- It is merely added to the @{Database}. +-- It is merely added to the @{Core.Database}. -- @param #GROUP self -- @param #table GroupTemplate The GroupTemplate Structure exactly as defined within the mission editor. --- @param Dcs.DCScoalition#coalition.side CoalitionSide The coalition.side of the group. --- @param Dcs.DCSGroup#Group.Category CategoryID The Group.Category of the group. --- @param Dcs.DCScountry#country.id CountryID the country.id of the group. +-- @param DCS#coalition.side CoalitionSide The coalition.side of the group. +-- @param DCS#Group.Category CategoryID The Group.Category of the group. +-- @param DCS#country.id CountryID the country.id of the group. -- @return #GROUP self function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID ) local GroupName = GroupTemplate.name - _DATABASE:_RegisterGroupTemplate( GroupTemplate, CategoryID, CountryID, CoalitionSide, GroupName ) + _DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) self:F2( GroupName ) self.GroupName = GroupName @@ -151,7 +151,7 @@ end --- Find the GROUP wrapper class instance using the DCS Group. -- @param #GROUP self --- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. +-- @param DCS#Group DCSGroup The DCS Group. -- @return #GROUP The GROUP. function GROUP:Find( DCSGroup ) @@ -174,7 +174,7 @@ end --- Returns the DCS Group. -- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group The DCS Group. +-- @return DCS#Group The DCS Group. function GROUP:GetDCSObject() local DCSGroup = Group.getByName( self.GroupName ) @@ -185,9 +185,9 @@ function GROUP:GetDCSObject() return nil end ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. +--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) @@ -209,8 +209,8 @@ end -- * Exist at run-time. -- * Has at least one unit. -- --- When the first @{Unit} of the Group is active, it will return true. --- If the first @{Unit} of the Group is inactive, it will return false. +-- When the first @{Wrapper.Unit} of the Group is active, it will return true. +-- If the first @{Wrapper.Unit} of the Group is inactive, it will return false. -- -- @param #GROUP self -- @return #boolean true if the Group is alive and active. @@ -219,11 +219,11 @@ end function GROUP:IsAlive() self:F2( self.GroupName ) - local DCSGroup = self:GetDCSObject() -- Dcs.DCSGroup#Group + local DCSGroup = self:GetDCSObject() -- DCS#Group if DCSGroup then if DCSGroup:isExist() then - local DCSUnit = DCSGroup:getUnit(1) -- Dcs.DCSUnit#Unit + local DCSUnit = DCSGroup:getUnit(1) -- DCS#Unit if DCSUnit then local GroupIsAlive = DCSUnit:isActive() self:T3( GroupIsAlive ) @@ -236,17 +236,37 @@ function GROUP:IsAlive() end --- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. +-- Note that this destroy method also can raise a destroy event at run-time. +-- So all event listeners will catch the destroy event of this group for each unit in the group. +-- To raise these events, provide the `GenerateEvent` parameter. -- @param #GROUP self -function GROUP:Destroy() +-- @param #boolean GenerateEvent true if you want to generate a crash or dead event for each unit. +-- @usage +-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. +-- Helicopter = GROUP:FindByName( "Helicopter" ) +-- Helicopter:Destroy( true ) +-- @usage +-- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group. +-- Tanks = GROUP:FindByName( "Tanks" ) +-- Tanks:Destroy( true ) +-- @usage +-- -- Ship unit example: destroy the Ship silently. +-- Ship = GROUP:FindByName( "Ship" ) +-- Ship:Destroy( true ) +function GROUP:Destroy( GenerateEvent ) self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) + if GenerateEvent and GenerateEvent == true then + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + if self:IsAir() then + self:CreateEventCrash( timer.getTime(), UnitData ) + else + self:CreateEventDead( timer.getTime(), UnitData ) + end + end end USERFLAG:New( self:GetName() ):Set( 100 ) DCSGroup:destroy() @@ -259,7 +279,7 @@ end --- Returns category of the DCS Group. -- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group.Category The category ID +-- @return DCS#Group.Category The category ID function GROUP:GetCategory() self:F2( self.GroupName ) @@ -299,7 +319,7 @@ end --- Returns the coalition of the DCS Group. -- @param #GROUP self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. +-- @return DCS#coalition.side The coalition side of the DCS Group. function GROUP:GetCoalition() self:F2( self.GroupName ) @@ -315,7 +335,7 @@ end --- Returns the country of the DCS Group. -- @param #GROUP self --- @return Dcs.DCScountry#country.id The country identifier. +-- @return DCS#country.id The country identifier. -- @return #nil The DCS Group is not existing or alive. function GROUP:GetCountry() self:F2( self.GroupName ) @@ -330,13 +350,89 @@ function GROUP:GetCountry() return nil end +--- Returns the maximum speed of the group. +-- If the group is heterogenious and consists of different units, the max speed of the slowest unit is returned. +-- @param #GROUP self +-- @return #number Speed in km/h. +function GROUP:GetSpeedMax() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + + local Units=self:GetUnits() + + local speedmax=nil + + for _,unit in pairs(Units) do + local unit=unit --Wrapper.Unit#UNIT + local speed=unit:GetSpeedMax() + if speedmax==nil then + speedmax=speed + elseif speed The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}. +function GROUP:GetUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + local Units = {} + for Index, UnitData in pairs( DCSUnits ) do + Units[#Units+1] = UNIT:Find( UnitData ) + end + self:T3( Units ) + return Units + end + + return nil +end + + +--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player. +-- @param #GROUP self +-- @return #list The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}. +function GROUP:GetPlayerUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + local Units = {} + for Index, UnitData in pairs( DCSUnits ) do + local PlayerUnit = UNIT:Find( UnitData ) + if PlayerUnit:GetPlayerName() then + Units[#Units+1] = PlayerUnit + end + end + self:T3( Units ) + return Units + end + + return nil +end + + --- Returns the UNIT wrapper class with number UnitNumber. -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self -- @param #number UnitNumber The number of the UNIT wrapper class to be returned. -- @return Wrapper.Unit#UNIT The UNIT wrapper class. function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) + self:F3( { self.GroupName, UnitNumber } ) local DCSGroup = self:GetDCSObject() @@ -354,9 +450,9 @@ end -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self -- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. +-- @return DCS#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) + self:F3( { self.GroupName, UnitNumber } ) local DCSGroup = self:GetDCSObject() @@ -374,7 +470,7 @@ end -- @param #GROUP self -- @return #number The DCS Group size. function GROUP:GetSize() - self:F2( { self.GroupName } ) + self:F3( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -391,13 +487,85 @@ function GROUP:GetSize() return nil end + +--- Returns the average velocity Vec3 vector. +-- @param Wrapper.Group#GROUP self +-- @return DCS#Vec3 The velocity Vec3 vector +-- @return #nil The GROUP is not existing or alive. +function GROUP:GetVelocityVec3() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup and DCSGroup:isExist() then + local GroupUnits = DCSGroup:getUnits() + local GroupCount = #GroupUnits + + local VelocityVec3 = { x = 0, y = 0, z = 0 } + + for _, DCSUnit in pairs( GroupUnits ) do + local UnitVelocityVec3 = DCSUnit:getVelocity() + VelocityVec3.x = VelocityVec3.x + UnitVelocityVec3.x + VelocityVec3.y = VelocityVec3.y + UnitVelocityVec3.y + VelocityVec3.z = VelocityVec3.z + UnitVelocityVec3.z + end + + VelocityVec3.x = VelocityVec3.x / GroupCount + VelocityVec3.y = VelocityVec3.y / GroupCount + VelocityVec3.z = VelocityVec3.z / GroupCount + + return VelocityVec3 + end + + BASE:E( { "Cannot GetVelocityVec3", Group = self, Alive = self:IsAlive() } ) + + return nil +end + + +--- Returns the average group height in meters. +-- @param Wrapper.Group#GROUP self +-- @param #boolean FromGround Measure from the ground or from sea level. Provide **true** for measuring from the ground. **false** or **nil** if you measure from sea level. +-- @return DCS#Vec3 The height of the group. +-- @return #nil The GROUP is not existing or alive. +function GROUP:GetHeight( FromGround ) + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupUnits = DCSGroup:getUnits() + local GroupCount = #GroupUnits + + local GroupHeight = 0 + + for _, DCSUnit in pairs( GroupUnits ) do + local GroupPosition = DCSUnit:getPosition() + + if FromGround == true then + local LandHeight = land.getHeight( { x = GroupPosition.p.x, y = GroupPosition.p.z } ) + GroupHeight = GroupHeight + ( GroupPosition.p.y - LandHeight ) + else + GroupHeight = GroupHeight + GroupPosition.p.y + end + end + + return GroupHeight / GroupCount + end + + return nil +end + + + + --- --- Returns the initial size of the DCS Group. -- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. -- @param #GROUP self -- @return #number The DCS Group initial size. function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) + self:F3( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -492,7 +660,7 @@ end --- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. -- @param #GROUP self --- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. +-- @return DCS#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. function GROUP:GetVec2() self:F2( self.GroupName ) @@ -505,7 +673,7 @@ end --- Returns the current Vec3 vector of the first DCS Unit in the GROUP. -- @param #GROUP self --- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. +-- @return DCS#Vec3 Current Vec3 of the first DCS Unit of the GROUP. function GROUP:GetVec3() self:F2( self.GroupName ) @@ -554,13 +722,13 @@ function GROUP:GetCoordinate() end ---- Returns a random @{DCSTypes#Vec3} vector (point in 3D of the UNIT within the mission) within a range around the first UNIT of the GROUP. +--- Returns a random @{DCS#Vec3} vector (point in 3D of the UNIT within the mission) within a range around the first UNIT of the GROUP. -- @param #GROUP self -- @param #number Radius --- @return Dcs.DCSTypes#Vec3 The random 3D point vector around the first UNIT of the GROUP. +-- @return DCS#Vec3 The random 3D point vector around the first UNIT of the GROUP. -- @return #nil The GROUP is invalid or empty -- @usage --- -- If Radius is ignored, returns the Dcs.DCSTypes#Vec3 of first UNIT of the GROUP +-- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP function GROUP:GetRandomVec3(Radius) self:F2(self.GroupName) @@ -633,7 +801,7 @@ do -- Is Zone methods --- Returns true if all units of the group are within a @{Zone}. -- @param #GROUP self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} function GROUP:IsCompletelyInZone( Zone ) self:F2( { self.GroupName, Zone } ) @@ -653,7 +821,7 @@ end --- Returns true if some units of the group are within a @{Zone}. -- @param #GROUP self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is partially within the @{Zone#ZONE_BASE} +-- @return #boolean Returns true if the Group is partially within the @{Core.Zone#ZONE_BASE} function GROUP:IsPartlyInZone( Zone ) self:F2( { self.GroupName, Zone } ) @@ -681,7 +849,7 @@ end --- Returns true if none of the group units of the group are within a @{Zone}. -- @param #GROUP self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is not within the @{Zone#ZONE_BASE} +-- @return #boolean Returns true if the Group is not within the @{Core.Zone#ZONE_BASE} function GROUP:IsNotInZone( Zone ) self:F2( { self.GroupName, Zone } ) @@ -838,10 +1006,10 @@ do -- AI methods -- @return #GROUP The GROUP. function GROUP:SetAIOnOff( AIOnOff ) - local DCSGroup = self:GetDCSObject() -- Dcs.DCSGroup#Group + local DCSGroup = self:GetDCSObject() -- DCS#Group if DCSGroup then - local DCSController = DCSGroup:getController() -- Dcs.DCSController#Controller + local DCSController = DCSGroup:getController() -- DCS#Controller if DCSController then DCSController:setOnOff( AIOnOff ) return self @@ -948,7 +1116,7 @@ end --- Sets the CountryID of the group in a Template. -- @param #GROUP self --- @param Dcs.DCScountry#country.id CountryID The country ID. +-- @param DCS#country.id CountryID The country ID. -- @return #table function GROUP:SetTemplateCountry( Template, CountryID ) Template.CountryID = CountryID @@ -957,7 +1125,7 @@ end --- Sets the CoalitionID of the group in a Template. -- @param #GROUP self --- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. +-- @param DCS#coalition.side CoalitionID The coalition ID. -- @return #table function GROUP:SetTemplateCoalition( Template, CoalitionID ) Template.CoalitionID = CoalitionID @@ -1027,7 +1195,7 @@ function GROUP:InitRandomizePositionRadius( OuterRadius, InnerRadius ) end ---- Respawn the @{Group} at a @{Point}. +--- Respawn the @{Wrapper.Group} at a @{Point}. -- The method will setup the new group template according the Init(Respawn) settings provided for the group. -- These settings can be provided by calling the relevant Init...() methods of the Group. -- @@ -1093,7 +1261,7 @@ function GROUP:Respawn( Template, Reset ) else for UnitID, TemplateUnitData in pairs( Template.units ) do self:F( "Reset" ) - local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.z } + local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y } if Zone then if self.InitRespawnRandomizePositionZone then GroupUnitVec3 = Zone:GetRandomVec3() @@ -1121,10 +1289,106 @@ function GROUP:Respawn( Template, Reset ) self:ResetEvents() + return self + end +--- @param Wrapper.Group#GROUP self +function GROUP:RespawnAtAirbase( AirbaseRespawn, Takeoff, TakeoffAltitude ) -- R2.4 + self:F( { AirbaseRespawn, Takeoff, TakeoffAltitude } ) + local PointVec3 = AirbaseRespawn:GetPointVec3() + + Takeoff = Takeoff or SPAWN.Takeoff.Hot + + local SpawnTemplate = self:GetTemplate() + + if SpawnTemplate then + + local SpawnPoint = SpawnTemplate.route.points[1] + + -- These are only for ships. + SpawnPoint.linkUnit = nil + SpawnPoint.helipadId = nil + SpawnPoint.airdromeId = nil + + local AirbaseID = AirbaseRespawn:GetID() + local AirbaseCategory = AirbaseRespawn:GetDesc().category + self:F( { AirbaseCategory = AirbaseCategory, Ship = Airbase.Category.SHIP, Helipad = Airbase.Category.HELIPAD, Airdrome = Airbase.Category.AIRDROME } ) + + if AirbaseCategory == Airbase.Category.SHIP then + SpawnPoint.linkUnit = AirbaseID + SpawnPoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.HELIPAD then + SpawnPoint.linkUnit = AirbaseID + SpawnPoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.AIRDROME then + SpawnPoint.airdromeId = AirbaseID + end + + SpawnPoint.alt = 0 + + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + + + -- Translate the position of the Group Template to the Vec3. + for UnitID = 1, #SpawnTemplate.units do + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + + -- These cause a lot of confusion. + local UnitTemplate = SpawnTemplate.units[UnitID] + + UnitTemplate.parking = 15 + UnitTemplate.parking_id = "30" + UnitTemplate.alt = 0 + + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnPoint.x + local BY = SpawnPoint.y + local TX = PointVec3.x + ( SX - BX ) + local TY = PointVec3.z + ( SY - BY ) + + UnitTemplate.x = TX + UnitTemplate.y = TY + + if Takeoff == GROUP.Takeoff.Air then + UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 ) + --else + -- UnitTemplate.alt = PointVec3.y + 10 + end + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + end + + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z + + if Takeoff == GROUP.Takeoff.Air then + SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 ) + --else + -- SpawnPoint.alt = PointVec3.y + 10 + end + + SpawnTemplate.x = PointVec3.x + SpawnTemplate.y = PointVec3.z + + local GroupSpawned = self:Respawn( SpawnTemplate ) + + -- When spawned in the air, we need to generate a Takeoff Event + + if Takeoff == GROUP.Takeoff.Air then + for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 ) + end + end + + return GroupSpawned + end + + return nil +end --- Return the mission template of the group. @@ -1145,7 +1409,7 @@ function GROUP:GetTaskRoute() return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) end ---- Return the route of a group by using the @{Database#DATABASE} class. +--- Return the route of a group by using the @{Core.Database#DATABASE} class. -- @param #GROUP self -- @param #number Begin The route point from where the copy will start. The base route point is 0. -- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. @@ -1236,21 +1500,21 @@ end do -- Route methods - --- (AIR) Return the Group to an @{Airbase#AIRBASE}. + --- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}. -- The following things are to be taken into account: -- -- * The group is respawned to achieve the RTB, there may be side artefacts as a result of this. (Like weapons suddenly come back). -- * A group consisting out of more than one unit, may rejoin formation when respawned. -- * A speed can be given in km/h. If no speed is specified, the maximum speed of the first unit will be taken to return to base. - -- * When there is no @{Airbase} object specified, the group will return to the home base if the route of the group is pinned at take-off or at landing to a base. - -- * When there is no @{Airbase} object specified and the group route is not pinned to any airbase, it will return to the nearest airbase. + -- * When there is no @{Wrapper.Airbase} object specified, the group will return to the home base if the route of the group is pinned at take-off or at landing to a base. + -- * When there is no @{Wrapper.Airbase} object specified and the group route is not pinned to any airbase, it will return to the nearest airbase. -- -- @param #GROUP self - -- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase. + -- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase. -- @param #number Speed (optional) The Speed, if no Speed is given, the maximum Speed of the first unit is selected. -- @return #GROUP function GROUP:RouteRTB( RTBAirbase, Speed ) - self:F2( { RTBAirbase, Speed } ) + self:F( { RTBAirbase:GetName(), Speed } ) local DCSGroup = self:GetDCSObject() @@ -1291,9 +1555,7 @@ do -- Route methods Template.route.points = Points self:Respawn( Template ) - self:Route( Points ) - - self:Respawn(Template) + --self:Route( Points ) else self:ClearTasks() end @@ -1358,6 +1620,8 @@ do -- Players -- @return #nil The group has no players function GROUP:GetPlayerNames() + local HasPlayers = false + local PlayerNames = {} local Units = self:GetUnits() @@ -1367,11 +1631,36 @@ do -- Players if PlayerName and PlayerName ~= "" then PlayerNames = PlayerNames or {} table.insert( PlayerNames, PlayerName ) + HasPlayers = true end end + + if HasPlayers == true then + self:F2( PlayerNames ) + return PlayerNames + end - self:F2( PlayerNames ) - return PlayerNames + return nil + end + + + --- Get the active player count in the group. + -- @param #GROUP self + -- @return #number The amount of players. + function GROUP:GetPlayerCount() + + local PlayerCount = 0 + + local Units = self:GetUnits() + for UnitID, UnitData in pairs( Units or {} ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = Unit:GetPlayerName() + if PlayerName and PlayerName ~= "" then + PlayerCount = PlayerCount + 1 + end + end + + return PlayerCount end end diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index baa83eedb..6e226a466 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -8,15 +8,14 @@ -- -- === -- --- @module Identifiable +-- @module Wrapper.Identifiable +-- @image MOOSE.JPG --- @type IDENTIFIABLE -- @extends Wrapper.Object#OBJECT -- @field #string IdentifiableName The name of the identifiable. ---- # IDENTIFIABLE class, extends @{Object#OBJECT} --- --- The IDENTIFIABLE class is a wrapper class to handle the DCS Identifiable objects: +--- Wrapper class to handle the DCS Identifiable objects. -- -- * Support all DCS Identifiable APIs. -- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. @@ -44,7 +43,7 @@ local _CategoryName = { --- Create a new IDENTIFIABLE from a DCSIdentifiable -- @param #IDENTIFIABLE self --- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name +-- @param #string IdentifiableName The DCS Identifiable name -- @return #IDENTIFIABLE self function IDENTIFIABLE:New( IdentifiableName ) local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) @@ -62,7 +61,7 @@ end function IDENTIFIABLE:IsAlive() self:F3( self.IdentifiableName ) - local DCSIdentifiable = self:GetDCSObject() -- Dcs.DCSObject#Object + local DCSIdentifiable = self:GetDCSObject() -- DCS#Object if DCSIdentifiable then local IdentifiableIsAlive = DCSIdentifiable:isExist() @@ -110,7 +109,7 @@ end --- Returns category of the DCS Identifiable. -- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Object#Object.Category The category ID +-- @return DCS#Object.Category The category ID function IDENTIFIABLE:GetCategory() self:F2( self.ObjectName ) @@ -142,7 +141,7 @@ end --- Returns coalition of the Identifiable. -- @param #IDENTIFIABLE self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. +-- @return DCS#coalition.side The side of the coalition. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCoalition() self:F2( self.IdentifiableName ) @@ -191,7 +190,7 @@ end --- Returns country of the Identifiable. -- @param #IDENTIFIABLE self --- @return Dcs.DCScountry#country.id The country identifier. +-- @return DCS#country.id The country identifier. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCountry() self:F2( self.IdentifiableName ) @@ -212,7 +211,7 @@ end --- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. -- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. +-- @return DCS#Object.Desc The Identifiable descriptor. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetDesc() self:F2( self.IdentifiableName ) @@ -229,6 +228,26 @@ function IDENTIFIABLE:GetDesc() return nil end +--- Check if the Object has the attribute. +-- @param #IDENTIFIABLE self +-- @param #string AttributeName The attribute name. +-- @return #boolean true if the attribute exists. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:HasAttribute( AttributeName ) + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableHasAttribute = DCSIdentifiable:hasAttribute( AttributeName ) + self:T2( IdentifiableHasAttribute ) + return IdentifiableHasAttribute + end + + self:F( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + --- Gets the CallSign of the IDENTIFIABLE, which is a blank by default. -- @param #IDENTIFIABLE self -- @return #string The CallSign of the IDENTIFIABLE. diff --git a/Moose Development/Moose/Wrapper/Object.lua b/Moose Development/Moose/Wrapper/Object.lua index fc3c11126..f67ec389e 100644 --- a/Moose Development/Moose/Wrapper/Object.lua +++ b/Moose Development/Moose/Wrapper/Object.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module Object +-- @module Wrapper.Object +-- @image MOOSE.JPG --- @type OBJECT @@ -16,9 +17,7 @@ -- @field #string ObjectName The name of the Object. ---- # OBJECT class, extends @{Base#BASE} --- --- OBJECT handles the DCS Object objects: +--- Wrapper class to hendle the DCS Object objects. -- -- * Support all DCS Object APIs. -- * Enhance with Object specific APIs not in the DCS Object API set. @@ -28,7 +27,7 @@ -- -- The OBJECT class provides the following functions to construct a OBJECT instance: -- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. +-- * @{Wrapper.Object#OBJECT.New}(): Create a OBJECT instance. -- -- @field #OBJECT OBJECT = { @@ -42,7 +41,7 @@ OBJECT = { --- Create a new OBJECT from a DCSObject -- @param #OBJECT self --- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name +-- @param DCS#Object ObjectName The Object name -- @return #OBJECT self function OBJECT:New( ObjectName, Test ) local self = BASE:Inherit( self, BASE:New() ) @@ -55,7 +54,7 @@ end --- Returns the unit's unique identifier. -- @param Wrapper.Object#OBJECT self --- @return Dcs.DCSWrapper.Object#Object.ID ObjectID +-- @return DCS#Object.ID ObjectID -- @return #nil The DCS Object is not existing or alive. function OBJECT:GetID() @@ -73,6 +72,7 @@ end --- Destroys the OBJECT. -- @param #OBJECT self +-- @return #boolean true if the object is destroyed. -- @return #nil The DCS Unit is not existing or alive. function OBJECT:Destroy() @@ -80,7 +80,8 @@ function OBJECT:Destroy() if DCSObject then --BASE:CreateEventCrash( timer.getTime(), DCSObject ) - DCSObject:destroy() + DCSObject:destroy( false ) + return true end BASE:E( { "Cannot Destroy", Name = self.ObjectName, Class = self:GetClassName() } ) @@ -91,3 +92,5 @@ end + + diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 5a7f064c4..23b85806a 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module Positionable +-- @module Wrapper.Positionable +-- @image Wrapper_Positionable.JPG --- @type POSITIONABLE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-) -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -17,9 +18,7 @@ -- @extends Wrapper.Identifiable#IDENTIFIABLE ---- # POSITIONABLE class, extends @{Identifiable#IDENTIFIABLE} --- --- The POSITIONABLE class is a wrapper class to handle the POSITIONABLE objects: +--- Wrapper class to handle the POSITIONABLE objects. -- -- * Support all DCS APIs. -- * Enhance with POSITIONABLE specific APIs not in the DCS API set. @@ -61,7 +60,7 @@ POSITIONABLE.__.Cargo = {} --- Create a new POSITIONABLE from a DCSPositionable -- @param #POSITIONABLE self --- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name +-- @param #string PositionableName The POSITIONABLE name -- @return #POSITIONABLE self function POSITIONABLE:New( PositionableName ) local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) @@ -70,9 +69,9 @@ function POSITIONABLE:New( PositionableName ) return self end ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. +--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) @@ -90,9 +89,9 @@ function POSITIONABLE:GetPositionVec3() return nil end ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +--- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. +-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec2() self:F2( self.PositionableName ) @@ -129,7 +128,7 @@ function POSITIONABLE:GetPointVec2() local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - self:T2( PositionablePointVec2 ) + --self:F( PositionablePointVec2 ) return PositionablePointVec2 end @@ -186,13 +185,13 @@ function POSITIONABLE:GetCoordinate() end ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self -- @param #number Radius --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. -- @usage --- -- If Radius is ignored, returns the Dcs.DCSTypes#Vec3 of first UNIT of the GROUP +-- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP function POSITIONABLE:GetRandomVec3( Radius ) self:F2( self.PositionableName ) @@ -221,9 +220,9 @@ function POSITIONABLE:GetRandomVec3( Radius ) return nil end ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. +--- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec3() self:F2( self.PositionableName ) @@ -244,7 +243,7 @@ end --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self --- @return Dcs.DCSTypes#Distance The bounding box of the POSITIONABLE. +-- @return DCS#Distance The bounding box of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetBoundingBox() --R2.1 self:F2() @@ -252,7 +251,7 @@ function POSITIONABLE:GetBoundingBox() --R2.1 local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableDesc = DCSPositionable:getDesc() --Dcs.DCSTypes#Desc + local PositionableDesc = DCSPositionable:getDesc() --DCS#Desc if PositionableDesc then local PositionableBox = PositionableDesc.box return PositionableBox @@ -267,7 +266,7 @@ end --- Returns the altitude of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return DCS#Distance The altitude of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAltitude() self:F2() @@ -275,7 +274,7 @@ function POSITIONABLE:GetAltitude() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 + local PositionablePointVec3 = DCSPositionable:getPoint() --DCS#Vec3 return PositionablePointVec3.y end @@ -309,6 +308,18 @@ function POSITIONABLE:IsAboveRunway() end +function POSITIONABLE:GetSize() + + local DCSObject = self:GetDCSObject() + + if DCSObject then + return 1 + else + return 0 + end +end + + --- Returns the POSITIONABLE heading in degrees. -- @param Wrapper.Positionable#POSITIONABLE self @@ -372,14 +383,14 @@ end --- Returns the POSITIONABLE velocity Vec3 vector. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The velocity Vec3 vector +-- @return DCS#Vec3 The velocity Vec3 vector -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocityVec3() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - if DCSPositionable then + if DCSPositionable and DCSPositionable:isExist() then local PositionableVelocityVec3 = DCSPositionable:getVelocity() self:T3( PositionableVelocityVec3 ) return PositionableVelocityVec3 @@ -393,7 +404,7 @@ end --- Returns the POSITIONABLE height in meters. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The height of the positionable. +-- @return DCS#Vec3 The height of the positionable. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetHeight() --R2.1 self:F2( self.PositionableName ) @@ -421,7 +432,7 @@ function POSITIONABLE:GetVelocityKMH() local DCSPositionable = self:GetDCSObject() - if DCSPositionable then + if DCSPositionable and DCSPositionable:isExist() then local VelocityVec3 = self:GetVelocityVec3() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = Velocity * 3.6 -- now it is in km/h. @@ -440,7 +451,7 @@ function POSITIONABLE:GetVelocityMPS() local DCSPositionable = self:GetDCSObject() - if DCSPositionable then + if DCSPositionable and DCSPositionable:isExist() then local VelocityVec3 = self:GetVelocityVec3() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec self:T3( Velocity ) @@ -472,7 +483,7 @@ end --- Returns a message with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -- @return Core.Message#MESSAGE function POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText @@ -507,7 +518,7 @@ end -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToAll( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -524,12 +535,13 @@ end -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. --- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message. -function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition ) +-- @param DCS#Duration Duration The duration of the message. +-- @param DCS#coalition MessageCoalition The Coalition receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) self:F2( { Message, Duration } ) - local Name = "" + local Name = Name or "" local DCSObject = self:GetDCSObject() if DCSObject then @@ -545,11 +557,12 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. --- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message. -function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition ) +-- @param DCS#coalition MessageCoalition The Coalition receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition, Name ) self:F2( { Message, MessageType } ) - local Name = "" + local Name = Name or "" local DCSObject = self:GetDCSObject() if DCSObject then @@ -564,7 +577,7 @@ end -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToRed( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -581,7 +594,7 @@ end -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToBlue( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -598,7 +611,7 @@ end -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Client#CLIENT Client The client object receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) @@ -612,11 +625,11 @@ function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) return nil end ---- Send a message to a @{Group}. +--- Send a message to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) @@ -639,7 +652,7 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) return nil end ---- Send a message of a message type to a @{Group}. +--- Send a message of a message type to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text @@ -659,11 +672,11 @@ function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Na return nil end ---- Send a message to a @{Set#SET_GROUP}. +--- Send a message to a @{Core.Set#SET_GROUP}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_GROUP MessageSetGroup The SET_GROUP collection receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1 @@ -683,11 +696,11 @@ function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Nam return nil end ---- Send a message to the players in the @{Group}. +--- Send a message to the players in the @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param DCS#Duration Duration The duration of the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:Message( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -700,18 +713,18 @@ function POSITIONABLE:Message( Message, Duration, Name ) return nil end ---- Create a @{Radio#RADIO}, to allow radio transmission for this POSITIONABLE. +--- Create a @{Core.Radio#RADIO}, to allow radio transmission for this POSITIONABLE. -- Set parameters with the methods provided, then use RADIO:Broadcast() to actually broadcast the message -- @param #POSITIONABLE self --- @return #RADIO Radio +-- @return Core.Radio#RADIO Radio function POSITIONABLE:GetRadio() --R2.1 self:F2(self) return RADIO:New(self) end ---- Create a @{Radio#BEACON}, to allow this POSITIONABLE to broadcast beacon signals +--- Create a @{Core.Radio#BEACON}, to allow this POSITIONABLE to broadcast beacon signals -- @param #POSITIONABLE self --- @return #RADIO Radio +-- @return Core.Radio#RADIO Radio function POSITIONABLE:GetBeacon() --R2.1 self:F2(self) return BEACON:New(self) @@ -794,6 +807,15 @@ function POSITIONABLE:AddCargo( Cargo ) return self end +--- Get all contained cargo. +-- @param #POSITIONABLE self +-- @return #POSITIONABLE +function POSITIONABLE:GetCargo() + return self.__.Cargo +end + + + --- Remove cargo. -- @param #POSITIONABLE self -- @param Core.Cargo#CARGO Cargo diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index e999917b1..01d9af957 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -8,7 +8,8 @@ -- -- === -- --- @module Scenery +-- @module Wrapper.Scenery +-- @image Wrapper_Scenery.JPG @@ -16,10 +17,9 @@ -- @extends Wrapper.Positionable#POSITIONABLE ---- # SCENERY class, extends @{Positionable#POSITIONABLE} +--- Wrapper class to handle Scenery objects that are defined on the map. -- --- Scenery objects are defined on the map. --- The @{Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects: +-- The @{Wrapper.Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects: -- -- * Wraps the DCS Scenery objects. -- * Support all DCS Scenery APIs. diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 28e7c364b..a591522af 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -8,17 +8,17 @@ -- -- === -- --- @module Static +-- @module Wrapper.Static +-- @image Wrapper_Static.JPG --- @type STATIC -- @extends Wrapper.Positionable#POSITIONABLE ---- # STATIC class, extends @{Positionable#POSITIONABLE} +--- Wrapper class to handle Static objects. -- --- Statics are **Static Units** defined within the Mission Editor. -- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: +-- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: -- -- * Wraps the DCS Static objects. -- * Support all DCS Static APIs. @@ -48,6 +48,24 @@ STATIC = { } +function STATIC:Register( StaticName ) + local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) + self.StaticName = StaticName + return self +end + + +--- Finds a STATIC from the _DATABASE using a DCSStatic object. +-- @param #STATIC self +-- @param DCS#StaticObject DCSStatic An existing DCS Static object reference. +-- @return #STATIC self +function STATIC:Find( DCSStatic ) + + local StaticName = DCSStatic:getName() + local StaticFound = _DATABASE:FindStatic( StaticName ) + return StaticFound +end + --- Finds a STATIC from the _DATABASE using the relevant Static Name. -- As an optional parameter, a briefing text can be given also. -- @param #STATIC self @@ -71,12 +89,6 @@ function STATIC:FindByName( StaticName, RaiseError ) return nil end -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - self.StaticName = StaticName - return self -end - function STATIC:GetDCSObject() local DCSStatic = StaticObject.getByName( self.StaticName ) @@ -88,21 +100,62 @@ function STATIC:GetDCSObject() return nil end +--- Returns a list of one @{Static}. +-- @param #STATIC self +-- @return #list A list of one @{Static}. +function STATIC:GetUnits() + self:F2( { self.StaticName } ) + local DCSStatic = self:GetDCSObject() + + local Statics = {} + + if DCSStatic then + Statics[1] = STATIC:Find( DCSStatic ) + self:T3( Statics ) + return Statics + end + + return nil +end + + + + function STATIC:GetThreatLevel() return 1, "Static" end ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. --- @param #UNIT self +--- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. +-- @param #STATIC self -- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. -- @param #number Heading The heading of the unit respawn. -function STATIC:ReSpawn( Coordinate, Heading ) +function STATIC:SpawnAt( Coordinate, Heading ) - - -- todo: need to fix country - local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, country.id.USA ) + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName ) end + +--- Respawn the @{Wrapper.Unit} at the same location with the same properties. +-- This is useful to respawn a cargo after it has been destroyed. +-- @param #STATIC self +function STATIC:ReSpawn() + + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) + + SpawnStatic:ReSpawn() +end + + +--- Respawn the @{Wrapper.Unit} at a defined Coordinate with an optional heading. +-- @param #STATIC self +-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static. +-- @param #number Heading The heading of the unit respawn. +function STATIC:ReSpawnAt( Coordinate, Heading ) + + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) + + SpawnStatic:ReSpawnAt( Coordinate, Heading ) +end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 26d30ed91..753b842f4 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -17,16 +17,14 @@ -- -- === -- --- @module Unit +-- @module Wrapper.Unit +-- @image Wrapper_Unit.JPG --- @type UNIT -- @extends Wrapper.Controllable#CONTROLLABLE ---- --- # UNIT class, extends @{Controllable#CONTROLLABLE} --- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. +--- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). -- -- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference @@ -47,7 +45,7 @@ -- -- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. -- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSWrapper.Unit#Unit.getName}() +-- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCS#Unit.getName}() -- is implemented in the UNIT class as @{#UNIT.GetName}(). -- -- ## Smoke, Flare Units @@ -75,7 +73,7 @@ -- -- ### Zones range -- --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. +-- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Core.Zone#ZONE_BASE}. -- -- ### Unit range -- @@ -118,7 +116,7 @@ end --- Finds a UNIT from the _DATABASE using a DCSUnit object. -- @param #UNIT self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. +-- @param DCS#Unit DCSUnit An existing DCS Unit object reference. -- @return #UNIT self function UNIT:Find( DCSUnit ) @@ -147,7 +145,7 @@ end --- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit +-- @return DCS#Unit function UNIT:GetDCSObject() local DCSUnit = Unit.getByName( self.UnitName ) @@ -161,18 +159,28 @@ end --- Destroys the UNIT. -- @param #UNIT self +-- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the unit. -- @return #nil The DCS Unit is not existing or alive. -function UNIT:Destroy() +function UNIT:Destroy( GenerateEvent ) self:F2( self.ObjectName ) local DCSObject = self:GetDCSObject() if DCSObject then + local UnitGroup = self:GetGroup() local UnitGroupName = UnitGroup:GetName() self:F( { UnitGroupName = UnitGroupName } ) + + if GenerateEvent and GenerateEvent == true then + if self:IsAir() then + self:CreateEventCrash( timer.getTime(), DCSObject ) + else + self:CreateEventDead( timer.getTime(), DCSObject ) + end + end + USERFLAG:New( UnitGroupName ):Set( 100 ) - --BASE:CreateEventCrash( timer.getTime(), DCSObject ) DCSObject:destroy() end @@ -180,7 +188,7 @@ function UNIT:Destroy() end ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. +--- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. -- -- This function will: -- @@ -189,20 +197,22 @@ end -- * Then it will respawn the re-modelled group. -- -- @param #UNIT self --- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. +-- @param Core.Point#COORDINATE Coordinate The position where to Spawn the new Unit at. -- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawn( SpawnVec3, Heading ) +function UNIT:ReSpawnAt( Coordinate, Heading ) + self:T( self:Name() ) local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) self:T( SpawnGroupTemplate ) local SpawnGroup = self:GetGroup() + self:T( { SpawnGroup = SpawnGroup } ) if SpawnGroup then local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = SpawnVec3.x - SpawnGroupTemplate.y = SpawnVec3.z + SpawnGroupTemplate.x = Coordinate.x + SpawnGroupTemplate.y = Coordinate.z self:F( #SpawnGroupTemplate.units ) for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do @@ -221,12 +231,13 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) end for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( UnitTemplateData.name ) + self:T( { UnitTemplateData.name, self:Name() } ) + SpawnGroupTemplate.units[UnitTemplateID].unitId = nil if UnitTemplateData.name == self:Name() then self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y - SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x - SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z + SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y + SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x + SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z SpawnGroupTemplate.units[UnitTemplateID].heading = Heading self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) else @@ -261,6 +272,10 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) i = i + 1 end end + + SpawnGroupTemplate.groupId = nil + + self:T( SpawnGroupTemplate ) _DATABASE:Spawn( SpawnGroupTemplate ) end @@ -296,7 +311,7 @@ end function UNIT:IsAlive() self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() -- Dcs.DCSUnit#Unit + local DCSUnit = self:GetDCSObject() -- DCS#Unit if DCSUnit then local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive() @@ -319,6 +334,9 @@ function UNIT:GetCallsign() if DCSUnit then local UnitCallSign = DCSUnit:getCallsign() + if UnitCallSign == "" then + UnitCallSign = DCSUnit:getName() + end return UnitCallSign end @@ -334,18 +352,30 @@ end function UNIT:GetPlayerName() self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() + local DCSUnit = self:GetDCSObject() -- DCS#Unit if DCSUnit then local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" + -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 + if PlayerName == nil or PlayerName == "" then + local PlayerCategory = DCSUnit:getDesc().category + if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then + PlayerName = "Player" .. DCSUnit:getID() + end end +-- -- Good code +-- if PlayerName == nil then +-- PlayerName = nil +-- else +-- if PlayerName == "" then +-- PlayerName = "Player" .. DCSUnit:getID() +-- end +-- end return PlayerName end - return nil + return nil end @@ -369,6 +399,23 @@ function UNIT:GetNumber() return nil end + +--- Returns the unit's max speed in km/h derived from the DCS descriptors. +-- @param #UNIT self +-- @return #number Speed in km/h. +function UNIT:GetSpeedMax() + self:F2( self.UnitName ) + + local Desc = self:GetDesc() + + if Desc then + local SpeedMax = Desc.speedMax + return SpeedMax*3.6 + end + + return nil +end + --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self -- @return Wrapper.Group#GROUP The Group of the Unit. @@ -379,7 +426,7 @@ function UNIT:GetGroup() local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) + local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() ) return UnitGroup end @@ -411,7 +458,7 @@ end --- Returns the Unit's ammunition. -- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Ammo +-- @return DCS#Unit.Ammo -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetAmmo() self:F2( self.UnitName ) @@ -428,7 +475,7 @@ end --- Returns the unit sensors. -- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Sensors +-- @return DCS#Unit.Sensors -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetSensors() self:F2( self.UnitName ) @@ -492,7 +539,7 @@ end -- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. -- @param #UNIT self -- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @return DCS#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetRadar() self:F2( self.UnitName ) @@ -524,16 +571,16 @@ function UNIT:GetFuel() return nil end ---- Returns the UNIT in a UNIT list of one element. +--- Returns a list of one @{Wrapper.Unit}. -- @param #UNIT self --- @return #list The UNITs wrappers. +-- @return #list A list of one @{Wrapper.Unit}. function UNIT:GetUnits() self:F2( { self.UnitName } ) local DCSUnit = self:GetDCSObject() + local Units = {} + if DCSUnit then - local DCSUnits = DCSUnit:getUnits() - local Units = {} Units[1] = UNIT:Find( DCSUnit ) self:T3( Units ) return Units @@ -628,12 +675,9 @@ function UNIT:GetThreatLevel() if Descriptor then local Attributes = Descriptor.attributes - self:T( Attributes ) if self:IsGround() then - self:T( "Ground" ) - local ThreatLevels = { "Unarmed", "Infantry", @@ -670,8 +714,6 @@ function UNIT:GetThreatLevel() if self:IsAir() then - self:T( "Air" ) - local ThreatLevels = { "Unarmed", "Tanker", @@ -704,8 +746,6 @@ function UNIT:GetThreatLevel() if self:IsShip() then - self:T( "Ship" ) - --["Aircraft Carriers"] = {"Heavy armed ships",}, --["Cruisers"] = {"Heavy armed ships",}, --["Destroyers"] = {"Heavy armed ships",}, @@ -743,7 +783,6 @@ function UNIT:GetThreatLevel() end end - self:T2( ThreatLevel ) return ThreatLevel, ThreatText end @@ -754,7 +793,7 @@ end --- Returns true if the unit is within a @{Zone}. -- @param #UNIT self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} +-- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} function UNIT:IsInZone( Zone ) self:F2( { self.UnitName, Zone } ) @@ -769,7 +808,7 @@ end --- Returns true if the unit is not within a @{Zone}. -- @param #UNIT self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} +-- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE} function UNIT:IsNotInZone( Zone ) self:F2( { self.UnitName, Zone } ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 1e575f0f2..ea44b6326 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -21,7 +21,6 @@ Core/Radio.lua Core/Spawn.lua Core/SpawnStatic.lua Core/Goal.lua -Core/Cargo.lua Core/Spot.lua Wrapper/Object.lua @@ -35,6 +34,12 @@ Wrapper/Static.lua Wrapper/Airbase.lua Wrapper/Scenery.lua +Cargo/Cargo.lua +Cargo/CargoUnit.lua +Cargo/CargoSlingload.lua +Cargo/CargoCrate.lua +Cargo/CargoGroup.lua + Functional/Scoring.lua Functional/CleanUp.lua Functional/Movement.lua @@ -49,6 +54,9 @@ Functional/Range.lua Functional/ZoneGoal.lua Functional/ZoneGoalCoalition.lua Functional/ZoneCaptureCoalition.lua +Functional/Artillery.lua +Functional/Suppression.lua +Functional/PseudoATC.lua AI/AI_Balancer.lua AI/AI_A2A.lua @@ -61,6 +69,13 @@ AI/AI_Cap.lua AI/AI_Cas.lua AI/AI_Bai.lua AI/AI_Formation.lua +AI/AI_Cargo_APC.lua +AI/AI_Cargo_Helicopter.lua +AI/AI_Cargo_Airplane.lua +AI/AI_Cargo_Dispatcher.lua +AI/AI_Cargo_Dispatcher_APC.lua +AI/AI_Cargo_Dispatcher_Helicopter.lua +AI/AI_Cargo_Dispatcher_Airplane.lua Actions/Act_Assign.lua Actions/Act_Route.lua @@ -71,12 +86,16 @@ Tasking/CommandCenter.lua Tasking/Mission.lua Tasking/Task.lua Tasking/TaskInfo.lua +Tasking/Task_Manager.lua Tasking/DetectionManager.lua Tasking/Task_A2G_Dispatcher.lua Tasking/Task_A2G.lua Tasking/Task_A2A_Dispatcher.lua Tasking/Task_A2A.lua Tasking/Task_Cargo.lua +Tasking/Task_Cargo_Transport.lua +Tasking/Task_Cargo_CSAR.lua +Tasking/Task_Cargo_Dispatcher.lua Tasking/TaskZoneCapture.lua Moose.lua